| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 1 | /* | 
|  | 2 | * linux/drivers/mmc/card/sdio_uart.c - SDIO UART/GPS driver | 
|  | 3 | * | 
|  | 4 | * Based on drivers/serial/8250.c and drivers/serial/serial_core.c | 
|  | 5 | * by Russell King. | 
|  | 6 | * | 
|  | 7 | * Author:	Nicolas Pitre | 
|  | 8 | * Created:	June 15, 2007 | 
|  | 9 | * Copyright:	MontaVista Software, Inc. | 
|  | 10 | * | 
|  | 11 | * This program is free software; you can redistribute it and/or modify | 
|  | 12 | * it under the terms of the GNU General Public License as published by | 
|  | 13 | * the Free Software Foundation; either version 2 of the License, or (at | 
|  | 14 | * your option) any later version. | 
|  | 15 | */ | 
|  | 16 |  | 
|  | 17 | /* | 
|  | 18 | * Note: Although this driver assumes a 16550A-like UART implementation, | 
|  | 19 | * it is not possible to leverage the common 8250/16550 driver, nor the | 
|  | 20 | * core UART infrastructure, as they assumes direct access to the hardware | 
|  | 21 | * registers, often under a spinlock.  This is not possible in the SDIO | 
|  | 22 | * context as SDIO access functions must be able to sleep. | 
|  | 23 | * | 
|  | 24 | * Because we need to lock the SDIO host to ensure an exclusive access to | 
|  | 25 | * the card, we simply rely on that lock to also prevent and serialize | 
|  | 26 | * concurrent access to the same port. | 
|  | 27 | */ | 
|  | 28 |  | 
|  | 29 | #include <linux/module.h> | 
|  | 30 | #include <linux/init.h> | 
|  | 31 | #include <linux/kernel.h> | 
| Alan Cox | 4b3b49b | 2009-11-30 13:16:36 +0000 | [diff] [blame] | 32 | #include <linux/sched.h> | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 33 | #include <linux/mutex.h> | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 34 | #include <linux/seq_file.h> | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 35 | #include <linux/serial_reg.h> | 
|  | 36 | #include <linux/circ_buf.h> | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 37 | #include <linux/tty.h> | 
|  | 38 | #include <linux/tty_flip.h> | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 39 | #include <linux/kfifo.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 40 | #include <linux/slab.h> | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 41 |  | 
|  | 42 | #include <linux/mmc/core.h> | 
|  | 43 | #include <linux/mmc/card.h> | 
|  | 44 | #include <linux/mmc/sdio_func.h> | 
|  | 45 | #include <linux/mmc/sdio_ids.h> | 
|  | 46 |  | 
|  | 47 |  | 
|  | 48 | #define UART_NR		8	/* Number of UARTs this driver can handle */ | 
|  | 49 |  | 
|  | 50 |  | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 51 | #define FIFO_SIZE	PAGE_SIZE | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 52 | #define WAKEUP_CHARS	256 | 
|  | 53 |  | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 54 | struct uart_icount { | 
|  | 55 | __u32	cts; | 
|  | 56 | __u32	dsr; | 
|  | 57 | __u32	rng; | 
|  | 58 | __u32	dcd; | 
|  | 59 | __u32	rx; | 
|  | 60 | __u32	tx; | 
|  | 61 | __u32	frame; | 
|  | 62 | __u32	overrun; | 
|  | 63 | __u32	parity; | 
|  | 64 | __u32	brk; | 
|  | 65 | }; | 
|  | 66 |  | 
|  | 67 | struct sdio_uart_port { | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 68 | struct tty_port		port; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 69 | struct kref		kref; | 
|  | 70 | struct tty_struct	*tty; | 
|  | 71 | unsigned int		index; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 72 | struct sdio_func	*func; | 
|  | 73 | struct mutex		func_lock; | 
| Nicolas Pitre | 15b82b4 | 2007-08-20 17:17:37 -0400 | [diff] [blame] | 74 | struct task_struct	*in_sdio_uart_irq; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 75 | unsigned int		regs_offset; | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 76 | struct kfifo		xmit_fifo; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 77 | spinlock_t		write_lock; | 
|  | 78 | struct uart_icount	icount; | 
|  | 79 | unsigned int		uartclk; | 
|  | 80 | unsigned int		mctrl; | 
| Alan Cox | 4b3b49b | 2009-11-30 13:16:36 +0000 | [diff] [blame] | 81 | unsigned int		rx_mctrl; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 82 | unsigned int		read_status_mask; | 
|  | 83 | unsigned int		ignore_status_mask; | 
|  | 84 | unsigned char		x_char; | 
|  | 85 | unsigned char           ier; | 
|  | 86 | unsigned char           lcr; | 
|  | 87 | }; | 
|  | 88 |  | 
|  | 89 | static struct sdio_uart_port *sdio_uart_table[UART_NR]; | 
|  | 90 | static DEFINE_SPINLOCK(sdio_uart_table_lock); | 
|  | 91 |  | 
|  | 92 | static int sdio_uart_add_port(struct sdio_uart_port *port) | 
|  | 93 | { | 
|  | 94 | int index, ret = -EBUSY; | 
|  | 95 |  | 
|  | 96 | kref_init(&port->kref); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 97 | mutex_init(&port->func_lock); | 
|  | 98 | spin_lock_init(&port->write_lock); | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 99 | if (kfifo_alloc(&port->xmit_fifo, FIFO_SIZE, GFP_KERNEL)) | 
|  | 100 | return -ENOMEM; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 101 |  | 
|  | 102 | spin_lock(&sdio_uart_table_lock); | 
|  | 103 | for (index = 0; index < UART_NR; index++) { | 
|  | 104 | if (!sdio_uart_table[index]) { | 
|  | 105 | port->index = index; | 
|  | 106 | sdio_uart_table[index] = port; | 
|  | 107 | ret = 0; | 
|  | 108 | break; | 
|  | 109 | } | 
|  | 110 | } | 
|  | 111 | spin_unlock(&sdio_uart_table_lock); | 
|  | 112 |  | 
|  | 113 | return ret; | 
|  | 114 | } | 
|  | 115 |  | 
|  | 116 | static struct sdio_uart_port *sdio_uart_port_get(unsigned index) | 
|  | 117 | { | 
|  | 118 | struct sdio_uart_port *port; | 
|  | 119 |  | 
|  | 120 | if (index >= UART_NR) | 
|  | 121 | return NULL; | 
|  | 122 |  | 
|  | 123 | spin_lock(&sdio_uart_table_lock); | 
|  | 124 | port = sdio_uart_table[index]; | 
|  | 125 | if (port) | 
|  | 126 | kref_get(&port->kref); | 
|  | 127 | spin_unlock(&sdio_uart_table_lock); | 
|  | 128 |  | 
|  | 129 | return port; | 
|  | 130 | } | 
|  | 131 |  | 
|  | 132 | static void sdio_uart_port_destroy(struct kref *kref) | 
|  | 133 | { | 
|  | 134 | struct sdio_uart_port *port = | 
|  | 135 | container_of(kref, struct sdio_uart_port, kref); | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 136 | kfifo_free(&port->xmit_fifo); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 137 | kfree(port); | 
|  | 138 | } | 
|  | 139 |  | 
|  | 140 | static void sdio_uart_port_put(struct sdio_uart_port *port) | 
|  | 141 | { | 
|  | 142 | kref_put(&port->kref, sdio_uart_port_destroy); | 
|  | 143 | } | 
|  | 144 |  | 
|  | 145 | static void sdio_uart_port_remove(struct sdio_uart_port *port) | 
|  | 146 | { | 
|  | 147 | struct sdio_func *func; | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 148 | struct tty_struct *tty; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 149 |  | 
|  | 150 | BUG_ON(sdio_uart_table[port->index] != port); | 
|  | 151 |  | 
|  | 152 | spin_lock(&sdio_uart_table_lock); | 
|  | 153 | sdio_uart_table[port->index] = NULL; | 
|  | 154 | spin_unlock(&sdio_uart_table_lock); | 
|  | 155 |  | 
|  | 156 | /* | 
|  | 157 | * We're killing a port that potentially still is in use by | 
|  | 158 | * the tty layer. Be careful to prevent any further access | 
|  | 159 | * to the SDIO function and arrange for the tty layer to | 
|  | 160 | * give up on that port ASAP. | 
|  | 161 | * Beware: the lock ordering is critical. | 
|  | 162 | */ | 
| Alan Cox | 0a68f64 | 2009-11-05 13:28:38 +0000 | [diff] [blame] | 163 | mutex_lock(&port->port.mutex); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 164 | mutex_lock(&port->func_lock); | 
|  | 165 | func = port->func; | 
|  | 166 | sdio_claim_host(func); | 
|  | 167 | port->func = NULL; | 
|  | 168 | mutex_unlock(&port->func_lock); | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 169 | tty = tty_port_tty_get(&port->port); | 
|  | 170 | /* tty_hangup is async so is this safe as is ?? */ | 
|  | 171 | if (tty) { | 
|  | 172 | tty_hangup(tty); | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 173 | tty_kref_put(tty); | 
|  | 174 | } | 
| Alan Cox | 0a68f64 | 2009-11-05 13:28:38 +0000 | [diff] [blame] | 175 | mutex_unlock(&port->port.mutex); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 176 | sdio_release_irq(func); | 
|  | 177 | sdio_disable_func(func); | 
|  | 178 | sdio_release_host(func); | 
|  | 179 |  | 
|  | 180 | sdio_uart_port_put(port); | 
|  | 181 | } | 
|  | 182 |  | 
|  | 183 | static int sdio_uart_claim_func(struct sdio_uart_port *port) | 
|  | 184 | { | 
|  | 185 | mutex_lock(&port->func_lock); | 
|  | 186 | if (unlikely(!port->func)) { | 
|  | 187 | mutex_unlock(&port->func_lock); | 
|  | 188 | return -ENODEV; | 
|  | 189 | } | 
| Nicolas Pitre | 15b82b4 | 2007-08-20 17:17:37 -0400 | [diff] [blame] | 190 | if (likely(port->in_sdio_uart_irq != current)) | 
|  | 191 | sdio_claim_host(port->func); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 192 | mutex_unlock(&port->func_lock); | 
|  | 193 | return 0; | 
|  | 194 | } | 
|  | 195 |  | 
|  | 196 | static inline void sdio_uart_release_func(struct sdio_uart_port *port) | 
|  | 197 | { | 
| Nicolas Pitre | 15b82b4 | 2007-08-20 17:17:37 -0400 | [diff] [blame] | 198 | if (likely(port->in_sdio_uart_irq != current)) | 
|  | 199 | sdio_release_host(port->func); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 200 | } | 
|  | 201 |  | 
|  | 202 | static inline unsigned int sdio_in(struct sdio_uart_port *port, int offset) | 
|  | 203 | { | 
|  | 204 | unsigned char c; | 
|  | 205 | c = sdio_readb(port->func, port->regs_offset + offset, NULL); | 
|  | 206 | return c; | 
|  | 207 | } | 
|  | 208 |  | 
|  | 209 | static inline void sdio_out(struct sdio_uart_port *port, int offset, int value) | 
|  | 210 | { | 
|  | 211 | sdio_writeb(port->func, value, port->regs_offset + offset, NULL); | 
|  | 212 | } | 
|  | 213 |  | 
|  | 214 | static unsigned int sdio_uart_get_mctrl(struct sdio_uart_port *port) | 
|  | 215 | { | 
|  | 216 | unsigned char status; | 
|  | 217 | unsigned int ret; | 
|  | 218 |  | 
| Alan Cox | 4b3b49b | 2009-11-30 13:16:36 +0000 | [diff] [blame] | 219 | /* FIXME: What stops this losing the delta bits and breaking | 
|  | 220 | sdio_uart_check_modem_status ? */ | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 221 | status = sdio_in(port, UART_MSR); | 
|  | 222 |  | 
|  | 223 | ret = 0; | 
|  | 224 | if (status & UART_MSR_DCD) | 
|  | 225 | ret |= TIOCM_CAR; | 
|  | 226 | if (status & UART_MSR_RI) | 
|  | 227 | ret |= TIOCM_RNG; | 
|  | 228 | if (status & UART_MSR_DSR) | 
|  | 229 | ret |= TIOCM_DSR; | 
|  | 230 | if (status & UART_MSR_CTS) | 
|  | 231 | ret |= TIOCM_CTS; | 
|  | 232 | return ret; | 
|  | 233 | } | 
|  | 234 |  | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 235 | static void sdio_uart_write_mctrl(struct sdio_uart_port *port, | 
|  | 236 | unsigned int mctrl) | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 237 | { | 
|  | 238 | unsigned char mcr = 0; | 
|  | 239 |  | 
|  | 240 | if (mctrl & TIOCM_RTS) | 
|  | 241 | mcr |= UART_MCR_RTS; | 
|  | 242 | if (mctrl & TIOCM_DTR) | 
|  | 243 | mcr |= UART_MCR_DTR; | 
|  | 244 | if (mctrl & TIOCM_OUT1) | 
|  | 245 | mcr |= UART_MCR_OUT1; | 
|  | 246 | if (mctrl & TIOCM_OUT2) | 
|  | 247 | mcr |= UART_MCR_OUT2; | 
|  | 248 | if (mctrl & TIOCM_LOOP) | 
|  | 249 | mcr |= UART_MCR_LOOP; | 
|  | 250 |  | 
|  | 251 | sdio_out(port, UART_MCR, mcr); | 
|  | 252 | } | 
|  | 253 |  | 
|  | 254 | static inline void sdio_uart_update_mctrl(struct sdio_uart_port *port, | 
|  | 255 | unsigned int set, unsigned int clear) | 
|  | 256 | { | 
|  | 257 | unsigned int old; | 
|  | 258 |  | 
|  | 259 | old = port->mctrl; | 
|  | 260 | port->mctrl = (old & ~clear) | set; | 
|  | 261 | if (old != port->mctrl) | 
|  | 262 | sdio_uart_write_mctrl(port, port->mctrl); | 
|  | 263 | } | 
|  | 264 |  | 
|  | 265 | #define sdio_uart_set_mctrl(port, x)	sdio_uart_update_mctrl(port, x, 0) | 
|  | 266 | #define sdio_uart_clear_mctrl(port, x)	sdio_uart_update_mctrl(port, 0, x) | 
|  | 267 |  | 
|  | 268 | static void sdio_uart_change_speed(struct sdio_uart_port *port, | 
|  | 269 | struct ktermios *termios, | 
|  | 270 | struct ktermios *old) | 
|  | 271 | { | 
|  | 272 | unsigned char cval, fcr = 0; | 
|  | 273 | unsigned int baud, quot; | 
|  | 274 |  | 
|  | 275 | switch (termios->c_cflag & CSIZE) { | 
|  | 276 | case CS5: | 
|  | 277 | cval = UART_LCR_WLEN5; | 
|  | 278 | break; | 
|  | 279 | case CS6: | 
|  | 280 | cval = UART_LCR_WLEN6; | 
|  | 281 | break; | 
|  | 282 | case CS7: | 
|  | 283 | cval = UART_LCR_WLEN7; | 
|  | 284 | break; | 
|  | 285 | default: | 
|  | 286 | case CS8: | 
|  | 287 | cval = UART_LCR_WLEN8; | 
|  | 288 | break; | 
|  | 289 | } | 
|  | 290 |  | 
|  | 291 | if (termios->c_cflag & CSTOPB) | 
|  | 292 | cval |= UART_LCR_STOP; | 
|  | 293 | if (termios->c_cflag & PARENB) | 
|  | 294 | cval |= UART_LCR_PARITY; | 
|  | 295 | if (!(termios->c_cflag & PARODD)) | 
|  | 296 | cval |= UART_LCR_EPAR; | 
|  | 297 |  | 
|  | 298 | for (;;) { | 
|  | 299 | baud = tty_termios_baud_rate(termios); | 
|  | 300 | if (baud == 0) | 
|  | 301 | baud = 9600;  /* Special case: B0 rate. */ | 
|  | 302 | if (baud <= port->uartclk) | 
|  | 303 | break; | 
|  | 304 | /* | 
|  | 305 | * Oops, the quotient was zero.  Try again with the old | 
|  | 306 | * baud rate if possible, otherwise default to 9600. | 
|  | 307 | */ | 
|  | 308 | termios->c_cflag &= ~CBAUD; | 
|  | 309 | if (old) { | 
|  | 310 | termios->c_cflag |= old->c_cflag & CBAUD; | 
|  | 311 | old = NULL; | 
|  | 312 | } else | 
|  | 313 | termios->c_cflag |= B9600; | 
|  | 314 | } | 
|  | 315 | quot = (2 * port->uartclk + baud) / (2 * baud); | 
|  | 316 |  | 
|  | 317 | if (baud < 2400) | 
|  | 318 | fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1; | 
|  | 319 | else | 
|  | 320 | fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10; | 
|  | 321 |  | 
|  | 322 | port->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR; | 
|  | 323 | if (termios->c_iflag & INPCK) | 
|  | 324 | port->read_status_mask |= UART_LSR_FE | UART_LSR_PE; | 
|  | 325 | if (termios->c_iflag & (BRKINT | PARMRK)) | 
|  | 326 | port->read_status_mask |= UART_LSR_BI; | 
|  | 327 |  | 
|  | 328 | /* | 
|  | 329 | * Characters to ignore | 
|  | 330 | */ | 
|  | 331 | port->ignore_status_mask = 0; | 
|  | 332 | if (termios->c_iflag & IGNPAR) | 
|  | 333 | port->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; | 
|  | 334 | if (termios->c_iflag & IGNBRK) { | 
|  | 335 | port->ignore_status_mask |= UART_LSR_BI; | 
|  | 336 | /* | 
|  | 337 | * If we're ignoring parity and break indicators, | 
|  | 338 | * ignore overruns too (for real raw support). | 
|  | 339 | */ | 
|  | 340 | if (termios->c_iflag & IGNPAR) | 
|  | 341 | port->ignore_status_mask |= UART_LSR_OE; | 
|  | 342 | } | 
|  | 343 |  | 
|  | 344 | /* | 
|  | 345 | * ignore all characters if CREAD is not set | 
|  | 346 | */ | 
|  | 347 | if ((termios->c_cflag & CREAD) == 0) | 
|  | 348 | port->ignore_status_mask |= UART_LSR_DR; | 
|  | 349 |  | 
|  | 350 | /* | 
|  | 351 | * CTS flow control flag and modem status interrupts | 
|  | 352 | */ | 
|  | 353 | port->ier &= ~UART_IER_MSI; | 
|  | 354 | if ((termios->c_cflag & CRTSCTS) || !(termios->c_cflag & CLOCAL)) | 
|  | 355 | port->ier |= UART_IER_MSI; | 
|  | 356 |  | 
|  | 357 | port->lcr = cval; | 
|  | 358 |  | 
|  | 359 | sdio_out(port, UART_IER, port->ier); | 
|  | 360 | sdio_out(port, UART_LCR, cval | UART_LCR_DLAB); | 
|  | 361 | sdio_out(port, UART_DLL, quot & 0xff); | 
|  | 362 | sdio_out(port, UART_DLM, quot >> 8); | 
|  | 363 | sdio_out(port, UART_LCR, cval); | 
|  | 364 | sdio_out(port, UART_FCR, fcr); | 
|  | 365 |  | 
|  | 366 | sdio_uart_write_mctrl(port, port->mctrl); | 
|  | 367 | } | 
|  | 368 |  | 
|  | 369 | static void sdio_uart_start_tx(struct sdio_uart_port *port) | 
|  | 370 | { | 
|  | 371 | if (!(port->ier & UART_IER_THRI)) { | 
|  | 372 | port->ier |= UART_IER_THRI; | 
|  | 373 | sdio_out(port, UART_IER, port->ier); | 
|  | 374 | } | 
|  | 375 | } | 
|  | 376 |  | 
|  | 377 | static void sdio_uart_stop_tx(struct sdio_uart_port *port) | 
|  | 378 | { | 
|  | 379 | if (port->ier & UART_IER_THRI) { | 
|  | 380 | port->ier &= ~UART_IER_THRI; | 
|  | 381 | sdio_out(port, UART_IER, port->ier); | 
|  | 382 | } | 
|  | 383 | } | 
|  | 384 |  | 
|  | 385 | static void sdio_uart_stop_rx(struct sdio_uart_port *port) | 
|  | 386 | { | 
|  | 387 | port->ier &= ~UART_IER_RLSI; | 
|  | 388 | port->read_status_mask &= ~UART_LSR_DR; | 
|  | 389 | sdio_out(port, UART_IER, port->ier); | 
|  | 390 | } | 
|  | 391 |  | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 392 | static void sdio_uart_receive_chars(struct sdio_uart_port *port, | 
|  | 393 | unsigned int *status) | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 394 | { | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 395 | struct tty_struct *tty = tty_port_tty_get(&port->port); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 396 | unsigned int ch, flag; | 
|  | 397 | int max_count = 256; | 
|  | 398 |  | 
|  | 399 | do { | 
|  | 400 | ch = sdio_in(port, UART_RX); | 
|  | 401 | flag = TTY_NORMAL; | 
|  | 402 | port->icount.rx++; | 
|  | 403 |  | 
|  | 404 | if (unlikely(*status & (UART_LSR_BI | UART_LSR_PE | | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 405 | UART_LSR_FE | UART_LSR_OE))) { | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 406 | /* | 
|  | 407 | * For statistics only | 
|  | 408 | */ | 
|  | 409 | if (*status & UART_LSR_BI) { | 
|  | 410 | *status &= ~(UART_LSR_FE | UART_LSR_PE); | 
|  | 411 | port->icount.brk++; | 
|  | 412 | } else if (*status & UART_LSR_PE) | 
|  | 413 | port->icount.parity++; | 
|  | 414 | else if (*status & UART_LSR_FE) | 
|  | 415 | port->icount.frame++; | 
|  | 416 | if (*status & UART_LSR_OE) | 
|  | 417 | port->icount.overrun++; | 
|  | 418 |  | 
|  | 419 | /* | 
|  | 420 | * Mask off conditions which should be ignored. | 
|  | 421 | */ | 
|  | 422 | *status &= port->read_status_mask; | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 423 | if (*status & UART_LSR_BI) | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 424 | flag = TTY_BREAK; | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 425 | else if (*status & UART_LSR_PE) | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 426 | flag = TTY_PARITY; | 
|  | 427 | else if (*status & UART_LSR_FE) | 
|  | 428 | flag = TTY_FRAME; | 
|  | 429 | } | 
|  | 430 |  | 
|  | 431 | if ((*status & port->ignore_status_mask & ~UART_LSR_OE) == 0) | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 432 | if (tty) | 
|  | 433 | tty_insert_flip_char(tty, ch, flag); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 434 |  | 
|  | 435 | /* | 
|  | 436 | * Overrun is special.  Since it's reported immediately, | 
|  | 437 | * it doesn't affect the current character. | 
|  | 438 | */ | 
|  | 439 | if (*status & ~port->ignore_status_mask & UART_LSR_OE) | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 440 | if (tty) | 
|  | 441 | tty_insert_flip_char(tty, 0, TTY_OVERRUN); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 442 |  | 
|  | 443 | *status = sdio_in(port, UART_LSR); | 
|  | 444 | } while ((*status & UART_LSR_DR) && (max_count-- > 0)); | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 445 | if (tty) { | 
|  | 446 | tty_flip_buffer_push(tty); | 
|  | 447 | tty_kref_put(tty); | 
|  | 448 | } | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 449 | } | 
|  | 450 |  | 
|  | 451 | static void sdio_uart_transmit_chars(struct sdio_uart_port *port) | 
|  | 452 | { | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 453 | struct kfifo *xmit = &port->xmit_fifo; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 454 | int count; | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 455 | struct tty_struct *tty; | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 456 | u8 iobuf[16]; | 
|  | 457 | int len; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 458 |  | 
|  | 459 | if (port->x_char) { | 
|  | 460 | sdio_out(port, UART_TX, port->x_char); | 
|  | 461 | port->icount.tx++; | 
|  | 462 | port->x_char = 0; | 
|  | 463 | return; | 
|  | 464 | } | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 465 |  | 
|  | 466 | tty = tty_port_tty_get(&port->port); | 
|  | 467 |  | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 468 | if (tty == NULL || !kfifo_len(xmit) || | 
| Alan Cox | c271cf3 | 2009-11-30 13:16:25 +0000 | [diff] [blame] | 469 | tty->stopped || tty->hw_stopped) { | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 470 | sdio_uart_stop_tx(port); | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 471 | tty_kref_put(tty); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 472 | return; | 
|  | 473 | } | 
|  | 474 |  | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 475 | len = kfifo_out_locked(xmit, iobuf, 16, &port->write_lock); | 
|  | 476 | for (count = 0; count < len; count++) { | 
|  | 477 | sdio_out(port, UART_TX, iobuf[count]); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 478 | port->icount.tx++; | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 479 | } | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 480 |  | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 481 | len = kfifo_len(xmit); | 
|  | 482 | if (len < WAKEUP_CHARS) { | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 483 | tty_wakeup(tty); | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 484 | if (len == 0) | 
|  | 485 | sdio_uart_stop_tx(port); | 
|  | 486 | } | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 487 | tty_kref_put(tty); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 488 | } | 
|  | 489 |  | 
|  | 490 | static void sdio_uart_check_modem_status(struct sdio_uart_port *port) | 
|  | 491 | { | 
|  | 492 | int status; | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 493 | struct tty_struct *tty; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 494 |  | 
|  | 495 | status = sdio_in(port, UART_MSR); | 
|  | 496 |  | 
|  | 497 | if ((status & UART_MSR_ANY_DELTA) == 0) | 
|  | 498 | return; | 
|  | 499 |  | 
|  | 500 | if (status & UART_MSR_TERI) | 
|  | 501 | port->icount.rng++; | 
|  | 502 | if (status & UART_MSR_DDSR) | 
|  | 503 | port->icount.dsr++; | 
| Alan Cox | 4b3b49b | 2009-11-30 13:16:36 +0000 | [diff] [blame] | 504 | if (status & UART_MSR_DDCD) { | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 505 | port->icount.dcd++; | 
| Alan Cox | 4b3b49b | 2009-11-30 13:16:36 +0000 | [diff] [blame] | 506 | /* DCD raise - wake for open */ | 
|  | 507 | if (status & UART_MSR_DCD) | 
|  | 508 | wake_up_interruptible(&port->port.open_wait); | 
|  | 509 | else { | 
|  | 510 | /* DCD drop - hang up if tty attached */ | 
|  | 511 | tty = tty_port_tty_get(&port->port); | 
|  | 512 | if (tty) { | 
|  | 513 | tty_hangup(tty); | 
|  | 514 | tty_kref_put(tty); | 
|  | 515 | } | 
|  | 516 | } | 
|  | 517 | } | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 518 | if (status & UART_MSR_DCTS) { | 
|  | 519 | port->icount.cts++; | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 520 | tty = tty_port_tty_get(&port->port); | 
|  | 521 | if (tty && (tty->termios->c_cflag & CRTSCTS)) { | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 522 | int cts = (status & UART_MSR_CTS); | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 523 | if (tty->hw_stopped) { | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 524 | if (cts) { | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 525 | tty->hw_stopped = 0; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 526 | sdio_uart_start_tx(port); | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 527 | tty_wakeup(tty); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 528 | } | 
|  | 529 | } else { | 
|  | 530 | if (!cts) { | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 531 | tty->hw_stopped = 1; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 532 | sdio_uart_stop_tx(port); | 
|  | 533 | } | 
|  | 534 | } | 
|  | 535 | } | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 536 | tty_kref_put(tty); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 537 | } | 
|  | 538 | } | 
|  | 539 |  | 
|  | 540 | /* | 
|  | 541 | * This handles the interrupt from one port. | 
|  | 542 | */ | 
|  | 543 | static void sdio_uart_irq(struct sdio_func *func) | 
|  | 544 | { | 
|  | 545 | struct sdio_uart_port *port = sdio_get_drvdata(func); | 
|  | 546 | unsigned int iir, lsr; | 
|  | 547 |  | 
| Nicolas Pitre | 15b82b4 | 2007-08-20 17:17:37 -0400 | [diff] [blame] | 548 | /* | 
|  | 549 | * In a few places sdio_uart_irq() is called directly instead of | 
|  | 550 | * waiting for the actual interrupt to be raised and the SDIO IRQ | 
|  | 551 | * thread scheduled in order to reduce latency.  However, some | 
|  | 552 | * interaction with the tty core may end up calling us back | 
|  | 553 | * (serial echo, flow control, etc.) through those same places | 
|  | 554 | * causing undesirable effects.  Let's stop the recursion here. | 
|  | 555 | */ | 
|  | 556 | if (unlikely(port->in_sdio_uart_irq == current)) | 
|  | 557 | return; | 
|  | 558 |  | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 559 | iir = sdio_in(port, UART_IIR); | 
|  | 560 | if (iir & UART_IIR_NO_INT) | 
|  | 561 | return; | 
| Nicolas Pitre | 15b82b4 | 2007-08-20 17:17:37 -0400 | [diff] [blame] | 562 |  | 
|  | 563 | port->in_sdio_uart_irq = current; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 564 | lsr = sdio_in(port, UART_LSR); | 
|  | 565 | if (lsr & UART_LSR_DR) | 
|  | 566 | sdio_uart_receive_chars(port, &lsr); | 
|  | 567 | sdio_uart_check_modem_status(port); | 
|  | 568 | if (lsr & UART_LSR_THRE) | 
|  | 569 | sdio_uart_transmit_chars(port); | 
| Nicolas Pitre | 15b82b4 | 2007-08-20 17:17:37 -0400 | [diff] [blame] | 570 | port->in_sdio_uart_irq = NULL; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 571 | } | 
|  | 572 |  | 
| Alan Cox | 4b3b49b | 2009-11-30 13:16:36 +0000 | [diff] [blame] | 573 | static int uart_carrier_raised(struct tty_port *tport) | 
|  | 574 | { | 
|  | 575 | struct sdio_uart_port *port = | 
|  | 576 | container_of(tport, struct sdio_uart_port, port); | 
|  | 577 | unsigned int ret = sdio_uart_claim_func(port); | 
| Adam Buchbinder | c9404c9 | 2009-12-18 15:40:42 -0500 | [diff] [blame] | 578 | if (ret)	/* Missing hardware shouldn't block for carrier */ | 
| Alan Cox | 4b3b49b | 2009-11-30 13:16:36 +0000 | [diff] [blame] | 579 | return 1; | 
|  | 580 | ret = sdio_uart_get_mctrl(port); | 
|  | 581 | sdio_uart_release_func(port); | 
|  | 582 | if (ret & TIOCM_CAR) | 
|  | 583 | return 1; | 
|  | 584 | return 0; | 
|  | 585 | } | 
|  | 586 |  | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 587 | /** | 
|  | 588 | *	uart_dtr_rts		-	 port helper to set uart signals | 
|  | 589 | *	@tport: tty port to be updated | 
|  | 590 | *	@onoff: set to turn on DTR/RTS | 
|  | 591 | * | 
|  | 592 | *	Called by the tty port helpers when the modem signals need to be | 
|  | 593 | *	adjusted during an open, close and hangup. | 
|  | 594 | */ | 
| Alan Cox | 530646f | 2009-11-05 13:28:29 +0000 | [diff] [blame] | 595 |  | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 596 | static void uart_dtr_rts(struct tty_port *tport, int onoff) | 
|  | 597 | { | 
|  | 598 | struct sdio_uart_port *port = | 
|  | 599 | container_of(tport, struct sdio_uart_port, port); | 
| Alan Cox | 1f100b3 | 2009-11-30 13:16:30 +0000 | [diff] [blame] | 600 | int ret = sdio_uart_claim_func(port); | 
|  | 601 | if (ret) | 
|  | 602 | return; | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 603 | if (onoff == 0) | 
|  | 604 | sdio_uart_clear_mctrl(port, TIOCM_DTR | TIOCM_RTS); | 
|  | 605 | else | 
|  | 606 | sdio_uart_set_mctrl(port, TIOCM_DTR | TIOCM_RTS); | 
| Alan Cox | 1f100b3 | 2009-11-30 13:16:30 +0000 | [diff] [blame] | 607 | sdio_uart_release_func(port); | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 608 | } | 
|  | 609 |  | 
|  | 610 | /** | 
|  | 611 | *	sdio_uart_activate	-	start up hardware | 
|  | 612 | *	@tport: tty port to activate | 
|  | 613 | *	@tty: tty bound to this port | 
|  | 614 | * | 
|  | 615 | *	Activate a tty port. The port locking guarantees us this will be | 
|  | 616 | *	run exactly once per set of opens, and if successful will see the | 
|  | 617 | *	shutdown method run exactly once to match. Start up and shutdown are | 
|  | 618 | *	protected from each other by the internal locking and will not run | 
|  | 619 | *	at the same time even during a hangup event. | 
|  | 620 | * | 
|  | 621 | *	If we successfully start up the port we take an extra kref as we | 
|  | 622 | *	will keep it around until shutdown when the kref is dropped. | 
|  | 623 | */ | 
|  | 624 |  | 
|  | 625 | static int sdio_uart_activate(struct tty_port *tport, struct tty_struct *tty) | 
|  | 626 | { | 
|  | 627 | struct sdio_uart_port *port = | 
|  | 628 | container_of(tport, struct sdio_uart_port, port); | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 629 | int ret; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 630 |  | 
|  | 631 | /* | 
|  | 632 | * Set the TTY IO error marker - we will only clear this | 
|  | 633 | * once we have successfully opened the port. | 
|  | 634 | */ | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 635 | set_bit(TTY_IO_ERROR, &tty->flags); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 636 |  | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 637 | kfifo_reset(&port->xmit_fifo); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 638 |  | 
|  | 639 | ret = sdio_uart_claim_func(port); | 
|  | 640 | if (ret) | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 641 | return ret; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 642 | ret = sdio_enable_func(port->func); | 
|  | 643 | if (ret) | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 644 | goto err1; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 645 | ret = sdio_claim_irq(port->func, sdio_uart_irq); | 
|  | 646 | if (ret) | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 647 | goto err2; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 648 |  | 
|  | 649 | /* | 
|  | 650 | * Clear the FIFO buffers and disable them. | 
|  | 651 | * (they will be reenabled in sdio_change_speed()) | 
|  | 652 | */ | 
|  | 653 | sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO); | 
|  | 654 | sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO | | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 655 | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 656 | sdio_out(port, UART_FCR, 0); | 
|  | 657 |  | 
|  | 658 | /* | 
|  | 659 | * Clear the interrupt registers. | 
|  | 660 | */ | 
|  | 661 | (void) sdio_in(port, UART_LSR); | 
|  | 662 | (void) sdio_in(port, UART_RX); | 
|  | 663 | (void) sdio_in(port, UART_IIR); | 
|  | 664 | (void) sdio_in(port, UART_MSR); | 
|  | 665 |  | 
|  | 666 | /* | 
|  | 667 | * Now, initialize the UART | 
|  | 668 | */ | 
|  | 669 | sdio_out(port, UART_LCR, UART_LCR_WLEN8); | 
|  | 670 |  | 
| Alan Cox | c271cf3 | 2009-11-30 13:16:25 +0000 | [diff] [blame] | 671 | port->ier = UART_IER_RLSI|UART_IER_RDI|UART_IER_RTOIE|UART_IER_UUE; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 672 | port->mctrl = TIOCM_OUT2; | 
|  | 673 |  | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 674 | sdio_uart_change_speed(port, tty->termios, NULL); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 675 |  | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 676 | if (tty->termios->c_cflag & CBAUD) | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 677 | sdio_uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR); | 
|  | 678 |  | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 679 | if (tty->termios->c_cflag & CRTSCTS) | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 680 | if (!(sdio_uart_get_mctrl(port) & TIOCM_CTS)) | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 681 | tty->hw_stopped = 1; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 682 |  | 
| Nicolas Pitre | 0395b48 | 2009-11-05 13:28:17 +0000 | [diff] [blame] | 683 | clear_bit(TTY_IO_ERROR, &tty->flags); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 684 |  | 
|  | 685 | /* Kick the IRQ handler once while we're still holding the host lock */ | 
|  | 686 | sdio_uart_irq(port->func); | 
|  | 687 |  | 
|  | 688 | sdio_uart_release_func(port); | 
|  | 689 | return 0; | 
|  | 690 |  | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 691 | err2: | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 692 | sdio_disable_func(port->func); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 693 | err1: | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 694 | sdio_uart_release_func(port); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 695 | return ret; | 
|  | 696 | } | 
|  | 697 |  | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 698 | /** | 
|  | 699 | *	sdio_uart_shutdown	-	stop hardware | 
|  | 700 | *	@tport: tty port to shut down | 
|  | 701 | * | 
|  | 702 | *	Deactivate a tty port. The port locking guarantees us this will be | 
|  | 703 | *	run only if a successful matching activate already ran. The two are | 
|  | 704 | *	protected from each other by the internal locking and will not run | 
|  | 705 | *	at the same time even during a hangup event. | 
|  | 706 | */ | 
|  | 707 |  | 
|  | 708 | static void sdio_uart_shutdown(struct tty_port *tport) | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 709 | { | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 710 | struct sdio_uart_port *port = | 
|  | 711 | container_of(tport, struct sdio_uart_port, port); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 712 | int ret; | 
|  | 713 |  | 
|  | 714 | ret = sdio_uart_claim_func(port); | 
|  | 715 | if (ret) | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 716 | return; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 717 |  | 
|  | 718 | sdio_uart_stop_rx(port); | 
|  | 719 |  | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 720 | /* Disable interrupts from this port */ | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 721 | sdio_release_irq(port->func); | 
|  | 722 | port->ier = 0; | 
|  | 723 | sdio_out(port, UART_IER, 0); | 
|  | 724 |  | 
|  | 725 | sdio_uart_clear_mctrl(port, TIOCM_OUT2); | 
|  | 726 |  | 
|  | 727 | /* Disable break condition and FIFOs. */ | 
|  | 728 | port->lcr &= ~UART_LCR_SBC; | 
|  | 729 | sdio_out(port, UART_LCR, port->lcr); | 
|  | 730 | sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO | | 
|  | 731 | UART_FCR_CLEAR_RCVR | | 
|  | 732 | UART_FCR_CLEAR_XMIT); | 
|  | 733 | sdio_out(port, UART_FCR, 0); | 
|  | 734 |  | 
|  | 735 | sdio_disable_func(port->func); | 
|  | 736 |  | 
|  | 737 | sdio_uart_release_func(port); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 738 | } | 
|  | 739 |  | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 740 | /** | 
|  | 741 | *	sdio_uart_install	-	install method | 
|  | 742 | *	@driver: the driver in use (sdio_uart in our case) | 
|  | 743 | *	@tty: the tty being bound | 
|  | 744 | * | 
|  | 745 | *	Look up and bind the tty and the driver together. Initialize | 
|  | 746 | *	any needed private data (in our case the termios) | 
|  | 747 | */ | 
|  | 748 |  | 
|  | 749 | static int sdio_uart_install(struct tty_driver *driver, struct tty_struct *tty) | 
|  | 750 | { | 
|  | 751 | int idx = tty->index; | 
|  | 752 | struct sdio_uart_port *port = sdio_uart_port_get(idx); | 
|  | 753 | int ret = tty_init_termios(tty); | 
|  | 754 |  | 
|  | 755 | if (ret == 0) { | 
|  | 756 | tty_driver_kref_get(driver); | 
|  | 757 | tty->count++; | 
|  | 758 | /* This is the ref sdio_uart_port get provided */ | 
|  | 759 | tty->driver_data = port; | 
|  | 760 | driver->ttys[idx] = tty; | 
|  | 761 | } else | 
|  | 762 | sdio_uart_port_put(port); | 
|  | 763 | return ret; | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 764 | } | 
|  | 765 |  | 
|  | 766 | /** | 
|  | 767 | *	sdio_uart_cleanup	-	called on the last tty kref drop | 
|  | 768 | *	@tty: the tty being destroyed | 
|  | 769 | * | 
|  | 770 | *	Called asynchronously when the last reference to the tty is dropped. | 
|  | 771 | *	We cannot destroy the tty->driver_data port kref until this point | 
|  | 772 | */ | 
|  | 773 |  | 
|  | 774 | static void sdio_uart_cleanup(struct tty_struct *tty) | 
|  | 775 | { | 
|  | 776 | struct sdio_uart_port *port = tty->driver_data; | 
|  | 777 | tty->driver_data = NULL;	/* Bug trap */ | 
|  | 778 | sdio_uart_port_put(port); | 
|  | 779 | } | 
|  | 780 |  | 
|  | 781 | /* | 
|  | 782 | *	Open/close/hangup is now entirely boilerplate | 
|  | 783 | */ | 
|  | 784 |  | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 785 | static int sdio_uart_open(struct tty_struct *tty, struct file *filp) | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 786 | { | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 787 | struct sdio_uart_port *port = tty->driver_data; | 
|  | 788 | return tty_port_open(&port->port, tty, filp); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 789 | } | 
|  | 790 |  | 
|  | 791 | static void sdio_uart_close(struct tty_struct *tty, struct file * filp) | 
|  | 792 | { | 
|  | 793 | struct sdio_uart_port *port = tty->driver_data; | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 794 | tty_port_close(&port->port, tty, filp); | 
|  | 795 | } | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 796 |  | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 797 | static void sdio_uart_hangup(struct tty_struct *tty) | 
|  | 798 | { | 
|  | 799 | struct sdio_uart_port *port = tty->driver_data; | 
|  | 800 | tty_port_hangup(&port->port); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 801 | } | 
|  | 802 |  | 
| Alan Cox | c271cf3 | 2009-11-30 13:16:25 +0000 | [diff] [blame] | 803 | static int sdio_uart_write(struct tty_struct *tty, const unsigned char *buf, | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 804 | int count) | 
|  | 805 | { | 
|  | 806 | struct sdio_uart_port *port = tty->driver_data; | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 807 | int ret; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 808 |  | 
|  | 809 | if (!port->func) | 
|  | 810 | return -ENODEV; | 
|  | 811 |  | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 812 | ret = kfifo_in_locked(&port->xmit_fifo, buf, count, &port->write_lock); | 
| Alan Cox | c271cf3 | 2009-11-30 13:16:25 +0000 | [diff] [blame] | 813 | if (!(port->ier & UART_IER_THRI)) { | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 814 | int err = sdio_uart_claim_func(port); | 
|  | 815 | if (!err) { | 
|  | 816 | sdio_uart_start_tx(port); | 
|  | 817 | sdio_uart_irq(port->func); | 
|  | 818 | sdio_uart_release_func(port); | 
|  | 819 | } else | 
|  | 820 | ret = err; | 
|  | 821 | } | 
|  | 822 |  | 
|  | 823 | return ret; | 
|  | 824 | } | 
|  | 825 |  | 
|  | 826 | static int sdio_uart_write_room(struct tty_struct *tty) | 
|  | 827 | { | 
|  | 828 | struct sdio_uart_port *port = tty->driver_data; | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 829 | return FIFO_SIZE - kfifo_len(&port->xmit_fifo); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 830 | } | 
|  | 831 |  | 
|  | 832 | static int sdio_uart_chars_in_buffer(struct tty_struct *tty) | 
|  | 833 | { | 
|  | 834 | struct sdio_uart_port *port = tty->driver_data; | 
| Alan Cox | 8b197a5 | 2010-02-08 10:08:39 +0000 | [diff] [blame] | 835 | return kfifo_len(&port->xmit_fifo); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 836 | } | 
|  | 837 |  | 
|  | 838 | static void sdio_uart_send_xchar(struct tty_struct *tty, char ch) | 
|  | 839 | { | 
|  | 840 | struct sdio_uart_port *port = tty->driver_data; | 
|  | 841 |  | 
|  | 842 | port->x_char = ch; | 
|  | 843 | if (ch && !(port->ier & UART_IER_THRI)) { | 
|  | 844 | if (sdio_uart_claim_func(port) != 0) | 
|  | 845 | return; | 
|  | 846 | sdio_uart_start_tx(port); | 
|  | 847 | sdio_uart_irq(port->func); | 
|  | 848 | sdio_uart_release_func(port); | 
|  | 849 | } | 
|  | 850 | } | 
|  | 851 |  | 
|  | 852 | static void sdio_uart_throttle(struct tty_struct *tty) | 
|  | 853 | { | 
|  | 854 | struct sdio_uart_port *port = tty->driver_data; | 
|  | 855 |  | 
|  | 856 | if (!I_IXOFF(tty) && !(tty->termios->c_cflag & CRTSCTS)) | 
|  | 857 | return; | 
|  | 858 |  | 
|  | 859 | if (sdio_uart_claim_func(port) != 0) | 
|  | 860 | return; | 
|  | 861 |  | 
|  | 862 | if (I_IXOFF(tty)) { | 
|  | 863 | port->x_char = STOP_CHAR(tty); | 
|  | 864 | sdio_uart_start_tx(port); | 
|  | 865 | } | 
|  | 866 |  | 
|  | 867 | if (tty->termios->c_cflag & CRTSCTS) | 
|  | 868 | sdio_uart_clear_mctrl(port, TIOCM_RTS); | 
|  | 869 |  | 
|  | 870 | sdio_uart_irq(port->func); | 
|  | 871 | sdio_uart_release_func(port); | 
|  | 872 | } | 
|  | 873 |  | 
|  | 874 | static void sdio_uart_unthrottle(struct tty_struct *tty) | 
|  | 875 | { | 
|  | 876 | struct sdio_uart_port *port = tty->driver_data; | 
|  | 877 |  | 
|  | 878 | if (!I_IXOFF(tty) && !(tty->termios->c_cflag & CRTSCTS)) | 
|  | 879 | return; | 
|  | 880 |  | 
|  | 881 | if (sdio_uart_claim_func(port) != 0) | 
|  | 882 | return; | 
|  | 883 |  | 
|  | 884 | if (I_IXOFF(tty)) { | 
|  | 885 | if (port->x_char) { | 
|  | 886 | port->x_char = 0; | 
|  | 887 | } else { | 
|  | 888 | port->x_char = START_CHAR(tty); | 
|  | 889 | sdio_uart_start_tx(port); | 
|  | 890 | } | 
|  | 891 | } | 
|  | 892 |  | 
|  | 893 | if (tty->termios->c_cflag & CRTSCTS) | 
|  | 894 | sdio_uart_set_mctrl(port, TIOCM_RTS); | 
|  | 895 |  | 
|  | 896 | sdio_uart_irq(port->func); | 
|  | 897 | sdio_uart_release_func(port); | 
|  | 898 | } | 
|  | 899 |  | 
| Alan Cox | c271cf3 | 2009-11-30 13:16:25 +0000 | [diff] [blame] | 900 | static void sdio_uart_set_termios(struct tty_struct *tty, | 
|  | 901 | struct ktermios *old_termios) | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 902 | { | 
|  | 903 | struct sdio_uart_port *port = tty->driver_data; | 
|  | 904 | unsigned int cflag = tty->termios->c_cflag; | 
|  | 905 |  | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 906 | if (sdio_uart_claim_func(port) != 0) | 
|  | 907 | return; | 
|  | 908 |  | 
|  | 909 | sdio_uart_change_speed(port, tty->termios, old_termios); | 
|  | 910 |  | 
|  | 911 | /* Handle transition to B0 status */ | 
|  | 912 | if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) | 
|  | 913 | sdio_uart_clear_mctrl(port, TIOCM_RTS | TIOCM_DTR); | 
|  | 914 |  | 
|  | 915 | /* Handle transition away from B0 status */ | 
|  | 916 | if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) { | 
|  | 917 | unsigned int mask = TIOCM_DTR; | 
|  | 918 | if (!(cflag & CRTSCTS) || !test_bit(TTY_THROTTLED, &tty->flags)) | 
|  | 919 | mask |= TIOCM_RTS; | 
|  | 920 | sdio_uart_set_mctrl(port, mask); | 
|  | 921 | } | 
|  | 922 |  | 
|  | 923 | /* Handle turning off CRTSCTS */ | 
|  | 924 | if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) { | 
|  | 925 | tty->hw_stopped = 0; | 
|  | 926 | sdio_uart_start_tx(port); | 
|  | 927 | } | 
|  | 928 |  | 
|  | 929 | /* Handle turning on CRTSCTS */ | 
|  | 930 | if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) { | 
|  | 931 | if (!(sdio_uart_get_mctrl(port) & TIOCM_CTS)) { | 
|  | 932 | tty->hw_stopped = 1; | 
|  | 933 | sdio_uart_stop_tx(port); | 
|  | 934 | } | 
|  | 935 | } | 
|  | 936 |  | 
|  | 937 | sdio_uart_release_func(port); | 
|  | 938 | } | 
|  | 939 |  | 
| David Howells | c43d863 | 2008-07-10 12:28:48 +0100 | [diff] [blame] | 940 | static int sdio_uart_break_ctl(struct tty_struct *tty, int break_state) | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 941 | { | 
|  | 942 | struct sdio_uart_port *port = tty->driver_data; | 
| David Howells | c43d863 | 2008-07-10 12:28:48 +0100 | [diff] [blame] | 943 | int result; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 944 |  | 
| David Howells | c43d863 | 2008-07-10 12:28:48 +0100 | [diff] [blame] | 945 | result = sdio_uart_claim_func(port); | 
|  | 946 | if (result != 0) | 
|  | 947 | return result; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 948 |  | 
|  | 949 | if (break_state == -1) | 
|  | 950 | port->lcr |= UART_LCR_SBC; | 
|  | 951 | else | 
|  | 952 | port->lcr &= ~UART_LCR_SBC; | 
|  | 953 | sdio_out(port, UART_LCR, port->lcr); | 
|  | 954 |  | 
|  | 955 | sdio_uart_release_func(port); | 
| David Howells | c43d863 | 2008-07-10 12:28:48 +0100 | [diff] [blame] | 956 | return 0; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 957 | } | 
|  | 958 |  | 
|  | 959 | static int sdio_uart_tiocmget(struct tty_struct *tty, struct file *file) | 
|  | 960 | { | 
|  | 961 | struct sdio_uart_port *port = tty->driver_data; | 
|  | 962 | int result; | 
|  | 963 |  | 
|  | 964 | result = sdio_uart_claim_func(port); | 
|  | 965 | if (!result) { | 
|  | 966 | result = port->mctrl | sdio_uart_get_mctrl(port); | 
|  | 967 | sdio_uart_release_func(port); | 
|  | 968 | } | 
|  | 969 |  | 
|  | 970 | return result; | 
|  | 971 | } | 
|  | 972 |  | 
|  | 973 | static int sdio_uart_tiocmset(struct tty_struct *tty, struct file *file, | 
|  | 974 | unsigned int set, unsigned int clear) | 
|  | 975 | { | 
|  | 976 | struct sdio_uart_port *port = tty->driver_data; | 
|  | 977 | int result; | 
|  | 978 |  | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 979 | result = sdio_uart_claim_func(port); | 
| Alan Cox | c271cf3 | 2009-11-30 13:16:25 +0000 | [diff] [blame] | 980 | if (!result) { | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 981 | sdio_uart_update_mctrl(port, set, clear); | 
|  | 982 | sdio_uart_release_func(port); | 
|  | 983 | } | 
|  | 984 |  | 
|  | 985 | return result; | 
|  | 986 | } | 
|  | 987 |  | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 988 | static int sdio_uart_proc_show(struct seq_file *m, void *v) | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 989 | { | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 990 | int i; | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 991 |  | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 992 | seq_printf(m, "serinfo:1.0 driver%s%s revision:%s\n", | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 993 | "", "", ""); | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 994 | for (i = 0; i < UART_NR; i++) { | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 995 | struct sdio_uart_port *port = sdio_uart_port_get(i); | 
|  | 996 | if (port) { | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 997 | seq_printf(m, "%d: uart:SDIO", i); | 
| Alan Cox | c271cf3 | 2009-11-30 13:16:25 +0000 | [diff] [blame] | 998 | if (capable(CAP_SYS_ADMIN)) { | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 999 | seq_printf(m, " tx:%d rx:%d", | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 1000 | port->icount.tx, port->icount.rx); | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1001 | if (port->icount.frame) | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1002 | seq_printf(m, " fe:%d", | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 1003 | port->icount.frame); | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1004 | if (port->icount.parity) | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1005 | seq_printf(m, " pe:%d", | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 1006 | port->icount.parity); | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1007 | if (port->icount.brk) | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1008 | seq_printf(m, " brk:%d", | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 1009 | port->icount.brk); | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1010 | if (port->icount.overrun) | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1011 | seq_printf(m, " oe:%d", | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 1012 | port->icount.overrun); | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1013 | if (port->icount.cts) | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1014 | seq_printf(m, " cts:%d", | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 1015 | port->icount.cts); | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1016 | if (port->icount.dsr) | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1017 | seq_printf(m, " dsr:%d", | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 1018 | port->icount.dsr); | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1019 | if (port->icount.rng) | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1020 | seq_printf(m, " rng:%d", | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 1021 | port->icount.rng); | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1022 | if (port->icount.dcd) | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1023 | seq_printf(m, " dcd:%d", | 
| Thadeu Lima de Souza Cascardo | 1e04b7a | 2009-10-15 16:44:00 -0300 | [diff] [blame] | 1024 | port->icount.dcd); | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1025 | } | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1026 | sdio_uart_port_put(port); | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1027 | seq_putc(m, '\n'); | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1028 | } | 
|  | 1029 | } | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1030 | return 0; | 
| Nicolas Pitre | 5ed334a | 2007-07-04 23:40:34 -0400 | [diff] [blame] | 1031 | } | 
|  | 1032 |  | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1033 | static int sdio_uart_proc_open(struct inode *inode, struct file *file) | 
|  | 1034 | { | 
|  | 1035 | return single_open(file, sdio_uart_proc_show, NULL); | 
|  | 1036 | } | 
|  | 1037 |  | 
|  | 1038 | static const struct file_operations sdio_uart_proc_fops = { | 
|  | 1039 | .owner		= THIS_MODULE, | 
|  | 1040 | .open		= sdio_uart_proc_open, | 
|  | 1041 | .read		= seq_read, | 
|  | 1042 | .llseek		= seq_lseek, | 
|  | 1043 | .release	= single_release, | 
|  | 1044 | }; | 
|  | 1045 |  | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 1046 | static const struct tty_port_operations sdio_uart_port_ops = { | 
|  | 1047 | .dtr_rts = uart_dtr_rts, | 
| Alan Cox | 4b3b49b | 2009-11-30 13:16:36 +0000 | [diff] [blame] | 1048 | .carrier_raised = uart_carrier_raised, | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 1049 | .shutdown = sdio_uart_shutdown, | 
|  | 1050 | .activate = sdio_uart_activate, | 
|  | 1051 | }; | 
|  | 1052 |  | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 1053 | static const struct tty_operations sdio_uart_ops = { | 
|  | 1054 | .open			= sdio_uart_open, | 
|  | 1055 | .close			= sdio_uart_close, | 
|  | 1056 | .write			= sdio_uart_write, | 
|  | 1057 | .write_room		= sdio_uart_write_room, | 
|  | 1058 | .chars_in_buffer	= sdio_uart_chars_in_buffer, | 
|  | 1059 | .send_xchar		= sdio_uart_send_xchar, | 
|  | 1060 | .throttle		= sdio_uart_throttle, | 
|  | 1061 | .unthrottle		= sdio_uart_unthrottle, | 
|  | 1062 | .set_termios		= sdio_uart_set_termios, | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 1063 | .hangup			= sdio_uart_hangup, | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 1064 | .break_ctl		= sdio_uart_break_ctl, | 
|  | 1065 | .tiocmget		= sdio_uart_tiocmget, | 
|  | 1066 | .tiocmset		= sdio_uart_tiocmset, | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 1067 | .install		= sdio_uart_install, | 
|  | 1068 | .cleanup		= sdio_uart_cleanup, | 
| Alexey Dobriyan | 201a50b | 2009-03-31 15:19:20 -0700 | [diff] [blame] | 1069 | .proc_fops		= &sdio_uart_proc_fops, | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 1070 | }; | 
|  | 1071 |  | 
|  | 1072 | static struct tty_driver *sdio_uart_tty_driver; | 
|  | 1073 |  | 
|  | 1074 | static int sdio_uart_probe(struct sdio_func *func, | 
|  | 1075 | const struct sdio_device_id *id) | 
|  | 1076 | { | 
|  | 1077 | struct sdio_uart_port *port; | 
|  | 1078 | int ret; | 
|  | 1079 |  | 
|  | 1080 | port = kzalloc(sizeof(struct sdio_uart_port), GFP_KERNEL); | 
|  | 1081 | if (!port) | 
|  | 1082 | return -ENOMEM; | 
|  | 1083 |  | 
|  | 1084 | if (func->class == SDIO_CLASS_UART) { | 
|  | 1085 | printk(KERN_WARNING "%s: need info on UART class basic setup\n", | 
|  | 1086 | sdio_func_id(func)); | 
|  | 1087 | kfree(port); | 
|  | 1088 | return -ENOSYS; | 
|  | 1089 | } else if (func->class == SDIO_CLASS_GPS) { | 
|  | 1090 | /* | 
|  | 1091 | * We need tuple 0x91.  It contains SUBTPL_SIOREG | 
|  | 1092 | * and SUBTPL_RCVCAPS. | 
|  | 1093 | */ | 
|  | 1094 | struct sdio_func_tuple *tpl; | 
|  | 1095 | for (tpl = func->tuples; tpl; tpl = tpl->next) { | 
|  | 1096 | if (tpl->code != 0x91) | 
|  | 1097 | continue; | 
|  | 1098 | if (tpl->size < 10) | 
|  | 1099 | continue; | 
|  | 1100 | if (tpl->data[1] == 0)  /* SUBTPL_SIOREG */ | 
|  | 1101 | break; | 
|  | 1102 | } | 
|  | 1103 | if (!tpl) { | 
|  | 1104 | printk(KERN_WARNING | 
| Alan Cox | c271cf3 | 2009-11-30 13:16:25 +0000 | [diff] [blame] | 1105 | "%s: can't find tuple 0x91 subtuple 0 (SUBTPL_SIOREG) for GPS class\n", | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 1106 | sdio_func_id(func)); | 
|  | 1107 | kfree(port); | 
|  | 1108 | return -EINVAL; | 
|  | 1109 | } | 
|  | 1110 | printk(KERN_DEBUG "%s: Register ID = 0x%02x, Exp ID = 0x%02x\n", | 
|  | 1111 | sdio_func_id(func), tpl->data[2], tpl->data[3]); | 
|  | 1112 | port->regs_offset = (tpl->data[4] << 0) | | 
|  | 1113 | (tpl->data[5] << 8) | | 
|  | 1114 | (tpl->data[6] << 16); | 
|  | 1115 | printk(KERN_DEBUG "%s: regs offset = 0x%x\n", | 
|  | 1116 | sdio_func_id(func), port->regs_offset); | 
|  | 1117 | port->uartclk = tpl->data[7] * 115200; | 
|  | 1118 | if (port->uartclk == 0) | 
|  | 1119 | port->uartclk = 115200; | 
|  | 1120 | printk(KERN_DEBUG "%s: clk %d baudcode %u 4800-div %u\n", | 
|  | 1121 | sdio_func_id(func), port->uartclk, | 
|  | 1122 | tpl->data[7], tpl->data[8] | (tpl->data[9] << 8)); | 
|  | 1123 | } else { | 
|  | 1124 | kfree(port); | 
|  | 1125 | return -EINVAL; | 
|  | 1126 | } | 
|  | 1127 |  | 
|  | 1128 | port->func = func; | 
|  | 1129 | sdio_set_drvdata(func, port); | 
| Alan Cox | b5849b1 | 2009-11-05 13:28:06 +0000 | [diff] [blame] | 1130 | tty_port_init(&port->port); | 
| Alan Cox | 584abc3 | 2009-11-30 13:16:09 +0000 | [diff] [blame] | 1131 | port->port.ops = &sdio_uart_port_ops; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 1132 |  | 
|  | 1133 | ret = sdio_uart_add_port(port); | 
|  | 1134 | if (ret) { | 
|  | 1135 | kfree(port); | 
|  | 1136 | } else { | 
|  | 1137 | struct device *dev; | 
| Alan Cox | c271cf3 | 2009-11-30 13:16:25 +0000 | [diff] [blame] | 1138 | dev = tty_register_device(sdio_uart_tty_driver, | 
|  | 1139 | port->index, &func->dev); | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 1140 | if (IS_ERR(dev)) { | 
|  | 1141 | sdio_uart_port_remove(port); | 
|  | 1142 | ret = PTR_ERR(dev); | 
|  | 1143 | } | 
|  | 1144 | } | 
|  | 1145 |  | 
|  | 1146 | return ret; | 
|  | 1147 | } | 
|  | 1148 |  | 
|  | 1149 | static void sdio_uart_remove(struct sdio_func *func) | 
|  | 1150 | { | 
|  | 1151 | struct sdio_uart_port *port = sdio_get_drvdata(func); | 
|  | 1152 |  | 
|  | 1153 | tty_unregister_device(sdio_uart_tty_driver, port->index); | 
|  | 1154 | sdio_uart_port_remove(port); | 
|  | 1155 | } | 
|  | 1156 |  | 
|  | 1157 | static const struct sdio_device_id sdio_uart_ids[] = { | 
|  | 1158 | { SDIO_DEVICE_CLASS(SDIO_CLASS_UART)		}, | 
|  | 1159 | { SDIO_DEVICE_CLASS(SDIO_CLASS_GPS)		}, | 
|  | 1160 | { /* end: all zeroes */				}, | 
|  | 1161 | }; | 
|  | 1162 |  | 
|  | 1163 | MODULE_DEVICE_TABLE(sdio, sdio_uart_ids); | 
|  | 1164 |  | 
|  | 1165 | static struct sdio_driver sdio_uart_driver = { | 
|  | 1166 | .probe		= sdio_uart_probe, | 
|  | 1167 | .remove		= sdio_uart_remove, | 
|  | 1168 | .name		= "sdio_uart", | 
|  | 1169 | .id_table	= sdio_uart_ids, | 
|  | 1170 | }; | 
|  | 1171 |  | 
|  | 1172 | static int __init sdio_uart_init(void) | 
|  | 1173 | { | 
|  | 1174 | int ret; | 
|  | 1175 | struct tty_driver *tty_drv; | 
|  | 1176 |  | 
|  | 1177 | sdio_uart_tty_driver = tty_drv = alloc_tty_driver(UART_NR); | 
|  | 1178 | if (!tty_drv) | 
|  | 1179 | return -ENOMEM; | 
|  | 1180 |  | 
|  | 1181 | tty_drv->owner = THIS_MODULE; | 
|  | 1182 | tty_drv->driver_name = "sdio_uart"; | 
|  | 1183 | tty_drv->name =   "ttySDIO"; | 
|  | 1184 | tty_drv->major = 0;  /* dynamically allocated */ | 
|  | 1185 | tty_drv->minor_start = 0; | 
|  | 1186 | tty_drv->type = TTY_DRIVER_TYPE_SERIAL; | 
|  | 1187 | tty_drv->subtype = SERIAL_TYPE_NORMAL; | 
|  | 1188 | tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; | 
|  | 1189 | tty_drv->init_termios = tty_std_termios; | 
|  | 1190 | tty_drv->init_termios.c_cflag = B4800 | CS8 | CREAD | HUPCL | CLOCAL; | 
| Nicolas Pitre | 2ba30ee | 2007-08-15 13:27:29 -0400 | [diff] [blame] | 1191 | tty_drv->init_termios.c_ispeed = 4800; | 
|  | 1192 | tty_drv->init_termios.c_ospeed = 4800; | 
| Nicolas Pitre | 6e418a9 | 2007-06-30 02:04:21 -0400 | [diff] [blame] | 1193 | tty_set_operations(tty_drv, &sdio_uart_ops); | 
|  | 1194 |  | 
|  | 1195 | ret = tty_register_driver(tty_drv); | 
|  | 1196 | if (ret) | 
|  | 1197 | goto err1; | 
|  | 1198 |  | 
|  | 1199 | ret = sdio_register_driver(&sdio_uart_driver); | 
|  | 1200 | if (ret) | 
|  | 1201 | goto err2; | 
|  | 1202 |  | 
|  | 1203 | return 0; | 
|  | 1204 |  | 
|  | 1205 | err2: | 
|  | 1206 | tty_unregister_driver(tty_drv); | 
|  | 1207 | err1: | 
|  | 1208 | put_tty_driver(tty_drv); | 
|  | 1209 | return ret; | 
|  | 1210 | } | 
|  | 1211 |  | 
|  | 1212 | static void __exit sdio_uart_exit(void) | 
|  | 1213 | { | 
|  | 1214 | sdio_unregister_driver(&sdio_uart_driver); | 
|  | 1215 | tty_unregister_driver(sdio_uart_tty_driver); | 
|  | 1216 | put_tty_driver(sdio_uart_tty_driver); | 
|  | 1217 | } | 
|  | 1218 |  | 
|  | 1219 | module_init(sdio_uart_init); | 
|  | 1220 | module_exit(sdio_uart_exit); | 
|  | 1221 |  | 
|  | 1222 | MODULE_AUTHOR("Nicolas Pitre"); | 
|  | 1223 | MODULE_LICENSE("GPL"); |