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 | ||
90 | static int __devinit dw8250_probe(struct platform_device *pdev) | |
91 | { | |
2655a2c7 | 92 | struct uart_8250_port uart = {}; |
7d4008eb JI |
93 | struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
94 | struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
95 | struct device_node *np = pdev->dev.of_node; | |
96 | u32 val; | |
97 | struct dw8250_data *data; | |
98 | ||
99 | if (!regs || !irq) { | |
100 | dev_err(&pdev->dev, "no registers/irq defined\n"); | |
101 | return -EINVAL; | |
102 | } | |
103 | ||
104 | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); | |
105 | if (!data) | |
106 | return -ENOMEM; | |
2655a2c7 AC |
107 | uart.port.private_data = data; |
108 | ||
109 | spin_lock_init(&uart.port.lock); | |
110 | uart.port.mapbase = regs->start; | |
111 | uart.port.irq = irq->start; | |
112 | uart.port.handle_irq = dw8250_handle_irq; | |
113 | uart.port.type = PORT_8250; | |
114 | uart.port.flags = UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF | UPF_IOREMAP | | |
7d4008eb | 115 | UPF_FIXED_PORT | UPF_FIXED_TYPE; |
2655a2c7 | 116 | uart.port.dev = &pdev->dev; |
7d4008eb | 117 | |
2655a2c7 AC |
118 | uart.port.iotype = UPIO_MEM; |
119 | uart.port.serial_in = dw8250_serial_in; | |
120 | uart.port.serial_out = dw8250_serial_out; | |
7d4008eb JI |
121 | if (!of_property_read_u32(np, "reg-io-width", &val)) { |
122 | switch (val) { | |
123 | case 1: | |
124 | break; | |
125 | case 4: | |
2655a2c7 AC |
126 | uart.port.iotype = UPIO_MEM32; |
127 | uart.port.serial_in = dw8250_serial_in32; | |
128 | uart.port.serial_out = dw8250_serial_out32; | |
7d4008eb JI |
129 | break; |
130 | default: | |
131 | dev_err(&pdev->dev, "unsupported reg-io-width (%u)\n", | |
132 | val); | |
133 | return -EINVAL; | |
134 | } | |
135 | } | |
136 | ||
137 | if (!of_property_read_u32(np, "reg-shift", &val)) | |
2655a2c7 | 138 | uart.port.regshift = val; |
7d4008eb JI |
139 | |
140 | if (of_property_read_u32(np, "clock-frequency", &val)) { | |
141 | dev_err(&pdev->dev, "no clock-frequency property set\n"); | |
142 | return -EINVAL; | |
143 | } | |
ce7240e4 | 144 | uart.port.uartclk = val; |
7d4008eb | 145 | |
2655a2c7 | 146 | data->line = serial8250_register_8250_port(&uart); |
7d4008eb JI |
147 | if (data->line < 0) |
148 | return data->line; | |
149 | ||
150 | platform_set_drvdata(pdev, data); | |
151 | ||
152 | return 0; | |
153 | } | |
154 | ||
155 | static int __devexit dw8250_remove(struct platform_device *pdev) | |
156 | { | |
157 | struct dw8250_data *data = platform_get_drvdata(pdev); | |
158 | ||
159 | serial8250_unregister_port(data->line); | |
160 | ||
161 | return 0; | |
162 | } | |
163 | ||
b61c5ed5 JH |
164 | #ifdef CONFIG_PM |
165 | static int dw8250_suspend(struct platform_device *pdev, pm_message_t state) | |
166 | { | |
167 | struct dw8250_data *data = platform_get_drvdata(pdev); | |
168 | ||
169 | serial8250_suspend_port(data->line); | |
170 | ||
171 | return 0; | |
172 | } | |
173 | ||
174 | static int dw8250_resume(struct platform_device *pdev) | |
175 | { | |
176 | struct dw8250_data *data = platform_get_drvdata(pdev); | |
177 | ||
178 | serial8250_resume_port(data->line); | |
179 | ||
180 | return 0; | |
181 | } | |
182 | #else | |
183 | #define dw8250_suspend NULL | |
184 | #define dw8250_resume NULL | |
185 | #endif /* CONFIG_PM */ | |
186 | ||
7d4008eb JI |
187 | static const struct of_device_id dw8250_match[] = { |
188 | { .compatible = "snps,dw-apb-uart" }, | |
189 | { /* Sentinel */ } | |
190 | }; | |
191 | MODULE_DEVICE_TABLE(of, dw8250_match); | |
192 | ||
193 | static struct platform_driver dw8250_platform_driver = { | |
194 | .driver = { | |
195 | .name = "dw-apb-uart", | |
196 | .owner = THIS_MODULE, | |
197 | .of_match_table = dw8250_match, | |
198 | }, | |
199 | .probe = dw8250_probe, | |
2d47b716 | 200 | .remove = dw8250_remove, |
b61c5ed5 JH |
201 | .suspend = dw8250_suspend, |
202 | .resume = dw8250_resume, | |
7d4008eb JI |
203 | }; |
204 | ||
c8381c15 | 205 | module_platform_driver(dw8250_platform_driver); |
7d4008eb JI |
206 | |
207 | MODULE_AUTHOR("Jamie Iles"); | |
208 | MODULE_LICENSE("GPL"); | |
209 | MODULE_DESCRIPTION("Synopsys DesignWare 8250 serial port driver"); |