diff options
Diffstat (limited to 'drivers/tty/serial/8250/8250_bcm2835aux.c')
-rw-r--r-- | drivers/tty/serial/8250/8250_bcm2835aux.c | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/drivers/tty/serial/8250/8250_bcm2835aux.c b/drivers/tty/serial/8250/8250_bcm2835aux.c new file mode 100644 index 000000000000..e10f1244409b --- /dev/null +++ b/drivers/tty/serial/8250/8250_bcm2835aux.c | |||
@@ -0,0 +1,146 @@ | |||
1 | /* | ||
2 | * Serial port driver for BCM2835AUX UART | ||
3 | * | ||
4 | * Copyright (C) 2016 Martin Sperl <kernel@martin.sperl.org> | ||
5 | * | ||
6 | * Based on 8250_lpc18xx.c: | ||
7 | * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com> | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | * | ||
13 | */ | ||
14 | |||
15 | #include <linux/clk.h> | ||
16 | #include <linux/io.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/of.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | |||
21 | #include "8250.h" | ||
22 | |||
23 | struct bcm2835aux_data { | ||
24 | struct uart_8250_port uart; | ||
25 | struct clk *clk; | ||
26 | int line; | ||
27 | }; | ||
28 | |||
29 | static int bcm2835aux_serial_probe(struct platform_device *pdev) | ||
30 | { | ||
31 | struct bcm2835aux_data *data; | ||
32 | struct resource *res; | ||
33 | int ret; | ||
34 | |||
35 | /* allocate the custom structure */ | ||
36 | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); | ||
37 | if (!data) | ||
38 | return -ENOMEM; | ||
39 | |||
40 | /* initialize data */ | ||
41 | spin_lock_init(&data->uart.port.lock); | ||
42 | data->uart.capabilities = UART_CAP_FIFO; | ||
43 | data->uart.port.dev = &pdev->dev; | ||
44 | data->uart.port.regshift = 2; | ||
45 | data->uart.port.type = PORT_16550; | ||
46 | data->uart.port.iotype = UPIO_MEM; | ||
47 | data->uart.port.fifosize = 8; | ||
48 | data->uart.port.flags = UPF_SHARE_IRQ | | ||
49 | UPF_FIXED_PORT | | ||
50 | UPF_FIXED_TYPE | | ||
51 | UPF_SKIP_TEST; | ||
52 | |||
53 | /* get the clock - this also enables the HW */ | ||
54 | data->clk = devm_clk_get(&pdev->dev, NULL); | ||
55 | ret = PTR_ERR_OR_ZERO(data->clk); | ||
56 | if (ret) { | ||
57 | dev_err(&pdev->dev, "could not get clk: %d\n", ret); | ||
58 | return ret; | ||
59 | } | ||
60 | |||
61 | /* get the interrupt */ | ||
62 | ret = platform_get_irq(pdev, 0); | ||
63 | if (ret < 0) { | ||
64 | dev_err(&pdev->dev, "irq not found - %i", ret); | ||
65 | return ret; | ||
66 | } | ||
67 | data->uart.port.irq = ret; | ||
68 | |||
69 | /* map the main registers */ | ||
70 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
71 | if (!res) { | ||
72 | dev_err(&pdev->dev, "memory resource not found"); | ||
73 | return -EINVAL; | ||
74 | } | ||
75 | data->uart.port.membase = devm_ioremap_resource(&pdev->dev, res); | ||
76 | ret = PTR_ERR_OR_ZERO(data->uart.port.membase); | ||
77 | if (ret) | ||
78 | return ret; | ||
79 | |||
80 | /* Check for a fixed line number */ | ||
81 | ret = of_alias_get_id(pdev->dev.of_node, "serial"); | ||
82 | if (ret >= 0) | ||
83 | data->uart.port.line = ret; | ||
84 | |||
85 | /* enable the clock as a last step */ | ||
86 | ret = clk_prepare_enable(data->clk); | ||
87 | if (ret) { | ||
88 | dev_err(&pdev->dev, "unable to enable uart clock - %d\n", | ||
89 | ret); | ||
90 | return ret; | ||
91 | } | ||
92 | |||
93 | /* the HW-clock divider for bcm2835aux is 8, | ||
94 | * but 8250 expects a divider of 16, | ||
95 | * so we have to multiply the actual clock by 2 | ||
96 | * to get identical baudrates. | ||
97 | */ | ||
98 | data->uart.port.uartclk = clk_get_rate(data->clk) * 2; | ||
99 | |||
100 | /* register the port */ | ||
101 | ret = serial8250_register_8250_port(&data->uart); | ||
102 | if (ret < 0) { | ||
103 | dev_err(&pdev->dev, "unable to register 8250 port - %d\n", | ||
104 | ret); | ||
105 | goto dis_clk; | ||
106 | } | ||
107 | data->line = ret; | ||
108 | |||
109 | platform_set_drvdata(pdev, data); | ||
110 | |||
111 | return 0; | ||
112 | |||
113 | dis_clk: | ||
114 | clk_disable_unprepare(data->clk); | ||
115 | return ret; | ||
116 | } | ||
117 | |||
118 | static int bcm2835aux_serial_remove(struct platform_device *pdev) | ||
119 | { | ||
120 | struct bcm2835aux_data *data = platform_get_drvdata(pdev); | ||
121 | |||
122 | serial8250_unregister_port(data->uart.port.line); | ||
123 | clk_disable_unprepare(data->clk); | ||
124 | |||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | static const struct of_device_id bcm2835aux_serial_match[] = { | ||
129 | { .compatible = "brcm,bcm2835-aux-uart" }, | ||
130 | { }, | ||
131 | }; | ||
132 | MODULE_DEVICE_TABLE(of, bcm2835aux_serial_match); | ||
133 | |||
134 | static struct platform_driver bcm2835aux_serial_driver = { | ||
135 | .driver = { | ||
136 | .name = "bcm2835-aux-uart", | ||
137 | .of_match_table = bcm2835aux_serial_match, | ||
138 | }, | ||
139 | .probe = bcm2835aux_serial_probe, | ||
140 | .remove = bcm2835aux_serial_remove, | ||
141 | }; | ||
142 | module_platform_driver(bcm2835aux_serial_driver); | ||
143 | |||
144 | MODULE_DESCRIPTION("BCM2835 auxiliar UART driver"); | ||
145 | MODULE_AUTHOR("Martin Sperl <kernel@martin.sperl.org>"); | ||
146 | MODULE_LICENSE("GPL v2"); | ||