static int spi_tty_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear) { unsigned long flags; struct spi_tty_s *spi_tty = tty->driver_data; spin_lock_irqsave(&spi_tty->port_lock, flags); if (set & TIOCM_DTR) { SPI_IPC_INFO("set DTR\n"); spi_tty->dtr = 1; } if (clear & TIOCM_DTR) { SPI_IPC_INFO("clear DTR\n"); spi_tty->dtr = 0; } spi_tty->tx_null = 1; spin_unlock_irqrestore(&spi_tty->port_lock, flags); queue_work(spi_tty->work_queue, &spi_tty->write_work); return 0; }
int spi_tty_write_cache(struct tty_struct *tty, const unsigned char *buffer, int count) { unsigned long flags; int ret; struct spi_tty_s *spi_tty = tty->driver_data; spin_lock_irqsave(&spi_tty->port_lock, flags); if (spi_tty->throttle == 1) { // SPI is symmetrical bus, so stop write means stop receive ret = 0; }else{ if (!in_interrupt() && spi_tty_buf_room_avail(spi_tty->write_buf) < count) { // no enough room, wait spin_unlock_irqrestore(&spi_tty->port_lock, flags); SPI_IPC_INFO("No write room, put write wait...\n"); spi_tty->write_buf_full = 1; wait_event_interruptible_timeout(spi_tty->write_wait, spi_tty->write_buf_full == 0, 2*HZ); SPI_IPC_INFO("Write wait up from sleep\n"); spin_lock_irqsave(&spi_tty->port_lock, flags); } ret = spi_tty_buf_put(spi_tty->write_buf, buffer, count); } // wake lock to prevent suspend wake_lock_timeout(&spi_tty->wakelock, SPI_TTY_WAKE_LOCK_TIMEOUT); spin_unlock_irqrestore(&spi_tty->port_lock, flags); return ret; }
static int __devinit mdm6600_spi_probe(struct spi_device *spi) { struct mdm6600_spi_platform_data *plat; struct mdm6600_spi_device *spi_dev = &mdm6600_spi_dev; plat = (struct mdm6600_spi_platform_data *)spi->dev.platform_data; SPI_IPC_INFO("%s, spi dev=%p, srdy=%d, mrdy=%d\n", __func__, spi, plat->gpio_srdy, plat->gpio_mrdy); spi->dev.platform_data = spi_dev; spi_dev->srdy_gpio = plat->gpio_srdy; spi_dev->mrdy_gpio = plat->gpio_mrdy; spi_dev->spi = spi; spi_dev->spi_tty.spi = spi; mdm6600_spi_slave_config_gpio(spi_dev); #ifdef CONFIG_MDM_CTRL if (plat->peer_register) plat->peer_register(mdm6600_spi_handle_peer_startup, mdm6600_spi_handle_peer_shutdown, &mdm6600_spi_dev); #endif return spi_tty_register_device(&spi_dev->spi_tty); }
static int spi_tty_write(struct tty_struct *tty, const unsigned char *buffer, int count) { unsigned long flags; struct spi_tty_s *spi_tty = tty->driver_data; int retval = 0; if (!spi_tty || spi_tty->spi_slave_dev->peer_is_dead) return -ENODEV; //spi_slave_dump_hex(buffer, count); if (count > SPI_MTU) { pr_err("%s: data length over SPI MTU!\n", __func__); return 0; } spin_lock_irqsave(&spi_tty->port_lock, flags); if (!spi_tty->open_count) { pr_err("%s: device not opened!\n", __func__); spin_unlock_irqrestore(&spi_tty->port_lock, flags); return -EIO; } spin_unlock_irqrestore(&spi_tty->port_lock, flags); retval = spi_tty_write_cache(tty, buffer, count); write_count++; queue_work(spi_tty->work_queue, &spi_tty->write_work); SPI_IPC_INFO("%s exit, retval=%d\n", __func__, retval); return retval; }
static void spi_tty_close(struct tty_struct *tty, struct file *file) { struct spi_tty_s *spi_tty = tty->driver_data; SPI_IPC_INFO("%s\n", __func__); if (spi_tty) do_close(spi_tty); }
static void spi_tty_handle_data(void *context, u8 *buf, u32 count) { int cnt, crc, len; struct spi_tty_s *spi_tty; spi_msg_header header; SPI_IPC_INFO("%s, context=%p, buf=%p, count=%d\n", __func__, context, buf, count); if (!context || !buf || !count) { pr_err("%s - Invalid param!\n", __func__); return; } spi_ipc_buf_dump("rx header: ", buf, SPI_MSG_HEADER_LEN); spi_tty = (struct spi_tty_s *) context; spi_msg_get_header(buf, &header); crc = spi_msg_cal_crc(&header); // validate data if (header.type != 1 || header.len > SPI_MTU || header.fcs != crc) { pr_err("Invalid data receicved!\n"); return; } if (spi_tty->open_count > 0 && header.len > 0) { len = header.len; spi_ipc_buf_dump_ascii("rx data: ", buf+SPI_MSG_HEADER_LEN, (len>16?16:len)); SPI_IPC_INFO("insert data to tty\n"); buf += SPI_MSG_HEADER_LEN; do { cnt = tty_buffer_request_room(spi_tty->tty, len); if (cnt == 0) break; cnt = tty_insert_flip_string(spi_tty->tty, buf, cnt); tty_flip_buffer_push(spi_tty->tty); len -= cnt; buf += cnt; }while(len > 0); } }
static int spi_tty_write_room(struct tty_struct *tty) { unsigned long flags; struct spi_tty_s *spi_tty = tty->driver_data; int room = 0; SPI_IPC_INFO("%s\n", __func__); spin_lock_irqsave(&spi_tty->port_lock, flags); if (spi_tty->open_count) { if (spi_tty->throttle == 1) room = 0; else room = spi_tty_buf_room_avail(spi_tty->write_buf); } spin_unlock_irqrestore(&spi_tty->port_lock, flags); SPI_IPC_INFO("room=%d\n", room); return room; }
/* It's safe to call this before or after spi_tty_init() */ int spi_tty_register_device(struct spi_tty_device *device) { if (spi_tty_dev) return -EBUSY; /* Populate the caller's callbacks */ device->callback_context = spi_tty_gbl; device->data_callback = spi_tty_handle_data; device->mrdy_callback = spi_tty_handle_mrdy; spi_tty_dev = device; SPI_IPC_INFO("%s: registered device 0x%p\n", __func__, device); return 0; }
static void do_close(struct spi_tty_s *spi_tty) { unsigned long flags; SPI_IPC_INFO("%s\n", __func__); spin_lock_irqsave(&spi_tty->port_lock, flags); if (!spi_tty->open_count) { goto exit; } --spi_tty->open_count; if (spi_tty->open_count <= 0) { /* The port is being closed by the last user. */ spi_tty->tty = NULL; } exit: spin_unlock_irqrestore(&spi_tty->port_lock, flags); }
void spi_ipc_buf_dump1(const char *header, const u8 *buf, int len, int in_ascii) { int i; int c; u8 dbg_buf[256]; if (len <= 0) return; for (i = 0, c = 0; (i < len) && (c < (256 - 3)); i++) { if (in_ascii && isprint(buf[i])) { sprintf(&dbg_buf[c], "%c ", buf[i]); c += 2; }else{ sprintf(&dbg_buf[c], "%02x ", buf[i]); c += 3; } } dbg_buf[c] = 0; SPI_IPC_INFO("%s%s\n", header, dbg_buf); }
// Our API will be called by PPP from irq, so that only spinlock can be used static int spi_tty_open(struct tty_struct *tty, struct file *file) { unsigned long flags; int index; struct spi_tty_s *spi_tty; SPI_IPC_INFO("%s, tty=%p\n", __func__, tty); spi_tty = spi_tty_gbl; spin_lock_irqsave(&spi_tty->port_lock, flags); index = tty->index; tty->driver_data = spi_tty; tty->low_latency = 1; spi_tty->tty = tty; ++spi_tty->open_count; spin_unlock_irqrestore(&spi_tty->port_lock, flags); return 0; }
static int mdm6600_spi_suspend(struct spi_device *dev, pm_message_t mesg) { SPI_IPC_INFO("%s\n", __func__); return 0; }
static int __init spi_tty_init(void) { int retval; struct spi_tty_s *spi_tty; SPI_IPC_INFO("%s\n", __func__); tx_size = 0L; tx_time = 0L; tx_count = 0L; write_count = 0L; spi_tty_gbl = kmalloc(sizeof(*spi_tty), GFP_KERNEL); if (spi_tty_gbl == NULL) { pr_err("%s: Cannot malloc mem!\n", __func__); return -ENOMEM; } memset(spi_tty_gbl, 0x0, sizeof(*spi_tty)); spi_tty = spi_tty_gbl; SPI_IPC_INFO("spi_tty=%p\n", spi_tty); spi_tty->write_buf = spi_tty_buf_alloc(); if (!spi_tty->write_buf) { kfree(spi_tty_gbl); pr_err("failed to malloc spi_tty write buf!\n"); return -ENOMEM; } spi_tty->throttle = 0; spi_tty->open_count = 0; spi_tty->tx_null = 0; spin_lock_init(&spi_tty->port_lock); mutex_init(&spi_tty->work_lock); INIT_WORK(&spi_tty->write_work, spi_tty_write_worker); wake_lock_init(&spi_tty->wakelock, WAKE_LOCK_SUSPEND, "spi_tty_wakelock"); init_waitqueue_head(&spi_tty->write_wait); spi_tty->write_buf_full = 0; spi_tty->work_queue = create_singlethread_workqueue("spi_tty_wq"); if (spi_tty->work_queue == NULL) { kfree(spi_tty); kfree(spi_big_trans.tx_buf); pr_err("Failed to create work queue\n"); return -ESRCH; } spi_slave_message_init(&spi_big_msg); spi_big_trans.tx_buf = kmalloc(SPI_TRANSACTION_LEN*2, GFP_KERNEL); if (!spi_big_trans.tx_buf) { kfree(spi_tty); pr_err("%s: Cannot malloc mem!\n", __func__); return -ENOMEM; } spi_big_trans.rx_buf = spi_big_trans.tx_buf + SPI_TRANSACTION_LEN; spi_slave_message_add_tail(&spi_big_trans, &spi_big_msg); spi_tty_driver = alloc_tty_driver(SPI_TTY_MINORS); if (!spi_tty_driver) return -ENOMEM; spi_tty_driver->owner = THIS_MODULE; spi_tty_driver->driver_name = "spi_modem"; spi_tty_driver->name = "ttySPI"; spi_tty_driver->major = SPI_TTY_MAJOR; spi_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; spi_tty_driver->subtype = SERIAL_TYPE_NORMAL; spi_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; spi_tty_driver->init_termios = tty_std_termios; spi_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; tty_set_operations(spi_tty_driver, &serial_ops); retval = tty_register_driver(spi_tty_driver); if (retval) { pr_err("failed to register spi_tty tty driver"); put_tty_driver(spi_tty_driver); return retval; } tty_register_device(spi_tty_driver, 0, NULL); // Depends on module_init() call sequence, mdm6600_dev_probe may mdm6600_spi_dev.cb_context = spi_tty; //mdm6600_spi_dev.callback = spi_tty_handle_data; mdm6600_spi_dev.callback = NULL; mdm6600_spi_dev.handle_master_mrdy = spi_tty_handle_mrdy; spi_tty->spi_slave_dev = &mdm6600_spi_dev; return retval; }
void spi_tty_write_worker(struct work_struct *work) { int c; int crc; unsigned long flags; unsigned long start_t = 0; spi_msg_header header; struct spi_tty_s *spi_tty = container_of(work, struct spi_tty_s, write_work); start_t = jiffies; SPI_IPC_INFO("%s Enter\n", __func__); mutex_lock(&(spi_tty->work_lock)); spin_lock_irqsave(&(spi_tty->port_lock), flags); while( ((c = spi_tty_buf_data_avail(spi_tty->write_buf)) || (spi_tty->tx_null)) && (!spi_tty->throttle) && (!spi_tty->spi_slave_dev->peer_is_dead)) { if (spi_tty->tx_null) spi_tty->tx_null = 0; // initiate spi_big_trans memset(spi_big_trans.tx_buf, 0x0, SPI_TRANSACTION_LEN*2); spi_big_msg.actual_length = 0; c = MIN(c, SPI_MTU); spi_tty_buf_get(spi_tty->write_buf, spi_big_trans.tx_buf+SPI_MSG_HEADER_LEN, c); if (spi_tty->tty && spi_tty->open_count) tty_wakeup(spi_tty->tty); spin_unlock_irqrestore(&(spi_tty->port_lock), flags); header.type = 1; header.len = c; header.dtr = spi_tty->dtr; crc = spi_msg_cal_crc(&header); header.fcs = crc; spi_msg_set_header(spi_big_trans.tx_buf, &header); spi_big_trans.len = c + SPI_MSG_HEADER_LEN; spi_ipc_buf_dump("tx header: ", spi_big_trans.tx_buf, SPI_MSG_HEADER_LEN); spi_ipc_buf_dump_ascii("tx data: ", spi_big_trans.tx_buf + SPI_MSG_HEADER_LEN, (c>16?16:c)); spi_slave_sync(mdm6600_spi_dev.spi, &spi_big_msg); if (spi_big_msg.actual_length == SPI_TRANSACTION_LEN) { tx_count++; tx_size += spi_big_trans.len - SPI_MSG_HEADER_LEN; spi_tty_handle_data(spi_tty, spi_big_trans.rx_buf, SPI_TRANSACTION_LEN); }else { pr_err("%s: spi_slave_sync() failed to transfer data!\n", __func__); } // wake up writes wait on queue wake_up_interruptible(&spi_tty->write_wait); spin_lock_irqsave(&(spi_tty->port_lock), flags); } spin_unlock_irqrestore(&(spi_tty->port_lock), flags); mutex_unlock(&(spi_tty->work_lock)); SPI_IPC_INFO("%s Exit\n", __func__); tx_time += jiffies_to_msecs(jiffies - start_t); }
static int mdm6600_spi_resume(struct spi_device *dev) { SPI_IPC_INFO("%s\n", __func__); return 0; }
static int __init mdm6600_spi_init(void) { SPI_IPC_INFO("%s\n", __func__); return spi_register_driver(&mdm6600_spi_driver); }
static void spi_tty_write_worker(struct work_struct *work) { int c; int crc; unsigned long flags; unsigned long start_t = 0; spi_msg_header header; struct spi_tty_s *spi_tty = container_of(work, struct spi_tty_s, write_work); start_t = jiffies; SPI_IPC_INFO("%s\n", __func__); mutex_lock(&(spi_tty->work_lock)); spin_lock_irqsave(&(spi_tty->port_lock), flags); while(((c = spi_tty_buf_data_avail(spi_tty->write_buf)) || (spi_tty->tx_null)) && (!spi_tty->throttle) && (!(spi_tty_dev && spi_tty_dev->peer_is_dead))) { SPI_IPC_INFO("%s: %d outgoing bytes\n", __func__, c); if (spi_tty->tx_null) spi_tty->tx_null = 0; // initiate spi_big_trans memset((char*)spi_big_trans.tx_buf, 0x0, SPI_TRANSACTION_LEN * 2); spi_big_msg.actual_length = 0; c = MIN(c, SPI_MTU); spi_tty_buf_get(spi_tty->write_buf, (char*)spi_big_trans.tx_buf + SPI_MSG_HEADER_LEN, c); if (spi_tty->tty && spi_tty->open_count) tty_wakeup(spi_tty->tty); spin_unlock_irqrestore(&(spi_tty->port_lock), flags); header.type = 1; header.len = c; header.dtr = spi_tty->dtr; crc = spi_msg_cal_crc(&header); header.fcs = crc; spi_msg_set_header((u8*)spi_big_trans.tx_buf, &header); #if SPI_TTY_FORCE_FULL_TRANSACTION spi_big_trans.len = SPI_TRANSACTION_LEN; #else spi_big_trans.len = c + SPI_MSG_HEADER_LEN; #endif spi_big_trans.speed_hz = SPI_SPEED_HZ; spi_big_trans.bits_per_word = 32; spi_ipc_buf_dump("tx header: ", spi_big_trans.tx_buf, SPI_MSG_HEADER_LEN); spi_ipc_buf_dump_ascii("tx data: ", spi_big_trans.tx_buf + SPI_MSG_HEADER_LEN, (c>16?16:c)); if (spi_tty_dev) spi_sync(spi_tty_dev->spi, &spi_big_msg); else pr_warning("%s: dropping data: no spi device " "registered", __func__); if (spi_big_msg.actual_length == SPI_TRANSACTION_LEN) { tx_count++; tx_size += spi_big_trans.len - SPI_MSG_HEADER_LEN; spi_tty_handle_data(spi_tty, spi_big_trans.rx_buf, SPI_TRANSACTION_LEN); }else { pr_err("%s: spi data transfer failed\n", __func__); } // wake up writes wait on queue wake_up_interruptible(&spi_tty->write_wait); spin_lock_irqsave(&(spi_tty->port_lock), flags); } spin_unlock_irqrestore(&(spi_tty->port_lock), flags); mutex_unlock(&(spi_tty->work_lock)); SPI_IPC_INFO("%s: done\n", __func__); tx_time += jiffies_to_msecs(jiffies - start_t); }