• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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