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 | { | |
92 | struct uart_port port = {}; | |
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; | |
107 | port.private_data = data; | |
108 | ||
109 | spin_lock_init(&port.lock); | |
110 | port.mapbase = regs->start; | |
111 | port.irq = irq->start; | |
112 | port.handle_irq = dw8250_handle_irq; | |
113 | port.type = PORT_8250; | |
114 | port.flags = UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF | UPF_IOREMAP | | |
115 | UPF_FIXED_PORT | UPF_FIXED_TYPE; | |
116 | port.dev = &pdev->dev; | |
117 | ||
118 | port.iotype = UPIO_MEM; | |
119 | port.serial_in = dw8250_serial_in; | |
120 | port.serial_out = dw8250_serial_out; | |
121 | if (!of_property_read_u32(np, "reg-io-width", &val)) { | |
122 | switch (val) { | |
123 | case 1: | |
124 | break; | |
125 | case 4: | |
126 | port.iotype = UPIO_MEM32; | |
127 | port.serial_in = dw8250_serial_in32; | |
128 | port.serial_out = dw8250_serial_out32; | |
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)) | |
138 | port.regshift = val; | |
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 | } | |
144 | port.uartclk = val; | |
145 | ||
146 | data->line = serial8250_register_port(&port); | |
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 | ||
164 | static const struct of_device_id dw8250_match[] = { | |
165 | { .compatible = "snps,dw-apb-uart" }, | |
166 | { /* Sentinel */ } | |
167 | }; | |
168 | MODULE_DEVICE_TABLE(of, dw8250_match); | |
169 | ||
170 | static struct platform_driver dw8250_platform_driver = { | |
171 | .driver = { | |
172 | .name = "dw-apb-uart", | |
173 | .owner = THIS_MODULE, | |
174 | .of_match_table = dw8250_match, | |
175 | }, | |
176 | .probe = dw8250_probe, | |
177 | .remove = __devexit_p(dw8250_remove), | |
178 | }; | |
179 | ||
180 | static int __init dw8250_init(void) | |
181 | { | |
182 | return platform_driver_register(&dw8250_platform_driver); | |
183 | } | |
184 | module_init(dw8250_init); | |
185 | ||
186 | static void __exit dw8250_exit(void) | |
187 | { | |
188 | platform_driver_unregister(&dw8250_platform_driver); | |
189 | } | |
190 | module_exit(dw8250_exit); | |
191 | ||
192 | MODULE_AUTHOR("Jamie Iles"); | |
193 | MODULE_LICENSE("GPL"); | |
194 | MODULE_DESCRIPTION("Synopsys DesignWare 8250 serial port driver"); |