1 /* drivers/char/goldfish_tty.c
2 **
3 ** Copyright (C) 2007 Google, 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>
17 #include <linux/init.h>
18 #include <linux/interrupt.h>
19 #include <linux/platform_device.h>
20 #include <linux/tty.h>
21 #include <linux/tty_flip.h>
22
23 #ifdef CONFIG_ARM
24 #include <mach/hardware.h>
25 #endif
26
27 #include <asm/io.h>
28
29 enum {
30 GOLDFISH_TTY_PUT_CHAR = 0x00,
31 GOLDFISH_TTY_BYTES_READY = 0x04,
32 GOLDFISH_TTY_CMD = 0x08,
33
34 GOLDFISH_TTY_DATA_PTR = 0x10,
35 GOLDFISH_TTY_DATA_LEN = 0x14,
36
37 GOLDFISH_TTY_CMD_INT_DISABLE = 0,
38 GOLDFISH_TTY_CMD_INT_ENABLE = 1,
39 GOLDFISH_TTY_CMD_WRITE_BUFFER = 2,
40 GOLDFISH_TTY_CMD_READ_BUFFER = 3,
41 };
42
43 struct goldfish_tty {
44 spinlock_t lock;
45 void __iomem *base;
46 uint32_t irq;
47 int opencount;
48 struct tty_struct *tty;
49 struct console console;
50 };
51
52 static DEFINE_MUTEX(goldfish_tty_lock);
53 static struct tty_driver *goldfish_tty_driver;
54 static uint32_t goldfish_tty_line_count = 8;
55 static uint32_t goldfish_tty_current_line_count;
56 static struct goldfish_tty *goldfish_ttys;
57
goldfish_tty_do_write(int line,const char * buf,unsigned count)58 static void goldfish_tty_do_write(int line, const char *buf, unsigned count)
59 {
60 unsigned long irq_flags;
61 struct goldfish_tty *qtty = &goldfish_ttys[line];
62 void __iomem *base = qtty->base;
63 spin_lock_irqsave(&qtty->lock, irq_flags);
64 writel((uint32_t)buf, base + GOLDFISH_TTY_DATA_PTR);
65 writel(count, base + GOLDFISH_TTY_DATA_LEN);
66 writel(GOLDFISH_TTY_CMD_WRITE_BUFFER, base + GOLDFISH_TTY_CMD);
67 spin_unlock_irqrestore(&qtty->lock, irq_flags);
68 }
69
goldfish_tty_interrupt(int irq,void * dev_id)70 static irqreturn_t goldfish_tty_interrupt(int irq, void *dev_id)
71 {
72 struct platform_device *pdev = dev_id;
73 struct goldfish_tty *qtty = &goldfish_ttys[pdev->id];
74 void __iomem *base = qtty->base;
75 unsigned long irq_flags;
76 unsigned char *buf;
77 uint32_t count;
78
79 count = readl(base + GOLDFISH_TTY_BYTES_READY);
80 if(count == 0) {
81 return IRQ_NONE;
82 }
83 count = tty_prepare_flip_string(qtty->tty, &buf, count);
84 spin_lock_irqsave(&qtty->lock, irq_flags);
85 writel((uint32_t)buf, base + GOLDFISH_TTY_DATA_PTR);
86 writel(count, base + GOLDFISH_TTY_DATA_LEN);
87 writel(GOLDFISH_TTY_CMD_READ_BUFFER, base + GOLDFISH_TTY_CMD);
88 spin_unlock_irqrestore(&qtty->lock, irq_flags);
89 tty_schedule_flip(qtty->tty);
90 return IRQ_HANDLED;
91 }
92
goldfish_tty_open(struct tty_struct * tty,struct file * filp)93 static int goldfish_tty_open(struct tty_struct * tty, struct file * filp)
94 {
95 int ret;
96 struct goldfish_tty *qtty = &goldfish_ttys[tty->index];
97
98 mutex_lock(&goldfish_tty_lock);
99 if(qtty->tty == NULL || qtty->tty == tty) {
100 if(qtty->opencount++ == 0) {
101 qtty->tty = tty;
102 writel(GOLDFISH_TTY_CMD_INT_ENABLE, qtty->base + GOLDFISH_TTY_CMD);
103 }
104 ret = 0;
105 }
106 else
107 ret = -EBUSY;
108 mutex_unlock(&goldfish_tty_lock);
109 return ret;
110 }
111
goldfish_tty_close(struct tty_struct * tty,struct file * filp)112 static void goldfish_tty_close(struct tty_struct * tty, struct file * filp)
113 {
114 struct goldfish_tty *qtty = &goldfish_ttys[tty->index];
115
116 mutex_lock(&goldfish_tty_lock);
117 if(qtty->tty == tty) {
118 if(--qtty->opencount == 0) {
119 writel(GOLDFISH_TTY_CMD_INT_DISABLE, qtty->base + GOLDFISH_TTY_CMD);
120 qtty->tty = NULL;
121 }
122 }
123 mutex_unlock(&goldfish_tty_lock);
124 }
125
goldfish_tty_write(struct tty_struct * tty,const unsigned char * buf,int count)126 static int goldfish_tty_write(struct tty_struct * tty, const unsigned char *buf, int count)
127 {
128 goldfish_tty_do_write(tty->index, buf, count);
129 return count;
130 }
131
goldfish_tty_write_room(struct tty_struct * tty)132 static int goldfish_tty_write_room(struct tty_struct *tty)
133 {
134 return 0x10000;
135 }
136
goldfish_tty_chars_in_buffer(struct tty_struct * tty)137 static int goldfish_tty_chars_in_buffer(struct tty_struct *tty)
138 {
139 struct goldfish_tty *qtty = &goldfish_ttys[tty->index];
140 void __iomem *base = qtty->base;
141 return readl(base + GOLDFISH_TTY_BYTES_READY);
142 }
143
goldfish_tty_console_write(struct console * co,const char * b,unsigned count)144 static void goldfish_tty_console_write(struct console *co, const char *b, unsigned count)
145 {
146 goldfish_tty_do_write(co->index, b, count);
147 }
148
goldfish_tty_console_device(struct console * c,int * index)149 static struct tty_driver *goldfish_tty_console_device(struct console *c, int *index)
150 {
151 *index = c->index;
152 return goldfish_tty_driver;
153 }
154
goldfish_tty_console_setup(struct console * co,char * options)155 static int goldfish_tty_console_setup(struct console *co, char *options)
156 {
157 if((unsigned)co->index > goldfish_tty_line_count)
158 return -ENODEV;
159 if(goldfish_ttys[co->index].base == 0)
160 return -ENODEV;
161 return 0;
162 }
163
164 static struct tty_operations goldfish_tty_ops = {
165 .open = goldfish_tty_open,
166 .close = goldfish_tty_close,
167 .write = goldfish_tty_write,
168 .write_room = goldfish_tty_write_room,
169 .chars_in_buffer = goldfish_tty_chars_in_buffer,
170 };
171
goldfish_tty_create_driver(void)172 static int __devinit goldfish_tty_create_driver(void)
173 {
174 int ret;
175 struct tty_driver *tty;
176
177 goldfish_ttys = kzalloc(sizeof(*goldfish_ttys) * goldfish_tty_line_count, GFP_KERNEL);
178 if(goldfish_ttys == NULL) {
179 ret = -ENOMEM;
180 goto err_alloc_goldfish_ttys_failed;
181 }
182
183 tty = alloc_tty_driver(goldfish_tty_line_count);
184 if(tty == NULL) {
185 ret = -ENOMEM;
186 goto err_alloc_tty_driver_failed;
187 }
188 tty->driver_name = "goldfish";
189 tty->name = "ttyS";
190 tty->type = TTY_DRIVER_TYPE_SERIAL;
191 tty->subtype = SERIAL_TYPE_NORMAL;
192 tty->init_termios = tty_std_termios;
193 tty->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
194 tty_set_operations(tty, &goldfish_tty_ops);
195 ret = tty_register_driver(tty);
196 if(ret)
197 goto err_tty_register_driver_failed;
198
199 goldfish_tty_driver = tty;
200 return 0;
201
202 err_tty_register_driver_failed:
203 put_tty_driver(tty);
204 err_alloc_tty_driver_failed:
205 kfree(goldfish_ttys);
206 goldfish_ttys = NULL;
207 err_alloc_goldfish_ttys_failed:
208 return ret;
209 }
210
goldfish_tty_delete_driver(void)211 static void goldfish_tty_delete_driver(void)
212 {
213 tty_unregister_driver(goldfish_tty_driver);
214 put_tty_driver(goldfish_tty_driver);
215 goldfish_tty_driver = NULL;
216 kfree(goldfish_ttys);
217 goldfish_ttys = NULL;
218 }
219
goldfish_tty_probe(struct platform_device * pdev)220 static int __devinit goldfish_tty_probe(struct platform_device *pdev)
221 {
222 int ret;
223 int i;
224 struct resource *r;
225 struct device *ttydev;
226 void __iomem *base;
227 uint32_t irq;
228
229 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
230 if(r == NULL)
231 return -EINVAL;
232 #ifdef CONFIG_ARM
233 base = (void __iomem *)IO_ADDRESS(r->start - IO_START);
234 #else
235 base = ioremap(r->start, 0x1000);
236 #endif
237 r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
238 if(r == NULL)
239 return -EINVAL;
240 irq = r->start;
241
242 if(pdev->id >= goldfish_tty_line_count)
243 return -EINVAL;
244
245 mutex_lock(&goldfish_tty_lock);
246 if(goldfish_tty_current_line_count == 0) {
247 ret = goldfish_tty_create_driver();
248 if(ret)
249 goto err_create_driver_failed;
250 }
251 goldfish_tty_current_line_count++;
252
253 spin_lock_init(&goldfish_ttys[pdev->id].lock);
254 goldfish_ttys[pdev->id].base = base;
255 goldfish_ttys[pdev->id].irq = irq;
256
257 writel(GOLDFISH_TTY_CMD_INT_DISABLE, base + GOLDFISH_TTY_CMD);
258
259 ret = request_irq(irq, goldfish_tty_interrupt, IRQF_SHARED, "goldfish_tty", pdev);
260 if(ret)
261 goto err_request_irq_failed;
262
263
264 ttydev = tty_register_device(goldfish_tty_driver, pdev->id, NULL);
265 if(IS_ERR(ttydev)) {
266 ret = PTR_ERR(ttydev);
267 goto err_tty_register_device_failed;
268 }
269
270 strcpy(goldfish_ttys[pdev->id].console.name, "ttyS");
271 goldfish_ttys[pdev->id].console.write = goldfish_tty_console_write;
272 goldfish_ttys[pdev->id].console.device = goldfish_tty_console_device;
273 goldfish_ttys[pdev->id].console.setup = goldfish_tty_console_setup;
274 goldfish_ttys[pdev->id].console.flags = CON_PRINTBUFFER;
275 goldfish_ttys[pdev->id].console.index = pdev->id;
276 register_console(&goldfish_ttys[pdev->id].console);
277
278
279 mutex_unlock(&goldfish_tty_lock);
280
281 return 0;
282
283 tty_unregister_device(goldfish_tty_driver, i);
284 err_tty_register_device_failed:
285 free_irq(irq, pdev);
286 err_request_irq_failed:
287 goldfish_tty_current_line_count--;
288 if(goldfish_tty_current_line_count == 0) {
289 goldfish_tty_delete_driver();
290 }
291 err_create_driver_failed:
292 mutex_unlock(&goldfish_tty_lock);
293 return ret;
294 }
295
goldfish_tty_remove(struct platform_device * pdev)296 static int __devexit goldfish_tty_remove(struct platform_device *pdev)
297 {
298 mutex_lock(&goldfish_tty_lock);
299 unregister_console(&goldfish_ttys[pdev->id].console);
300 tty_unregister_device(goldfish_tty_driver, pdev->id);
301 goldfish_ttys[pdev->id].base = 0;
302 free_irq(goldfish_ttys[pdev->id].irq, pdev);
303 goldfish_tty_current_line_count--;
304 if(goldfish_tty_current_line_count == 0) {
305 goldfish_tty_delete_driver();
306 }
307 mutex_unlock(&goldfish_tty_lock);
308 return 0;
309 }
310
311 static struct platform_driver goldfish_tty_platform_driver = {
312 .probe = goldfish_tty_probe,
313 .remove = __devexit_p(goldfish_tty_remove),
314 .driver = {
315 .name = "goldfish_tty"
316 }
317 };
318
goldfish_tty_init(void)319 static int __init goldfish_tty_init(void)
320 {
321 return platform_driver_register(&goldfish_tty_platform_driver);
322 }
323
goldfish_tty_exit(void)324 static void __exit goldfish_tty_exit(void)
325 {
326 platform_driver_unregister(&goldfish_tty_platform_driver);
327 }
328
329 module_init(goldfish_tty_init);
330 module_exit(goldfish_tty_exit);
331