1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Serial Port driver for Aspeed VUART device |
4 | * |
5 | * Copyright (C) 2016 Jeremy Kerr <jk@ozlabs.org>, IBM Corp. |
6 | * Copyright (C) 2006 Arnd Bergmann <arnd@arndb.de>, IBM Corp. |
7 | */ |
8 | #include <linux/device.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of_address.h> |
11 | #include <linux/of_irq.h> |
12 | #include <linux/of_platform.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/mfd/syscon.h> |
15 | #include <linux/tty.h> |
16 | #include <linux/tty_flip.h> |
17 | #include <linux/clk.h> |
18 | |
19 | #include "8250.h" |
20 | |
21 | #define ASPEED_VUART_GCRA 0x20 |
22 | #define ASPEED_VUART_GCRA_VUART_EN BIT(0) |
23 | #define ASPEED_VUART_GCRA_HOST_SIRQ_POLARITY BIT(1) |
24 | #define ASPEED_VUART_GCRA_DISABLE_HOST_TX_DISCARD BIT(5) |
25 | #define ASPEED_VUART_GCRB 0x24 |
26 | #define ASPEED_VUART_GCRB_HOST_SIRQ_MASK GENMASK(7, 4) |
27 | #define ASPEED_VUART_GCRB_HOST_SIRQ_SHIFT 4 |
28 | #define ASPEED_VUART_ADDRL 0x28 |
29 | #define ASPEED_VUART_ADDRH 0x2c |
30 | |
31 | #define ASPEED_VUART_DEFAULT_LPC_ADDR 0x3f8 |
32 | #define ASPEED_VUART_DEFAULT_SIRQ 4 |
33 | #define ASPEED_VUART_DEFAULT_SIRQ_POLARITY IRQ_TYPE_LEVEL_LOW |
34 | |
35 | struct aspeed_vuart { |
36 | struct device *dev; |
37 | int line; |
38 | struct timer_list unthrottle_timer; |
39 | struct uart_8250_port *port; |
40 | }; |
41 | |
42 | /* |
43 | * If we fill the tty flip buffers, we throttle the data ready interrupt |
44 | * to prevent dropped characters. This timeout defines how long we wait |
45 | * to (conditionally, depending on buffer state) unthrottle. |
46 | */ |
47 | static const int unthrottle_timeout = HZ/10; |
48 | |
49 | /* |
50 | * The VUART is basically two UART 'front ends' connected by their FIFO |
51 | * (no actual serial line in between). One is on the BMC side (management |
52 | * controller) and one is on the host CPU side. |
53 | * |
54 | * It allows the BMC to provide to the host a "UART" that pipes into |
55 | * the BMC itself and can then be turned by the BMC into a network console |
56 | * of some sort for example. |
57 | * |
58 | * This driver is for the BMC side. The sysfs files allow the BMC |
59 | * userspace which owns the system configuration policy, to specify |
60 | * at what IO port and interrupt number the host side will appear |
61 | * to the host on the Host <-> BMC LPC bus. It could be different on a |
62 | * different system (though most of them use 3f8/4). |
63 | */ |
64 | |
65 | static inline u8 aspeed_vuart_readb(struct aspeed_vuart *vuart, u8 reg) |
66 | { |
67 | return readb(addr: vuart->port->port.membase + reg); |
68 | } |
69 | |
70 | static inline void aspeed_vuart_writeb(struct aspeed_vuart *vuart, u8 val, u8 reg) |
71 | { |
72 | writeb(val, addr: vuart->port->port.membase + reg); |
73 | } |
74 | |
75 | static ssize_t lpc_address_show(struct device *dev, |
76 | struct device_attribute *attr, char *buf) |
77 | { |
78 | struct aspeed_vuart *vuart = dev_get_drvdata(dev); |
79 | u16 addr; |
80 | |
81 | addr = (aspeed_vuart_readb(vuart, ASPEED_VUART_ADDRH) << 8) | |
82 | (aspeed_vuart_readb(vuart, ASPEED_VUART_ADDRL)); |
83 | |
84 | return sysfs_emit(buf, fmt: "0x%x\n" , addr); |
85 | } |
86 | |
87 | static int aspeed_vuart_set_lpc_address(struct aspeed_vuart *vuart, u32 addr) |
88 | { |
89 | if (addr > U16_MAX) |
90 | return -EINVAL; |
91 | |
92 | aspeed_vuart_writeb(vuart, val: addr >> 8, ASPEED_VUART_ADDRH); |
93 | aspeed_vuart_writeb(vuart, val: addr >> 0, ASPEED_VUART_ADDRL); |
94 | |
95 | return 0; |
96 | } |
97 | |
98 | static ssize_t lpc_address_store(struct device *dev, |
99 | struct device_attribute *attr, |
100 | const char *buf, size_t count) |
101 | { |
102 | struct aspeed_vuart *vuart = dev_get_drvdata(dev); |
103 | u32 val; |
104 | int err; |
105 | |
106 | err = kstrtou32(s: buf, base: 0, res: &val); |
107 | if (err) |
108 | return err; |
109 | |
110 | err = aspeed_vuart_set_lpc_address(vuart, addr: val); |
111 | return err ? : count; |
112 | } |
113 | |
114 | static DEVICE_ATTR_RW(lpc_address); |
115 | |
116 | static ssize_t sirq_show(struct device *dev, |
117 | struct device_attribute *attr, char *buf) |
118 | { |
119 | struct aspeed_vuart *vuart = dev_get_drvdata(dev); |
120 | u8 reg; |
121 | |
122 | reg = aspeed_vuart_readb(vuart, ASPEED_VUART_GCRB); |
123 | reg &= ASPEED_VUART_GCRB_HOST_SIRQ_MASK; |
124 | reg >>= ASPEED_VUART_GCRB_HOST_SIRQ_SHIFT; |
125 | |
126 | return sysfs_emit(buf, fmt: "%u\n" , reg); |
127 | } |
128 | |
129 | static int aspeed_vuart_set_sirq(struct aspeed_vuart *vuart, u32 sirq) |
130 | { |
131 | u8 reg; |
132 | |
133 | if (sirq > (ASPEED_VUART_GCRB_HOST_SIRQ_MASK >> ASPEED_VUART_GCRB_HOST_SIRQ_SHIFT)) |
134 | return -EINVAL; |
135 | |
136 | sirq <<= ASPEED_VUART_GCRB_HOST_SIRQ_SHIFT; |
137 | sirq &= ASPEED_VUART_GCRB_HOST_SIRQ_MASK; |
138 | |
139 | reg = aspeed_vuart_readb(vuart, ASPEED_VUART_GCRB); |
140 | reg &= ~ASPEED_VUART_GCRB_HOST_SIRQ_MASK; |
141 | reg |= sirq; |
142 | aspeed_vuart_writeb(vuart, val: reg, ASPEED_VUART_GCRB); |
143 | |
144 | return 0; |
145 | } |
146 | |
147 | static ssize_t sirq_store(struct device *dev, struct device_attribute *attr, |
148 | const char *buf, size_t count) |
149 | { |
150 | struct aspeed_vuart *vuart = dev_get_drvdata(dev); |
151 | unsigned long val; |
152 | int err; |
153 | |
154 | err = kstrtoul(s: buf, base: 0, res: &val); |
155 | if (err) |
156 | return err; |
157 | |
158 | err = aspeed_vuart_set_sirq(vuart, sirq: val); |
159 | return err ? : count; |
160 | } |
161 | |
162 | static DEVICE_ATTR_RW(sirq); |
163 | |
164 | static ssize_t sirq_polarity_show(struct device *dev, |
165 | struct device_attribute *attr, char *buf) |
166 | { |
167 | struct aspeed_vuart *vuart = dev_get_drvdata(dev); |
168 | u8 reg; |
169 | |
170 | reg = aspeed_vuart_readb(vuart, ASPEED_VUART_GCRA); |
171 | reg &= ASPEED_VUART_GCRA_HOST_SIRQ_POLARITY; |
172 | |
173 | return sysfs_emit(buf, fmt: "%u\n" , reg ? 1 : 0); |
174 | } |
175 | |
176 | static void aspeed_vuart_set_sirq_polarity(struct aspeed_vuart *vuart, |
177 | bool polarity) |
178 | { |
179 | u8 reg = aspeed_vuart_readb(vuart, ASPEED_VUART_GCRA); |
180 | |
181 | if (polarity) |
182 | reg |= ASPEED_VUART_GCRA_HOST_SIRQ_POLARITY; |
183 | else |
184 | reg &= ~ASPEED_VUART_GCRA_HOST_SIRQ_POLARITY; |
185 | |
186 | aspeed_vuart_writeb(vuart, val: reg, ASPEED_VUART_GCRA); |
187 | } |
188 | |
189 | static ssize_t sirq_polarity_store(struct device *dev, |
190 | struct device_attribute *attr, |
191 | const char *buf, size_t count) |
192 | { |
193 | struct aspeed_vuart *vuart = dev_get_drvdata(dev); |
194 | unsigned long val; |
195 | int err; |
196 | |
197 | err = kstrtoul(s: buf, base: 0, res: &val); |
198 | if (err) |
199 | return err; |
200 | |
201 | aspeed_vuart_set_sirq_polarity(vuart, polarity: val != 0); |
202 | |
203 | return count; |
204 | } |
205 | |
206 | static DEVICE_ATTR_RW(sirq_polarity); |
207 | |
208 | static struct attribute *aspeed_vuart_attrs[] = { |
209 | &dev_attr_sirq.attr, |
210 | &dev_attr_sirq_polarity.attr, |
211 | &dev_attr_lpc_address.attr, |
212 | NULL, |
213 | }; |
214 | |
215 | static const struct attribute_group aspeed_vuart_attr_group = { |
216 | .attrs = aspeed_vuart_attrs, |
217 | }; |
218 | |
219 | static void aspeed_vuart_set_enabled(struct aspeed_vuart *vuart, bool enabled) |
220 | { |
221 | u8 reg = aspeed_vuart_readb(vuart, ASPEED_VUART_GCRA); |
222 | |
223 | if (enabled) |
224 | reg |= ASPEED_VUART_GCRA_VUART_EN; |
225 | else |
226 | reg &= ~ASPEED_VUART_GCRA_VUART_EN; |
227 | |
228 | aspeed_vuart_writeb(vuart, val: reg, ASPEED_VUART_GCRA); |
229 | } |
230 | |
231 | static void aspeed_vuart_set_host_tx_discard(struct aspeed_vuart *vuart, |
232 | bool discard) |
233 | { |
234 | u8 reg; |
235 | |
236 | reg = aspeed_vuart_readb(vuart, ASPEED_VUART_GCRA); |
237 | |
238 | /* If the DISABLE_HOST_TX_DISCARD bit is set, discard is disabled */ |
239 | if (!discard) |
240 | reg |= ASPEED_VUART_GCRA_DISABLE_HOST_TX_DISCARD; |
241 | else |
242 | reg &= ~ASPEED_VUART_GCRA_DISABLE_HOST_TX_DISCARD; |
243 | |
244 | aspeed_vuart_writeb(vuart, val: reg, ASPEED_VUART_GCRA); |
245 | } |
246 | |
247 | static int aspeed_vuart_startup(struct uart_port *uart_port) |
248 | { |
249 | struct uart_8250_port *uart_8250_port = up_to_u8250p(up: uart_port); |
250 | struct aspeed_vuart *vuart = uart_8250_port->port.private_data; |
251 | int rc; |
252 | |
253 | rc = serial8250_do_startup(port: uart_port); |
254 | if (rc) |
255 | return rc; |
256 | |
257 | aspeed_vuart_set_host_tx_discard(vuart, discard: false); |
258 | |
259 | return 0; |
260 | } |
261 | |
262 | static void aspeed_vuart_shutdown(struct uart_port *uart_port) |
263 | { |
264 | struct uart_8250_port *uart_8250_port = up_to_u8250p(up: uart_port); |
265 | struct aspeed_vuart *vuart = uart_8250_port->port.private_data; |
266 | |
267 | aspeed_vuart_set_host_tx_discard(vuart, discard: true); |
268 | |
269 | serial8250_do_shutdown(port: uart_port); |
270 | } |
271 | |
272 | static void __aspeed_vuart_set_throttle(struct uart_8250_port *up, |
273 | bool throttle) |
274 | { |
275 | unsigned char irqs = UART_IER_RLSI | UART_IER_RDI; |
276 | |
277 | /* Port locked to synchronize UART_IER access against the console. */ |
278 | lockdep_assert_held_once(&up->port.lock); |
279 | |
280 | up->ier &= ~irqs; |
281 | if (!throttle) |
282 | up->ier |= irqs; |
283 | serial_out(up, UART_IER, value: up->ier); |
284 | } |
285 | static void aspeed_vuart_set_throttle(struct uart_port *port, bool throttle) |
286 | { |
287 | struct uart_8250_port *up = up_to_u8250p(up: port); |
288 | unsigned long flags; |
289 | |
290 | uart_port_lock_irqsave(up: port, flags: &flags); |
291 | __aspeed_vuart_set_throttle(up, throttle); |
292 | uart_port_unlock_irqrestore(up: port, flags); |
293 | } |
294 | |
295 | static void aspeed_vuart_throttle(struct uart_port *port) |
296 | { |
297 | aspeed_vuart_set_throttle(port, throttle: true); |
298 | } |
299 | |
300 | static void aspeed_vuart_unthrottle(struct uart_port *port) |
301 | { |
302 | aspeed_vuart_set_throttle(port, throttle: false); |
303 | } |
304 | |
305 | static void aspeed_vuart_unthrottle_exp(struct timer_list *timer) |
306 | { |
307 | struct aspeed_vuart *vuart = from_timer(vuart, timer, unthrottle_timer); |
308 | struct uart_8250_port *up = vuart->port; |
309 | |
310 | if (!tty_buffer_space_avail(port: &up->port.state->port)) { |
311 | mod_timer(timer: &vuart->unthrottle_timer, |
312 | expires: jiffies + unthrottle_timeout); |
313 | return; |
314 | } |
315 | |
316 | aspeed_vuart_unthrottle(port: &up->port); |
317 | } |
318 | |
319 | /* |
320 | * Custom interrupt handler to manage finer-grained flow control. Although we |
321 | * have throttle/unthrottle callbacks, we've seen that the VUART device can |
322 | * deliver characters faster than the ldisc has a chance to check buffer space |
323 | * against the throttle threshold. This results in dropped characters before |
324 | * the throttle. |
325 | * |
326 | * We do this by checking for flip buffer space before RX. If we have no space, |
327 | * throttle now and schedule an unthrottle for later, once the ldisc has had |
328 | * a chance to drain the buffers. |
329 | */ |
330 | static int aspeed_vuart_handle_irq(struct uart_port *port) |
331 | { |
332 | struct uart_8250_port *up = up_to_u8250p(up: port); |
333 | unsigned int iir, lsr; |
334 | unsigned long flags; |
335 | unsigned int space, count; |
336 | |
337 | iir = serial_port_in(up: port, UART_IIR); |
338 | |
339 | if (iir & UART_IIR_NO_INT) |
340 | return 0; |
341 | |
342 | uart_port_lock_irqsave(up: port, flags: &flags); |
343 | |
344 | lsr = serial_port_in(up: port, UART_LSR); |
345 | |
346 | if (lsr & (UART_LSR_DR | UART_LSR_BI)) { |
347 | space = tty_buffer_space_avail(port: &port->state->port); |
348 | |
349 | if (!space) { |
350 | /* throttle and schedule an unthrottle later */ |
351 | struct aspeed_vuart *vuart = port->private_data; |
352 | __aspeed_vuart_set_throttle(up, throttle: true); |
353 | |
354 | if (!timer_pending(timer: &vuart->unthrottle_timer)) |
355 | mod_timer(timer: &vuart->unthrottle_timer, |
356 | expires: jiffies + unthrottle_timeout); |
357 | |
358 | } else { |
359 | count = min(space, 256U); |
360 | |
361 | do { |
362 | serial8250_read_char(up, lsr); |
363 | lsr = serial_in(up, UART_LSR); |
364 | if (--count == 0) |
365 | break; |
366 | } while (lsr & (UART_LSR_DR | UART_LSR_BI)); |
367 | |
368 | tty_flip_buffer_push(port: &port->state->port); |
369 | } |
370 | } |
371 | |
372 | serial8250_modem_status(up); |
373 | if (lsr & UART_LSR_THRE) |
374 | serial8250_tx_chars(up); |
375 | |
376 | uart_unlock_and_check_sysrq_irqrestore(port, flags); |
377 | |
378 | return 1; |
379 | } |
380 | |
381 | static void aspeed_vuart_auto_configure_sirq_polarity( |
382 | struct aspeed_vuart *vuart, struct device_node *syscon_np, |
383 | u32 reg_offset, u32 reg_mask) |
384 | { |
385 | struct regmap *regmap; |
386 | u32 value; |
387 | |
388 | regmap = syscon_node_to_regmap(np: syscon_np); |
389 | if (IS_ERR(ptr: regmap)) { |
390 | dev_warn(vuart->dev, |
391 | "could not get regmap for aspeed,sirq-polarity-sense\n" ); |
392 | return; |
393 | } |
394 | if (regmap_read(map: regmap, reg: reg_offset, val: &value)) { |
395 | dev_warn(vuart->dev, "could not read hw strap table\n" ); |
396 | return; |
397 | } |
398 | |
399 | aspeed_vuart_set_sirq_polarity(vuart, polarity: (value & reg_mask) == 0); |
400 | } |
401 | |
402 | static int aspeed_vuart_map_irq_polarity(u32 dt) |
403 | { |
404 | switch (dt) { |
405 | case IRQ_TYPE_LEVEL_LOW: |
406 | return 0; |
407 | case IRQ_TYPE_LEVEL_HIGH: |
408 | return 1; |
409 | default: |
410 | return -EINVAL; |
411 | } |
412 | } |
413 | |
414 | static int aspeed_vuart_probe(struct platform_device *pdev) |
415 | { |
416 | struct of_phandle_args sirq_polarity_sense_args; |
417 | struct device *dev = &pdev->dev; |
418 | struct uart_8250_port port; |
419 | struct aspeed_vuart *vuart; |
420 | struct device_node *np; |
421 | struct resource *res; |
422 | int rc, sirq_polarity; |
423 | u32 prop, sirq[2]; |
424 | struct clk *vclk; |
425 | |
426 | np = pdev->dev.of_node; |
427 | |
428 | vuart = devm_kzalloc(dev: &pdev->dev, size: sizeof(*vuart), GFP_KERNEL); |
429 | if (!vuart) |
430 | return -ENOMEM; |
431 | |
432 | vuart->dev = &pdev->dev; |
433 | timer_setup(&vuart->unthrottle_timer, aspeed_vuart_unthrottle_exp, 0); |
434 | |
435 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
436 | if (!res) |
437 | return -EINVAL; |
438 | |
439 | memset(&port, 0, sizeof(port)); |
440 | port.port.private_data = vuart; |
441 | port.port.mapbase = res->start; |
442 | port.port.mapsize = resource_size(res); |
443 | port.port.startup = aspeed_vuart_startup; |
444 | port.port.shutdown = aspeed_vuart_shutdown; |
445 | port.port.throttle = aspeed_vuart_throttle; |
446 | port.port.unthrottle = aspeed_vuart_unthrottle; |
447 | port.port.status = UPSTAT_SYNC_FIFO; |
448 | port.port.dev = &pdev->dev; |
449 | port.port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_8250_CONSOLE); |
450 | port.port.flags = UPF_BOOT_AUTOCONF | UPF_IOREMAP | UPF_FIXED_PORT | UPF_FIXED_TYPE | |
451 | UPF_NO_THRE_TEST; |
452 | port.bugs |= UART_BUG_TXRACE; |
453 | |
454 | rc = sysfs_create_group(kobj: &vuart->dev->kobj, grp: &aspeed_vuart_attr_group); |
455 | if (rc < 0) |
456 | return rc; |
457 | |
458 | rc = uart_read_port_properties(port: &port.port); |
459 | if (rc) |
460 | goto err_sysfs_remove; |
461 | |
462 | /* Get clk rate through clk driver if present */ |
463 | if (!port.port.uartclk) { |
464 | vclk = devm_clk_get_enabled(dev, NULL); |
465 | if (IS_ERR(ptr: vclk)) { |
466 | rc = dev_err_probe(dev, err: PTR_ERR(ptr: vclk), fmt: "clk or clock-frequency not defined\n" ); |
467 | goto err_sysfs_remove; |
468 | } |
469 | |
470 | port.port.uartclk = clk_get_rate(clk: vclk); |
471 | } |
472 | |
473 | /* If current-speed was set, then try not to change it. */ |
474 | if (of_property_read_u32(np, propname: "current-speed" , out_value: &prop) == 0) |
475 | port.port.custom_divisor = port.port.uartclk / (16 * prop); |
476 | |
477 | port.port.handle_irq = aspeed_vuart_handle_irq; |
478 | port.port.type = PORT_ASPEED_VUART; |
479 | |
480 | if (port.port.fifosize) |
481 | port.capabilities = UART_CAP_FIFO; |
482 | |
483 | if (of_property_read_bool(np, propname: "auto-flow-control" )) |
484 | port.capabilities |= UART_CAP_AFE; |
485 | |
486 | rc = serial8250_register_8250_port(&port); |
487 | if (rc < 0) |
488 | goto err_sysfs_remove; |
489 | |
490 | vuart->line = rc; |
491 | vuart->port = serial8250_get_port(line: vuart->line); |
492 | |
493 | rc = of_parse_phandle_with_fixed_args( |
494 | np, list_name: "aspeed,sirq-polarity-sense" , cell_count: 2, index: 0, |
495 | out_args: &sirq_polarity_sense_args); |
496 | if (rc < 0) { |
497 | dev_dbg(&pdev->dev, |
498 | "aspeed,sirq-polarity-sense property not found\n" ); |
499 | } else { |
500 | aspeed_vuart_auto_configure_sirq_polarity( |
501 | vuart, syscon_np: sirq_polarity_sense_args.np, |
502 | reg_offset: sirq_polarity_sense_args.args[0], |
503 | BIT(sirq_polarity_sense_args.args[1])); |
504 | of_node_put(node: sirq_polarity_sense_args.np); |
505 | } |
506 | |
507 | rc = of_property_read_u32(np, propname: "aspeed,lpc-io-reg" , out_value: &prop); |
508 | if (rc < 0) |
509 | prop = ASPEED_VUART_DEFAULT_LPC_ADDR; |
510 | |
511 | rc = aspeed_vuart_set_lpc_address(vuart, addr: prop); |
512 | if (rc < 0) { |
513 | dev_err_probe(dev, err: rc, fmt: "invalid value in aspeed,lpc-io-reg property\n" ); |
514 | goto err_sysfs_remove; |
515 | } |
516 | |
517 | rc = of_property_read_u32_array(np, propname: "aspeed,lpc-interrupts" , out_values: sirq, sz: 2); |
518 | if (rc < 0) { |
519 | sirq[0] = ASPEED_VUART_DEFAULT_SIRQ; |
520 | sirq[1] = ASPEED_VUART_DEFAULT_SIRQ_POLARITY; |
521 | } |
522 | |
523 | rc = aspeed_vuart_set_sirq(vuart, sirq: sirq[0]); |
524 | if (rc < 0) { |
525 | dev_err_probe(dev, err: rc, fmt: "invalid sirq number in aspeed,lpc-interrupts property\n" ); |
526 | goto err_sysfs_remove; |
527 | } |
528 | |
529 | sirq_polarity = aspeed_vuart_map_irq_polarity(dt: sirq[1]); |
530 | if (sirq_polarity < 0) { |
531 | rc = dev_err_probe(dev, err: sirq_polarity, |
532 | fmt: "invalid sirq polarity in aspeed,lpc-interrupts property\n" ); |
533 | goto err_sysfs_remove; |
534 | } |
535 | |
536 | aspeed_vuart_set_sirq_polarity(vuart, polarity: sirq_polarity); |
537 | |
538 | aspeed_vuart_set_enabled(vuart, enabled: true); |
539 | aspeed_vuart_set_host_tx_discard(vuart, discard: true); |
540 | platform_set_drvdata(pdev, data: vuart); |
541 | |
542 | return 0; |
543 | |
544 | err_sysfs_remove: |
545 | sysfs_remove_group(kobj: &vuart->dev->kobj, grp: &aspeed_vuart_attr_group); |
546 | return rc; |
547 | } |
548 | |
549 | static void aspeed_vuart_remove(struct platform_device *pdev) |
550 | { |
551 | struct aspeed_vuart *vuart = platform_get_drvdata(pdev); |
552 | |
553 | del_timer_sync(timer: &vuart->unthrottle_timer); |
554 | aspeed_vuart_set_enabled(vuart, enabled: false); |
555 | serial8250_unregister_port(line: vuart->line); |
556 | sysfs_remove_group(kobj: &vuart->dev->kobj, grp: &aspeed_vuart_attr_group); |
557 | } |
558 | |
559 | static const struct of_device_id aspeed_vuart_table[] = { |
560 | { .compatible = "aspeed,ast2400-vuart" }, |
561 | { .compatible = "aspeed,ast2500-vuart" }, |
562 | { }, |
563 | }; |
564 | |
565 | static struct platform_driver aspeed_vuart_driver = { |
566 | .driver = { |
567 | .name = "aspeed-vuart" , |
568 | .of_match_table = aspeed_vuart_table, |
569 | }, |
570 | .probe = aspeed_vuart_probe, |
571 | .remove_new = aspeed_vuart_remove, |
572 | }; |
573 | |
574 | module_platform_driver(aspeed_vuart_driver); |
575 | |
576 | MODULE_AUTHOR("Jeremy Kerr <jk@ozlabs.org>" ); |
577 | MODULE_LICENSE("GPL" ); |
578 | MODULE_DESCRIPTION("Driver for Aspeed VUART device" ); |
579 | |