Commit | Line | Data |
---|---|---|
7d4008eb JI |
1 | /* |
2 | * Synopsys DesignWare 8250 driver. | |
3 | * | |
4 | * Copyright 2011 Picochip, Jamie Iles. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * The Synopsys DesignWare 8250 has an extra feature whereby it detects if the | |
12 | * LCR is written whilst busy. If it is, then a busy detect interrupt is | |
13 | * raised, the LCR needs to be rewritten and the uart status register read. | |
14 | */ | |
15 | #include <linux/device.h> | |
16 | #include <linux/init.h> | |
17 | #include <linux/io.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/serial_8250.h> | |
20 | #include <linux/serial_core.h> | |
21 | #include <linux/serial_reg.h> | |
22 | #include <linux/of.h> | |
23 | #include <linux/of_irq.h> | |
24 | #include <linux/of_platform.h> | |
25 | #include <linux/platform_device.h> | |
26 | #include <linux/slab.h> | |
27 | ||
28 | struct dw8250_data { | |
29 | int last_lcr; | |
30 | int line; | |
31 | }; | |
32 | ||
33 | static void dw8250_serial_out(struct uart_port *p, int offset, int value) | |
34 | { | |
35 | struct dw8250_data *d = p->private_data; | |
36 | ||
37 | if (offset == UART_LCR) | |
38 | d->last_lcr = value; | |
39 | ||
40 | offset <<= p->regshift; | |
41 | writeb(value, p->membase + offset); | |
42 | } | |
43 | ||
44 | static unsigned int dw8250_serial_in(struct uart_port *p, int offset) | |
45 | { | |
46 | offset <<= p->regshift; | |
47 | ||
48 | return readb(p->membase + offset); | |
49 | } | |
50 | ||
51 | static void dw8250_serial_out32(struct uart_port *p, int offset, int value) | |
52 | { | |
53 | struct dw8250_data *d = p->private_data; | |
54 | ||
55 | if (offset == UART_LCR) | |
56 | d->last_lcr = value; | |
57 | ||
58 | offset <<= p->regshift; | |
59 | writel(value, p->membase + offset); | |
60 | } | |
61 | ||
62 | static unsigned int dw8250_serial_in32(struct uart_port *p, int offset) | |
63 | { | |
64 | offset <<= p->regshift; | |
65 | ||
66 | return readl(p->membase + offset); | |
67 | } | |
68 | ||
69 | /* Offset for the DesignWare's UART Status Register. */ | |
70 | #define UART_USR 0x1f | |
71 | ||
72 | static int dw8250_handle_irq(struct uart_port *p) | |
73 | { | |
74 | struct dw8250_data *d = p->private_data; | |
75 | unsigned int iir = p->serial_in(p, UART_IIR); | |
76 | ||
77 | if (serial8250_handle_irq(p, iir)) { | |
78 | return 1; | |
79 | } else if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY) { | |
80 | /* Clear the USR and write the LCR again. */ | |
81 | (void)p->serial_in(p, UART_USR); | |
82 | p->serial_out(p, d->last_lcr, UART_LCR); | |
83 | ||
84 | return 1; | |
85 | } | |
86 | ||
87 | return 0; | |
88 | } | |
89 | ||
a7260c8c HK |
90 | static int dw8250_probe_of(struct uart_port *p) |
91 | { | |
92 | struct device_node *np = p->dev->of_node; | |
93 | u32 val; | |
94 | ||
95 | if (!of_property_read_u32(np, "reg-io-width", &val)) { | |
96 | switch (val) { | |
97 | case 1: | |
98 | break; | |
99 | case 4: | |
100 | p->iotype = UPIO_MEM32; | |
101 | p->serial_in = dw8250_serial_in32; | |
102 | p->serial_out = dw8250_serial_out32; | |
103 | break; | |
104 | default: | |
105 | dev_err(p->dev, "unsupported reg-io-width (%u)\n", val); | |
106 | return -EINVAL; | |
107 | } | |
108 | } | |
109 | ||
110 | if (!of_property_read_u32(np, "reg-shift", &val)) | |
111 | p->regshift = val; | |
112 | ||
113 | if (of_property_read_u32(np, "clock-frequency", &val)) { | |
114 | dev_err(p->dev, "no clock-frequency property set\n"); | |
115 | return -EINVAL; | |
116 | } | |
117 | p->uartclk = val; | |
118 | ||
119 | return 0; | |
120 | } | |
121 | ||
9671f099 | 122 | static int dw8250_probe(struct platform_device *pdev) |
7d4008eb | 123 | { |
2655a2c7 | 124 | struct uart_8250_port uart = {}; |
7d4008eb JI |
125 | struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
126 | struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
7d4008eb | 127 | struct dw8250_data *data; |
a7260c8c | 128 | int err; |
7d4008eb JI |
129 | |
130 | if (!regs || !irq) { | |
131 | dev_err(&pdev->dev, "no registers/irq defined\n"); | |
132 | return -EINVAL; | |
133 | } | |
134 | ||
2655a2c7 AC |
135 | spin_lock_init(&uart.port.lock); |
136 | uart.port.mapbase = regs->start; | |
137 | uart.port.irq = irq->start; | |
138 | uart.port.handle_irq = dw8250_handle_irq; | |
139 | uart.port.type = PORT_8250; | |
f93366ff | 140 | uart.port.flags = UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF | UPF_FIXED_PORT; |
2655a2c7 | 141 | uart.port.dev = &pdev->dev; |
7d4008eb | 142 | |
f93366ff HK |
143 | uart.port.membase = ioremap(regs->start, resource_size(regs)); |
144 | if (!uart.port.membase) | |
145 | return -ENOMEM; | |
146 | ||
2655a2c7 AC |
147 | uart.port.iotype = UPIO_MEM; |
148 | uart.port.serial_in = dw8250_serial_in; | |
149 | uart.port.serial_out = dw8250_serial_out; | |
a7260c8c HK |
150 | |
151 | if (pdev->dev.of_node) { | |
152 | err = dw8250_probe_of(&uart.port); | |
153 | if (err) | |
154 | return err; | |
155 | } else { | |
156 | return -ENODEV; | |
7d4008eb JI |
157 | } |
158 | ||
a7260c8c HK |
159 | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); |
160 | if (!data) | |
161 | return -ENOMEM; | |
7d4008eb | 162 | |
a7260c8c | 163 | uart.port.private_data = data; |
7d4008eb | 164 | |
2655a2c7 | 165 | data->line = serial8250_register_8250_port(&uart); |
7d4008eb JI |
166 | if (data->line < 0) |
167 | return data->line; | |
168 | ||
169 | platform_set_drvdata(pdev, data); | |
170 | ||
171 | return 0; | |
172 | } | |
173 | ||
ae8d8a14 | 174 | static int dw8250_remove(struct platform_device *pdev) |
7d4008eb JI |
175 | { |
176 | struct dw8250_data *data = platform_get_drvdata(pdev); | |
177 | ||
178 | serial8250_unregister_port(data->line); | |
179 | ||
180 | return 0; | |
181 | } | |
182 | ||
b61c5ed5 JH |
183 | #ifdef CONFIG_PM |
184 | static int dw8250_suspend(struct platform_device *pdev, pm_message_t state) | |
185 | { | |
186 | struct dw8250_data *data = platform_get_drvdata(pdev); | |
187 | ||
188 | serial8250_suspend_port(data->line); | |
189 | ||
190 | return 0; | |
191 | } | |
192 | ||
193 | static int dw8250_resume(struct platform_device *pdev) | |
194 | { | |
195 | struct dw8250_data *data = platform_get_drvdata(pdev); | |
196 | ||
197 | serial8250_resume_port(data->line); | |
198 | ||
199 | return 0; | |
200 | } | |
201 | #else | |
202 | #define dw8250_suspend NULL | |
203 | #define dw8250_resume NULL | |
204 | #endif /* CONFIG_PM */ | |
205 | ||
a7260c8c | 206 | static const struct of_device_id dw8250_of_match[] = { |
7d4008eb JI |
207 | { .compatible = "snps,dw-apb-uart" }, |
208 | { /* Sentinel */ } | |
209 | }; | |
a7260c8c | 210 | MODULE_DEVICE_TABLE(of, dw8250_of_match); |
7d4008eb JI |
211 | |
212 | static struct platform_driver dw8250_platform_driver = { | |
213 | .driver = { | |
214 | .name = "dw-apb-uart", | |
215 | .owner = THIS_MODULE, | |
a7260c8c | 216 | .of_match_table = dw8250_of_match, |
7d4008eb JI |
217 | }, |
218 | .probe = dw8250_probe, | |
2d47b716 | 219 | .remove = dw8250_remove, |
b61c5ed5 JH |
220 | .suspend = dw8250_suspend, |
221 | .resume = dw8250_resume, | |
7d4008eb JI |
222 | }; |
223 | ||
c8381c15 | 224 | module_platform_driver(dw8250_platform_driver); |
7d4008eb JI |
225 | |
226 | MODULE_AUTHOR("Jamie Iles"); | |
227 | MODULE_LICENSE("GPL"); | |
228 | MODULE_DESCRIPTION("Synopsys DesignWare 8250 serial port driver"); |