blob: 08167aa093b252f518dca815f4fa8728a791b717 [file] [log] [blame]
Arve Hjønnevåg666b7792013-01-21 23:38:47 +00001/*
2 * Copyright (C) 2007 Google, Inc.
3 * Copyright (C) 2012 Intel, Inc.
4 *
5 * This software is licensed under the terms of the GNU General Public
6 * License version 2, as published by the Free Software Foundation, and
7 * may be copied, distributed, and modified under those terms.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 */
15
16#include <linux/console.h>
Arve Hjønnevåg666b7792013-01-21 23:38:47 +000017#include <linux/interrupt.h>
18#include <linux/platform_device.h>
19#include <linux/tty.h>
20#include <linux/tty_flip.h>
21#include <linux/slab.h>
22#include <linux/io.h>
23#include <linux/module.h>
24
25enum {
26 GOLDFISH_TTY_PUT_CHAR = 0x00,
27 GOLDFISH_TTY_BYTES_READY = 0x04,
28 GOLDFISH_TTY_CMD = 0x08,
29
30 GOLDFISH_TTY_DATA_PTR = 0x10,
31 GOLDFISH_TTY_DATA_LEN = 0x14,
Jun Tianb8658bc2014-05-12 16:55:26 +010032#ifdef CONFIG_64BIT
33 GOLDFISH_TTY_DATA_PTR_HIGH = 0x18,
34#endif
Arve Hjønnevåg666b7792013-01-21 23:38:47 +000035
36 GOLDFISH_TTY_CMD_INT_DISABLE = 0,
37 GOLDFISH_TTY_CMD_INT_ENABLE = 1,
38 GOLDFISH_TTY_CMD_WRITE_BUFFER = 2,
39 GOLDFISH_TTY_CMD_READ_BUFFER = 3,
40};
41
42struct goldfish_tty {
43 struct tty_port port;
44 spinlock_t lock;
45 void __iomem *base;
46 u32 irq;
47 int opencount;
48 struct console console;
49};
50
51static DEFINE_MUTEX(goldfish_tty_lock);
52static struct tty_driver *goldfish_tty_driver;
53static u32 goldfish_tty_line_count = 8;
54static u32 goldfish_tty_current_line_count;
55static struct goldfish_tty *goldfish_ttys;
56
57static void goldfish_tty_do_write(int line, const char *buf, unsigned count)
58{
59 unsigned long irq_flags;
60 struct goldfish_tty *qtty = &goldfish_ttys[line];
61 void __iomem *base = qtty->base;
62 spin_lock_irqsave(&qtty->lock, irq_flags);
63 writel((u32)buf, base + GOLDFISH_TTY_DATA_PTR);
Jun Tianb8658bc2014-05-12 16:55:26 +010064#ifdef CONFIG_64BIT
65 writel((u32)((u64)buf >> 32), base + GOLDFISH_TTY_DATA_PTR_HIGH);
66#endif
Arve Hjønnevåg666b7792013-01-21 23:38:47 +000067 writel(count, base + GOLDFISH_TTY_DATA_LEN);
68 writel(GOLDFISH_TTY_CMD_WRITE_BUFFER, base + GOLDFISH_TTY_CMD);
69 spin_unlock_irqrestore(&qtty->lock, irq_flags);
70}
71
72static irqreturn_t goldfish_tty_interrupt(int irq, void *dev_id)
73{
74 struct platform_device *pdev = dev_id;
75 struct goldfish_tty *qtty = &goldfish_ttys[pdev->id];
76 void __iomem *base = qtty->base;
77 unsigned long irq_flags;
78 unsigned char *buf;
79 u32 count;
Arve Hjønnevåg666b7792013-01-21 23:38:47 +000080
81 count = readl(base + GOLDFISH_TTY_BYTES_READY);
82 if(count == 0)
83 return IRQ_NONE;
84
Alan Coxebcf0982013-01-25 15:05:30 +000085 count = tty_prepare_flip_string(&qtty->port, &buf, count);
86 spin_lock_irqsave(&qtty->lock, irq_flags);
87 writel((u32)buf, base + GOLDFISH_TTY_DATA_PTR);
Jun Tianb8658bc2014-05-12 16:55:26 +010088#ifdef CONFIG_64BIT
89 writel((u32)((u64)buf >> 32), base + GOLDFISH_TTY_DATA_PTR_HIGH);
90#endif
Alan Coxebcf0982013-01-25 15:05:30 +000091 writel(count, base + GOLDFISH_TTY_DATA_LEN);
92 writel(GOLDFISH_TTY_CMD_READ_BUFFER, base + GOLDFISH_TTY_CMD);
93 spin_unlock_irqrestore(&qtty->lock, irq_flags);
94 tty_schedule_flip(&qtty->port);
Arve Hjønnevåg666b7792013-01-21 23:38:47 +000095 return IRQ_HANDLED;
96}
97
98static int goldfish_tty_activate(struct tty_port *port, struct tty_struct *tty)
99{
100 struct goldfish_tty *qtty = container_of(port, struct goldfish_tty, port);
101 writel(GOLDFISH_TTY_CMD_INT_ENABLE, qtty->base + GOLDFISH_TTY_CMD);
102 return 0;
103}
104
105static void goldfish_tty_shutdown(struct tty_port *port)
106{
107 struct goldfish_tty *qtty = container_of(port, struct goldfish_tty, port);
108 writel(GOLDFISH_TTY_CMD_INT_DISABLE, qtty->base + GOLDFISH_TTY_CMD);
109}
110
111static int goldfish_tty_open(struct tty_struct * tty, struct file * filp)
112{
113 struct goldfish_tty *qtty = &goldfish_ttys[tty->index];
114 return tty_port_open(&qtty->port, tty, filp);
115}
116
117static void goldfish_tty_close(struct tty_struct * tty, struct file * filp)
118{
119 tty_port_close(tty->port, tty, filp);
120}
121
122static void goldfish_tty_hangup(struct tty_struct *tty)
123{
124 tty_port_hangup(tty->port);
125}
126
127static int goldfish_tty_write(struct tty_struct * tty, const unsigned char *buf, int count)
128{
129 goldfish_tty_do_write(tty->index, buf, count);
130 return count;
131}
132
133static int goldfish_tty_write_room(struct tty_struct *tty)
134{
135 return 0x10000;
136}
137
138static int goldfish_tty_chars_in_buffer(struct tty_struct *tty)
139{
140 struct goldfish_tty *qtty = &goldfish_ttys[tty->index];
141 void __iomem *base = qtty->base;
142 return readl(base + GOLDFISH_TTY_BYTES_READY);
143}
144
145static void goldfish_tty_console_write(struct console *co, const char *b, unsigned count)
146{
147 goldfish_tty_do_write(co->index, b, count);
148}
149
150static struct tty_driver *goldfish_tty_console_device(struct console *c, int *index)
151{
152 *index = c->index;
153 return goldfish_tty_driver;
154}
155
156static int goldfish_tty_console_setup(struct console *co, char *options)
157{
158 if((unsigned)co->index > goldfish_tty_line_count)
159 return -ENODEV;
160 if(goldfish_ttys[co->index].base == 0)
161 return -ENODEV;
162 return 0;
163}
164
165static struct tty_port_operations goldfish_port_ops = {
166 .activate = goldfish_tty_activate,
167 .shutdown = goldfish_tty_shutdown
168};
169
170static struct tty_operations goldfish_tty_ops = {
171 .open = goldfish_tty_open,
172 .close = goldfish_tty_close,
173 .hangup = goldfish_tty_hangup,
174 .write = goldfish_tty_write,
175 .write_room = goldfish_tty_write_room,
176 .chars_in_buffer = goldfish_tty_chars_in_buffer,
177};
178
179static int goldfish_tty_create_driver(void)
180{
181 int ret;
182 struct tty_driver *tty;
183
184 goldfish_ttys = kzalloc(sizeof(*goldfish_ttys) * goldfish_tty_line_count, GFP_KERNEL);
185 if(goldfish_ttys == NULL) {
186 ret = -ENOMEM;
187 goto err_alloc_goldfish_ttys_failed;
188 }
189 tty = alloc_tty_driver(goldfish_tty_line_count);
190 if(tty == NULL) {
191 ret = -ENOMEM;
192 goto err_alloc_tty_driver_failed;
193 }
194 tty->driver_name = "goldfish";
195 tty->name = "ttyGF";
196 tty->type = TTY_DRIVER_TYPE_SERIAL;
197 tty->subtype = SERIAL_TYPE_NORMAL;
198 tty->init_termios = tty_std_termios;
199 tty->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
200 tty_set_operations(tty, &goldfish_tty_ops);
201 ret = tty_register_driver(tty);
202 if(ret)
203 goto err_tty_register_driver_failed;
204
205 goldfish_tty_driver = tty;
206 return 0;
207
208err_tty_register_driver_failed:
209 put_tty_driver(tty);
210err_alloc_tty_driver_failed:
211 kfree(goldfish_ttys);
212 goldfish_ttys = NULL;
213err_alloc_goldfish_ttys_failed:
214 return ret;
215}
216
217static void goldfish_tty_delete_driver(void)
218{
219 tty_unregister_driver(goldfish_tty_driver);
220 put_tty_driver(goldfish_tty_driver);
221 goldfish_tty_driver = NULL;
222 kfree(goldfish_ttys);
223 goldfish_ttys = NULL;
224}
225
226static int goldfish_tty_probe(struct platform_device *pdev)
227{
228 struct goldfish_tty *qtty;
229 int ret = -EINVAL;
230 int i;
231 struct resource *r;
232 struct device *ttydev;
233 void __iomem *base;
234 u32 irq;
235
236 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
237 if(r == NULL)
238 return -EINVAL;
239
240 base = ioremap(r->start, 0x1000);
241 if (base == NULL)
242 pr_err("goldfish_tty: unable to remap base\n");
243
244 r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
245 if(r == NULL)
246 goto err_unmap;
247
248 irq = r->start;
249
250 if(pdev->id >= goldfish_tty_line_count)
251 goto err_unmap;
252
253 mutex_lock(&goldfish_tty_lock);
254 if(goldfish_tty_current_line_count == 0) {
255 ret = goldfish_tty_create_driver();
256 if(ret)
257 goto err_create_driver_failed;
258 }
259 goldfish_tty_current_line_count++;
260
261 qtty = &goldfish_ttys[pdev->id];
262 spin_lock_init(&qtty->lock);
263 tty_port_init(&qtty->port);
264 qtty->port.ops = &goldfish_port_ops;
265 qtty->base = base;
266 qtty->irq = irq;
267
268 writel(GOLDFISH_TTY_CMD_INT_DISABLE, base + GOLDFISH_TTY_CMD);
269
270 ret = request_irq(irq, goldfish_tty_interrupt, IRQF_SHARED, "goldfish_tty", pdev);
271 if(ret)
272 goto err_request_irq_failed;
273
274
275 ttydev = tty_port_register_device(&qtty->port, goldfish_tty_driver,
276 pdev->id, &pdev->dev);
277 if(IS_ERR(ttydev)) {
278 ret = PTR_ERR(ttydev);
279 goto err_tty_register_device_failed;
280 }
281
282 strcpy(qtty->console.name, "ttyGF");
283 qtty->console.write = goldfish_tty_console_write;
284 qtty->console.device = goldfish_tty_console_device;
285 qtty->console.setup = goldfish_tty_console_setup;
286 qtty->console.flags = CON_PRINTBUFFER;
287 qtty->console.index = pdev->id;
288 register_console(&qtty->console);
289
290 mutex_unlock(&goldfish_tty_lock);
291 return 0;
292
293 tty_unregister_device(goldfish_tty_driver, i);
294err_tty_register_device_failed:
295 free_irq(irq, pdev);
296err_request_irq_failed:
297 goldfish_tty_current_line_count--;
298 if(goldfish_tty_current_line_count == 0)
299 goldfish_tty_delete_driver();
300err_create_driver_failed:
301 mutex_unlock(&goldfish_tty_lock);
302err_unmap:
303 iounmap(base);
304 return ret;
305}
306
307static int goldfish_tty_remove(struct platform_device *pdev)
308{
309 struct goldfish_tty *qtty;
310
311 mutex_lock(&goldfish_tty_lock);
312
313 qtty = &goldfish_ttys[pdev->id];
314 unregister_console(&qtty->console);
315 tty_unregister_device(goldfish_tty_driver, pdev->id);
316 iounmap(qtty->base);
317 qtty->base = 0;
318 free_irq(qtty->irq, pdev);
319 goldfish_tty_current_line_count--;
320 if(goldfish_tty_current_line_count == 0)
321 goldfish_tty_delete_driver();
322 mutex_unlock(&goldfish_tty_lock);
323 return 0;
324}
325
326static struct platform_driver goldfish_tty_platform_driver = {
327 .probe = goldfish_tty_probe,
328 .remove = goldfish_tty_remove,
329 .driver = {
330 .name = "goldfish_tty"
331 }
332};
333
334module_platform_driver(goldfish_tty_platform_driver);
335
336MODULE_LICENSE("GPL v2");