1161 lines
26 KiB
C
1161 lines
26 KiB
C
|
/*
|
||
|
* 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 <linux/kernel.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/termios.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
|
||
|
#include <mach/sdio_al.h>
|
||
|
#include <mach/sdio_cmux.h>
|
||
|
#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 */
|