/* * u_sdio.c - utilities for USB gadget serial over sdio * * This code also borrows from drivers/usb/gadget/u_serial.c, which is * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) * Copyright (C) 2008 David Brownell * Copyright (C) 2008 by Nokia Corporation * Copyright (c) 2011, The Linux Foundation. All rights reserved. * * This program from The Linux Foundation is free software; you can * redistribute it and/or modify it under the GNU General Public License * version 2 and only version 2 as published by the Free Software Foundation. * The original work available from [kernel.org] is subject to the notice below. * * This software is distributed under the terms of the GNU General * Public License ("GPL") as published by the Free Software Foundation, * either version 2 of that License or (at your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include "u_serial.h" #define SDIO_RX_QUEUE_SIZE 8 #define SDIO_RX_BUF_SIZE 2048 #define SDIO_TX_QUEUE_SIZE 8 #define SDIO_TX_BUF_SIZE 2048 /* 1 - DUN, 2-NMEA/GPS */ #define SDIO_N_PORTS 2 static struct sdio_portmaster { struct mutex lock; struct gsdio_port *port; struct platform_driver gsdio_ch; } sdio_ports[SDIO_N_PORTS]; static unsigned n_sdio_ports; struct sdio_port_info { /* data channel info */ char *data_ch_name; struct sdio_channel *ch; /* control channel info */ int ctrl_ch_id; }; struct sdio_port_info sport_info[SDIO_N_PORTS] = { { .data_ch_name = "SDIO_DUN", .ctrl_ch_id = 9, }, { .data_ch_name = "SDIO_NMEA", .ctrl_ch_id = 10, }, }; static struct workqueue_struct *gsdio_wq; struct gsdio_port { unsigned port_num; spinlock_t port_lock; unsigned n_read; struct list_head read_pool; struct list_head read_queue; struct work_struct push; unsigned long rp_len; unsigned long rq_len; struct list_head write_pool; struct work_struct pull; unsigned long wp_len; struct work_struct notify_modem; struct gserial *port_usb; struct usb_cdc_line_coding line_coding; int sdio_open; int sdio_probe; int ctrl_ch_err; struct sdio_port_info *sport_info; struct delayed_work sdio_open_work; #define SDIO_ACM_CTRL_RI (1 << 3) #define SDIO_ACM_CTRL_DSR (1 << 1) #define SDIO_ACM_CTRL_DCD (1 << 0) int cbits_to_laptop; #define SDIO_ACM_CTRL_RTS (1 << 1) /* unused with full duplex */ #define SDIO_ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */ int cbits_to_modem; /* pkt logging */ unsigned long nbytes_tolaptop; unsigned long nbytes_tomodem; }; void gsdio_free_req(struct usb_ep *ep, struct usb_request *req) { kfree(req->buf); usb_ep_free_request(ep, req); } struct usb_request * gsdio_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags) { struct usb_request *req; req = usb_ep_alloc_request(ep, flags); if (!req) { pr_err("%s: usb alloc request failed\n", __func__); return NULL; } req->length = len; req->buf = kmalloc(len, flags); if (!req->buf) { pr_err("%s: request buf allocation failed\n", __func__); usb_ep_free_request(ep, req); return NULL; } return req; } void gsdio_free_requests(struct usb_ep *ep, struct list_head *head) { struct usb_request *req; while (!list_empty(head)) { req = list_entry(head->next, struct usb_request, list); list_del(&req->list); gsdio_free_req(ep, req); } } int gsdio_alloc_requests(struct usb_ep *ep, struct list_head *head, int num, int size, void (*cb)(struct usb_ep *ep, struct usb_request *)) { int i; struct usb_request *req; pr_debug("%s: ep:%p head:%p num:%d size:%d cb:%p", __func__, ep, head, num, size, cb); for (i = 0; i < num; i++) { req = gsdio_alloc_req(ep, size, GFP_ATOMIC); if (!req) { pr_debug("%s: req allocated:%d\n", __func__, i); return list_empty(head) ? -ENOMEM : 0; } req->complete = cb; list_add(&req->list, head); } return 0; } void gsdio_start_rx(struct gsdio_port *port) { struct list_head *pool; struct usb_ep *out; int ret; if (!port) { pr_err("%s: port is null\n", __func__); return; } pr_debug("%s: port:%p port#%d\n", __func__, port, port->port_num); spin_lock_irq(&port->port_lock); if (!port->port_usb) { pr_debug("%s: usb is disconnected\n", __func__); goto start_rx_end; } if (!port->sdio_open) { pr_debug("%s: sdio is not open\n", __func__); goto start_rx_end; } pool = &port->read_pool; out = port->port_usb->out; while (!list_empty(pool)) { struct usb_request *req; req = list_entry(pool->next, struct usb_request, list); list_del(&req->list); req->length = SDIO_RX_BUF_SIZE; port->rp_len--; spin_unlock_irq(&port->port_lock); ret = usb_ep_queue(out, req, GFP_ATOMIC); spin_lock_irq(&port->port_lock); if (ret) { pr_err("%s: usb ep out queue failed" "port:%p, port#%d\n", __func__, port, port->port_num); list_add_tail(&req->list, pool); port->rp_len++; break; } /* usb could have disconnected while we released spin lock */ if (!port->port_usb) { pr_debug("%s: usb is disconnected\n", __func__); goto start_rx_end; } } start_rx_end: spin_unlock_irq(&port->port_lock); } int gsdio_write(struct gsdio_port *port, struct usb_request *req) { unsigned avail; char *packet; unsigned size; unsigned n; int ret = 0; if (!port) { pr_err("%s: port is null\n", __func__); return -ENODEV; } if (!req) { pr_err("%s: usb request is null port#%d\n", __func__, port->port_num); return -ENODEV; } pr_debug("%s: port:%p port#%d req:%p actual:%d n_read:%d\n", __func__, port, port->port_num, req, req->actual, port->n_read); if (!port->sdio_open) { pr_debug("%s: SDIO IO is not supported\n", __func__); return -ENODEV; } avail = sdio_write_avail(port->sport_info->ch); pr_debug("%s: sdio_write_avail:%d", __func__, avail); if (!avail) return -EBUSY; if (!req->actual) { pr_debug("%s: req->actual is already zero,update bytes read\n", __func__); port->n_read = 0; return -ENODEV; } size = req->actual; packet = req->buf; n = port->n_read; if (n) { packet += n; size -= n; } if (size > avail) size = avail; spin_unlock_irq(&port->port_lock); ret = sdio_write(port->sport_info->ch, packet, size); spin_lock_irq(&port->port_lock); if (ret) { pr_err("%s: port#%d sdio write failed err:%d", __func__, port->port_num, ret); /* try again later */ return ret; } port->nbytes_tomodem += size; if (size + n == req->actual) port->n_read = 0; else port->n_read += size; return ret; } void gsdio_rx_push(struct work_struct *w) { struct gsdio_port *port = container_of(w, struct gsdio_port, push); struct list_head *q = &port->read_queue; struct usb_ep *out; int ret; pr_debug("%s: port:%p port#%d read_queue:%p", __func__, port, port->port_num, q); spin_lock_irq(&port->port_lock); if (!port->port_usb) { pr_debug("%s: usb cable is disconencted\n", __func__); spin_unlock_irq(&port->port_lock); return; } out = port->port_usb->out; while (!list_empty(q)) { struct usb_request *req; req = list_first_entry(q, struct usb_request, list); switch (req->status) { case -ESHUTDOWN: pr_debug("%s: req status shutdown portno#%d port:%p", __func__, port->port_num, port); goto rx_push_end; default: pr_warning("%s: port:%p port#%d" " Unexpected Rx Status:%d\n", __func__, port, port->port_num, req->status); /* FALL THROUGH */ case 0: /* normal completion */ break; } if (!port->sdio_open) { pr_err("%s: sio channel is not open\n", __func__); list_move(&req->list, &port->read_pool); port->rp_len++; port->rq_len--; goto rx_push_end; } list_del(&req->list); port->rq_len--; ret = gsdio_write(port, req); /* as gsdio_write drops spin_lock while writing data * to sdio usb cable may have been disconnected */ if (!port->port_usb) { port->n_read = 0; gsdio_free_req(out, req); spin_unlock_irq(&port->port_lock); return; } if (ret || port->n_read) { list_add(&req->list, &port->read_queue); port->rq_len++; goto rx_push_end; } list_add(&req->list, &port->read_pool); port->rp_len++; } if (port->sdio_open && !list_empty(q)) { if (sdio_write_avail(port->sport_info->ch)) queue_work(gsdio_wq, &port->push); } rx_push_end: spin_unlock_irq(&port->port_lock); /* start queuing out requests again to host */ gsdio_start_rx(port); } void gsdio_read_complete(struct usb_ep *ep, struct usb_request *req) { struct gsdio_port *port = ep->driver_data; unsigned long flags; pr_debug("%s: ep:%p port:%p\n", __func__, ep, port); if (!port) { pr_err("%s: port is null\n", __func__); return; } spin_lock_irqsave(&port->port_lock, flags); list_add_tail(&req->list, &port->read_queue); port->rq_len++; queue_work(gsdio_wq, &port->push); spin_unlock_irqrestore(&port->port_lock, flags); return; } void gsdio_write_complete(struct usb_ep *ep, struct usb_request *req) { struct gsdio_port *port = ep->driver_data; unsigned long flags; pr_debug("%s: ep:%p port:%p\n", __func__, ep, port); if (!port) { pr_err("%s: port is null\n", __func__); return; } spin_lock_irqsave(&port->port_lock, flags); list_add(&req->list, &port->write_pool); port->wp_len++; switch (req->status) { default: pr_warning("%s: port:%p port#%d unexpected %s status %d\n", __func__, port, port->port_num, ep->name, req->status); /* FALL THROUGH */ case 0: queue_work(gsdio_wq, &port->pull); break; case -ESHUTDOWN: /* disconnect */ pr_debug("%s: %s shutdown\n", __func__, ep->name); break; } spin_unlock_irqrestore(&port->port_lock, flags); return; } void gsdio_read_pending(struct gsdio_port *port) { struct sdio_channel *ch; char buf[1024]; int avail; if (!port) { pr_err("%s: port is null\n", __func__); return; } ch = port->sport_info->ch; if (!ch) return; while ((avail = sdio_read_avail(ch))) { if (avail > 1024) avail = 1024; sdio_read(ch, buf, avail); pr_debug("%s: flushed out %d bytes\n", __func__, avail); } } void gsdio_tx_pull(struct work_struct *w) { struct gsdio_port *port = container_of(w, struct gsdio_port, pull); struct list_head *pool = &port->write_pool; pr_debug("%s: port:%p port#%d pool:%p\n", __func__, port, port->port_num, pool); if (!port->port_usb) { pr_err("%s: usb disconnected\n", __func__); /* take out all the pending data from sdio */ gsdio_read_pending(port); return; } spin_lock_irq(&port->port_lock); while (!list_empty(pool)) { int avail; struct usb_ep *in = port->port_usb->in; struct sdio_channel *ch = port->sport_info->ch; struct usb_request *req; unsigned len = SDIO_TX_BUF_SIZE; int ret; req = list_entry(pool->next, struct usb_request, list); if (!port->sdio_open) { pr_debug("%s: SDIO channel is not open\n", __func__); goto tx_pull_end; } avail = sdio_read_avail(ch); if (!avail) { /* REVISIT: for ZLP */ pr_debug("%s: read_avail:%d port:%p port#%d\n", __func__, avail, port, port->port_num); goto tx_pull_end; } if (avail > len) avail = len; list_del(&req->list); port->wp_len--; spin_unlock_irq(&port->port_lock); ret = sdio_read(ch, req->buf, avail); spin_lock_irq(&port->port_lock); if (ret) { pr_err("%s: port:%p port#%d sdio read failed err:%d", __func__, port, port->port_num, ret); /* check if usb is still active */ if (!port->port_usb) { gsdio_free_req(in, req); } else { list_add(&req->list, pool); port->wp_len++; } goto tx_pull_end; } req->length = avail; spin_unlock_irq(&port->port_lock); ret = usb_ep_queue(in, req, GFP_KERNEL); spin_lock_irq(&port->port_lock); if (ret) { pr_err("%s: usb ep out queue failed" "port:%p, port#%d err:%d\n", __func__, port, port->port_num, ret); /* could be usb disconnected */ if (!port->port_usb) { gsdio_free_req(in, req); } else { list_add(&req->list, pool); port->wp_len++; } goto tx_pull_end; } port->nbytes_tolaptop += avail; } tx_pull_end: spin_unlock_irq(&port->port_lock); } int gsdio_start_io(struct gsdio_port *port) { int ret; unsigned long flags; pr_debug("%s:\n", __func__); spin_lock_irqsave(&port->port_lock, flags); if (!port->port_usb) { spin_unlock_irqrestore(&port->port_lock, flags); return -ENODEV; } /* start usb out queue */ ret = gsdio_alloc_requests(port->port_usb->out, &port->read_pool, SDIO_RX_QUEUE_SIZE, SDIO_RX_BUF_SIZE, gsdio_read_complete); if (ret) { spin_unlock_irqrestore(&port->port_lock, flags); pr_err("%s: unable to allocate out reqs\n", __func__); return ret; } port->rp_len = SDIO_RX_QUEUE_SIZE; ret = gsdio_alloc_requests(port->port_usb->in, &port->write_pool, SDIO_TX_QUEUE_SIZE, SDIO_TX_BUF_SIZE, gsdio_write_complete); if (ret) { gsdio_free_requests(port->port_usb->out, &port->read_pool); port->rp_len = 0; spin_unlock_irqrestore(&port->port_lock, flags); pr_err("%s: unable to allocate in reqs\n", __func__); return ret; } port->wp_len = SDIO_TX_QUEUE_SIZE; spin_unlock_irqrestore(&port->port_lock, flags); gsdio_start_rx(port); queue_work(gsdio_wq, &port->pull); return 0; } void gsdio_port_free(unsigned portno) { struct gsdio_port *port = sdio_ports[portno].port; struct platform_driver *pdriver = &sdio_ports[portno].gsdio_ch; if (!port) { pr_err("%s: invalid portno#%d\n", __func__, portno); return; } platform_driver_unregister(pdriver); kfree(port); } void gsdio_ctrl_wq(struct work_struct *w) { struct gsdio_port *port; port = container_of(w, struct gsdio_port, notify_modem); if (!port) { pr_err("%s: port is null\n", __func__); return; } if (!port->sdio_open || port->ctrl_ch_err) return; sdio_cmux_tiocmset(port->sport_info->ctrl_ch_id, port->cbits_to_modem, ~(port->cbits_to_modem)); } void gsdio_ctrl_notify_modem(void *gptr, u8 portno, int ctrl_bits) { struct gsdio_port *port; int temp; struct gserial *gser = gptr; if (portno >= n_sdio_ports) { pr_err("%s: invalid portno#%d\n", __func__, portno); return; } if (!gser) { pr_err("%s: gser is null\n", __func__); return; } port = sdio_ports[portno].port; temp = ctrl_bits & SDIO_ACM_CTRL_DTR ? TIOCM_DTR : 0; if (port->cbits_to_modem == temp) return; port->cbits_to_modem = temp; /* TIOCM_DTR - 0x002 - bit(1) */ pr_debug("%s: port:%p port#%d ctrl_bits:%08x\n", __func__, port, port->port_num, ctrl_bits); if (!port->sdio_open) { pr_err("%s: port:%p port#%d sdio not connected\n", __func__, port, port->port_num); return; } /* whenever DTR is high let laptop know that modem status */ if (port->cbits_to_modem && gser->send_modem_ctrl_bits) gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop); queue_work(gsdio_wq, &port->notify_modem); } void gsdio_ctrl_modem_status(int ctrl_bits, void *_dev) { struct gsdio_port *port = _dev; /* TIOCM_CD - 0x040 - bit(6) * TIOCM_RI - 0x080 - bit(7) * TIOCM_DSR- 0x100 - bit(8) */ pr_debug("%s: port:%p port#%d event:%08x\n", __func__, port, port->port_num, ctrl_bits); port->cbits_to_laptop = 0; ctrl_bits &= TIOCM_RI | TIOCM_CD | TIOCM_DSR; if (ctrl_bits & TIOCM_RI) port->cbits_to_laptop |= SDIO_ACM_CTRL_RI; if (ctrl_bits & TIOCM_CD) port->cbits_to_laptop |= SDIO_ACM_CTRL_DCD; if (ctrl_bits & TIOCM_DSR) port->cbits_to_laptop |= SDIO_ACM_CTRL_DSR; if (port->port_usb && port->port_usb->send_modem_ctrl_bits) port->port_usb->send_modem_ctrl_bits(port->port_usb, port->cbits_to_laptop); } void gsdio_ch_notify(void *_dev, unsigned event) { struct gsdio_port *port = _dev; pr_debug("%s: port:%p port#%d event:%s\n", __func__, port, port->port_num, event == 1 ? "READ AVAIL" : "WRITE_AVAIL"); if (event == SDIO_EVENT_DATA_WRITE_AVAIL) queue_work(gsdio_wq, &port->push); if (event == SDIO_EVENT_DATA_READ_AVAIL) queue_work(gsdio_wq, &port->pull); } static void gsdio_open_work(struct work_struct *w) { struct gsdio_port *port = container_of(w, struct gsdio_port, sdio_open_work.work); struct sdio_port_info *pi = port->sport_info; struct gserial *gser; int ret; int ctrl_bits; int startio; ret = sdio_open(pi->data_ch_name, &pi->ch, port, gsdio_ch_notify); if (ret) { pr_err("%s: port:%p port#%d unable to open sdio ch:%s\n", __func__, port, port->port_num, pi->data_ch_name); return; } port->ctrl_ch_err = 0; ret = sdio_cmux_open(pi->ctrl_ch_id, 0, 0, gsdio_ctrl_modem_status, port); if (ret) { pr_err("%s: port:%p port#%d unable to open ctrl ch:%d\n", __func__, port, port->port_num, pi->ctrl_ch_id); port->ctrl_ch_err = 1; } /* check for latest status update from modem */ if (!port->ctrl_ch_err) { ctrl_bits = sdio_cmux_tiocmget(pi->ctrl_ch_id); gsdio_ctrl_modem_status(ctrl_bits, port); } pr_debug("%s: SDIO data:%s ctrl:%d are open\n", __func__, pi->data_ch_name, pi->ctrl_ch_id); port->sdio_open = 1; /* start tx if usb is open already */ spin_lock_irq(&port->port_lock); startio = port->port_usb ? 1 : 0; gser = port->port_usb; spin_unlock_irq(&port->port_lock); if (startio) { pr_debug("%s: USB is already open, start io\n", __func__); gsdio_start_io(port); if (gser->send_modem_ctrl_bits) gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop); } } #define SDIO_CH_NAME_MAX_LEN 9 #define SDIO_OPEN_DELAY msecs_to_jiffies(10000) static int gsdio_ch_remove(struct platform_device *dev) { struct gsdio_port *port; struct sdio_port_info *pi; int i; unsigned long flags; pr_debug("%s: name:%s\n", __func__, dev->name); for (i = 0; i < n_sdio_ports; i++) { port = sdio_ports[i].port; pi = port->sport_info; if (!strncmp(pi->data_ch_name, dev->name, SDIO_CH_NAME_MAX_LEN)) { struct gserial *gser = port->port_usb; port->sdio_open = 0; port->sdio_probe = 0; port->ctrl_ch_err = 1; /* check if usb cable is connected */ if (!gser) continue; /* indicated call status to usb host */ gsdio_ctrl_modem_status(0, port); usb_ep_fifo_flush(gser->in); usb_ep_fifo_flush(gser->out); cancel_work_sync(&port->push); cancel_work_sync(&port->pull); spin_lock_irqsave(&port->port_lock, flags); gsdio_free_requests(gser->out, &port->read_pool); gsdio_free_requests(gser->out, &port->read_queue); gsdio_free_requests(gser->in, &port->write_pool); port->rp_len = 0; port->rq_len = 0; port->wp_len = 0; port->n_read = 0; spin_unlock_irqrestore(&port->port_lock, flags); } } return 0; } static int gsdio_ch_probe(struct platform_device *dev) { struct gsdio_port *port; struct sdio_port_info *pi; int i; pr_debug("%s: name:%s\n", __func__, dev->name); for (i = 0; i < n_sdio_ports; i++) { port = sdio_ports[i].port; pi = port->sport_info; pr_debug("%s: sdio_ch_name:%s dev_name:%s\n", __func__, pi->data_ch_name, dev->name); /* unfortunately cmux channle might not be ready even if * sdio channel is ready. as we dont have good notification * mechanism schedule a delayed work */ if (!strncmp(pi->data_ch_name, dev->name, SDIO_CH_NAME_MAX_LEN)) { port->sdio_probe = 1; queue_delayed_work(gsdio_wq, &port->sdio_open_work, SDIO_OPEN_DELAY); return 0; } } pr_info("%s: name:%s is not found\n", __func__, dev->name); return -ENODEV; } int gsdio_port_alloc(unsigned portno, struct usb_cdc_line_coding *coding, struct sdio_port_info *pi) { struct gsdio_port *port; struct platform_driver *pdriver; port = kzalloc(sizeof(struct gsdio_port), GFP_KERNEL); if (!port) { pr_err("%s: port allocation failed\n", __func__); return -ENOMEM; } port->port_num = portno; spin_lock_init(&port->port_lock); port->line_coding = *coding; /* READ: read from usb and write into sdio */ INIT_LIST_HEAD(&port->read_pool); INIT_LIST_HEAD(&port->read_queue); INIT_WORK(&port->push, gsdio_rx_push); INIT_LIST_HEAD(&port->write_pool); INIT_WORK(&port->pull, gsdio_tx_pull); INIT_WORK(&port->notify_modem, gsdio_ctrl_wq); INIT_DELAYED_WORK(&port->sdio_open_work, gsdio_open_work); sdio_ports[portno].port = port; port->sport_info = pi; pdriver = &sdio_ports[portno].gsdio_ch; pdriver->probe = gsdio_ch_probe; pdriver->remove = gsdio_ch_remove; pdriver->driver.name = pi->data_ch_name; pdriver->driver.owner = THIS_MODULE; pr_debug("%s: port:%p port#%d sdio_name: %s\n", __func__, port, port->port_num, pi->data_ch_name); platform_driver_register(pdriver); pr_debug("%s: port:%p port#%d\n", __func__, port, port->port_num); return 0; } int gsdio_connect(struct gserial *gser, u8 portno) { struct gsdio_port *port; int ret = 0; unsigned long flags; if (portno >= n_sdio_ports) { pr_err("%s: invalid portno#%d\n", __func__, portno); return -EINVAL; } if (!gser) { pr_err("%s: gser is null\n", __func__); return -EINVAL; } port = sdio_ports[portno].port; spin_lock_irqsave(&port->port_lock, flags); port->port_usb = gser; gser->notify_modem = gsdio_ctrl_notify_modem; spin_unlock_irqrestore(&port->port_lock, flags); ret = usb_ep_enable(gser->in); if (ret) { pr_err("%s: failed to enable in ep w/ err:%d\n", __func__, ret); port->port_usb = 0; return ret; } gser->in->driver_data = port; ret = usb_ep_enable(gser->out); if (ret) { pr_err("%s: failed to enable in ep w/ err:%d\n", __func__, ret); usb_ep_disable(gser->in); port->port_usb = 0; gser->in->driver_data = 0; return ret; } gser->out->driver_data = port; if (port->sdio_open) { pr_debug("%s: sdio is already open, start io\n", __func__); gsdio_start_io(port); if (gser->send_modem_ctrl_bits) gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop); } return 0; } void gsdio_disconnect(struct gserial *gser, u8 portno) { unsigned long flags; struct gsdio_port *port; if (portno >= n_sdio_ports) { pr_err("%s: invalid portno#%d\n", __func__, portno); return; } if (!gser) { pr_err("%s: gser is null\n", __func__); return; } port = sdio_ports[portno].port; /* send dtr zero to modem to notify disconnect */ port->cbits_to_modem = 0; queue_work(gsdio_wq, &port->notify_modem); spin_lock_irqsave(&port->port_lock, flags); port->port_usb = 0; port->nbytes_tomodem = 0; port->nbytes_tolaptop = 0; spin_unlock_irqrestore(&port->port_lock, flags); /* disable endpoints, aborting down any active I/O */ usb_ep_disable(gser->out); gser->out->driver_data = NULL; usb_ep_disable(gser->in); gser->in->driver_data = NULL; spin_lock_irqsave(&port->port_lock, flags); gsdio_free_requests(gser->out, &port->read_pool); gsdio_free_requests(gser->out, &port->read_queue); gsdio_free_requests(gser->in, &port->write_pool); port->rp_len = 0; port->rq_len = 0; port->wp_len = 0; port->n_read = 0; spin_unlock_irqrestore(&port->port_lock, flags); } #if defined(CONFIG_DEBUG_FS) static char debug_buffer[PAGE_SIZE]; static ssize_t debug_sdio_read_stats(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { struct gsdio_port *port; char *buf; unsigned long flags; int i = 0; int temp = 0; int ret; buf = kzalloc(sizeof(char) * 1024, GFP_KERNEL); if (!buf) return -ENOMEM; while (i < n_sdio_ports) { port = sdio_ports[i].port; spin_lock_irqsave(&port->port_lock, flags); temp += scnprintf(buf + temp, PAGE_SIZE - temp, "###PORT:%d port:%p###\n" "nbytes_tolaptop: %lu\n" "nbytes_tomodem: %lu\n" "cbits_to_modem: %u\n" "cbits_to_laptop: %u\n" "read_pool_len: %lu\n" "read_queue_len: %lu\n" "write_pool_len: %lu\n" "n_read: %u\n" "sdio_open: %d\n" "sdio_probe: %d\n", i, port, port->nbytes_tolaptop, port->nbytes_tomodem, port->cbits_to_modem, port->cbits_to_laptop, port->rp_len, port->rq_len, port->wp_len, port->n_read, port->sdio_open, port->sdio_probe); spin_unlock_irqrestore(&port->port_lock, flags); i++; } ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); kfree(buf); return ret; } static ssize_t debug_sdio_reset_stats(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct gsdio_port *port; unsigned long flags; int i = 0; while (i < n_sdio_ports) { port = sdio_ports[i].port; spin_lock_irqsave(&port->port_lock, flags); port->nbytes_tolaptop = 0; port->nbytes_tomodem = 0; spin_unlock_irqrestore(&port->port_lock, flags); i++; } return count; } static int debug_sdio_open(struct inode *inode, struct file *file) { return 0; } static const struct file_operations debug_gsdio_ops = { .open = debug_sdio_open, .read = debug_sdio_read_stats, .write = debug_sdio_reset_stats, }; static void gsdio_debugfs_init(void) { struct dentry *dent; dent = debugfs_create_dir("usb_gsdio", 0); if (IS_ERR(dent)) return; debugfs_create_file("status", 0444, dent, 0, &debug_gsdio_ops); } #else static void gsdio_debugfs_init(void) { return; } #endif /* connect, disconnect, alloc_requests, free_requests */ int gsdio_setup(struct usb_gadget *g, unsigned count) { struct usb_cdc_line_coding coding; int i; int ret = 0; pr_debug("%s: gadget:(%p) count:%d\n", __func__, g, count); if (count == 0 || count > SDIO_N_PORTS) { pr_err("%s: invalid number of ports count:%d max_ports:%d\n", __func__, count, SDIO_N_PORTS); return -EINVAL; } coding.dwDTERate = cpu_to_le32(9600); coding.bCharFormat = 8; coding.bParityType = USB_CDC_NO_PARITY; coding.bDataBits = USB_CDC_1_STOP_BITS; gsdio_wq = create_singlethread_workqueue("k_gserial"); if (!gsdio_wq) { pr_err("%s: unable to create workqueue gsdio_wq\n", __func__); return -ENOMEM; } for (i = 0; i < count; i++) { mutex_init(&sdio_ports[i].lock); ret = gsdio_port_alloc(i, &coding, sport_info + i); n_sdio_ports++; if (ret) { n_sdio_ports--; pr_err("%s: sdio logical port allocation failed\n", __func__); goto free_sdio_ports; } } gsdio_debugfs_init(); return 0; free_sdio_ports: for (i = 0; i < n_sdio_ports; i++) gsdio_port_free(i); destroy_workqueue(gsdio_wq); return ret; } /* TODO: Add gserial_cleanup */