Index: linux-2.6.33/drivers/spi/Kconfig =================================================================== --- linux-2.6.33.orig/drivers/spi/Kconfig +++ linux-2.6.33/drivers/spi/Kconfig @@ -335,6 +335,10 @@ config SPI_DW_PCI # comment "SPI Protocol Masters" +config SPI_MRST_GTM501 + tristate "SPI protocol driver for GTM501l" + depends on SPI_MRST + config SPI_SPIDEV tristate "User mode SPI device driver support" depends on EXPERIMENTAL Index: linux-2.6.33/drivers/spi/Makefile =================================================================== --- linux-2.6.33.orig/drivers/spi/Makefile +++ linux-2.6.33/drivers/spi/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_SPI_SH_MSIOF) += spi_sh_ms obj-$(CONFIG_SPI_STMP3XXX) += spi_stmp.o obj-$(CONFIG_SPI_NUC900) += spi_nuc900.o obj-$(CONFIG_SPI_MRST) += mrst_spi.o +obj-$(CONFIG_SPI_MRST_GTM501) += gtm501l_spi.o # special build for s3c24xx spi driver with fiq support spi_s3c24xx_hw-y := spi_s3c24xx.o Index: linux-2.6.33/drivers/spi/gtm501l_spi.c =================================================================== --- /dev/null +++ linux-2.6.33/drivers/spi/gtm501l_spi.c @@ -0,0 +1,2029 @@ +/**************************************************************************** + * + * Driver for the Option GTM501L spi modem. + * + * Copyright (C) 2008 Option International + * Copyright (C) 2008 Filip Aben + * Denis Joseph Barrow + * Jan Dumon + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA + * + * + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef CONFIG_DEBUG_FS +#include +#include +#include +#endif + +#include "gtm501l_spi.h" +#include + +/* various static variables */ +static struct tty_driver *tty_drv; +static struct ktermios *gtm501l_termios[GTM501L_MAX_MINORS]; +static struct ktermios *gtm501l_termios_locked[GTM501L_MAX_MINORS]; +static struct lock_class_key gtm501l_key; +static struct gtm501l_port_data *gtm501l_serial_ports[GTM501L_MAX_MINORS]; + +/* Default port spec */ +static struct gtm501l_port_spec gtm501l_default_port_spec[] = { + { 1, GTM501L_PORT_SPEC_SERIAL, "Control" }, /* 0 */ + { 1, GTM501L_PORT_SPEC_SERIAL, "NetAT" }, /* 1 */ + { 1, GTM501L_PORT_SPEC_NET, "NetIP" }, /* 2 */ + { 1, GTM501L_PORT_SPEC_SERIAL, "App" }, /* 3 */ + { 1, GTM501L_PORT_SPEC_SERIAL, "App2" }, /* 4 */ + { 1, GTM501L_PORT_SPEC_SERIAL, "PC/SC" }, /* 5 */ + { 1, GTM501L_PORT_SPEC_SERIAL, "Modem" }, /* 6 */ + { 1, GTM501L_PORT_SPEC_SERIAL, "Diag" }, /* 7 */ + { 1, GTM501L_PORT_SPEC_SERIAL, "Logger" }, /* 8 */ + { 0, GTM501L_PORT_SPEC_SERIAL, "" }, /* 9 */ + { 1, GTM501L_PORT_SPEC_NET, "NetIP2" }, /* 10 */ + { 0, GTM501L_PORT_SPEC_SERIAL, "" }, /* 11 */ + { 0, GTM501L_PORT_SPEC_SERIAL, "" }, /* 12 */ + { 0, GTM501L_PORT_SPEC_SERIAL, "" }, /* 13 */ + { 0, GTM501L_PORT_SPEC_SERIAL, "" }, /* 14 */ + { 0, GTM501L_PORT_SPEC_SERIAL, "" }, /* 15 */ +}; + +/* Module load parameter */ +static int gpio_in = -1; /* GPIO interrupt input assignment, set default to -1 */ +static int backoff_enabled = 1; /* Enable the backoff timer */ +static int spi_b16 = 1; /* Enable 16bit SPI word length, otherwise use 8bit SPI word length */ +int gtm501l_debug = 0; + +/* temp debug variables */ +static int spi_err_count = 0; +static unsigned int total_tty_write = 0; +static unsigned int total_spi_write = 0; + +static unsigned char scratch_buf[GTM501L_TRANSFER_SIZE]; + +static int reset_state = 1; + +/* prototype declarations */ +static void gtm501l_push_skb(struct gtm501l_port_data *port_data); +static void gtm501l_net_init(struct net_device *net); +static void gtm501l_spi_complete(void *ctx); +static void _gtm501l_set_termios(struct tty_struct *tty, struct ktermios *old); +static void gtm501l_throttle(struct tty_struct *tty); +static void gtm501l_create_ports(struct gtm501l_device *gtm_dev, + struct gtm501l_port_spec *specs); +/*static void gtm501l_pmic_init_voltages( void );*/ +static void gtm501l_pmic_set_wwanresetn( unsigned char level ); +static void gtm501l_pmic_set_wwandisablen( unsigned char level ); + +void gtm501l_debug_printk(const char *function, int line, char *format, ...) { + va_list args; + int len; + char buffer[255]; + + len = snprintf(buffer, 255, DRVNAME " [%s:%d] ", function, line); + + va_start(args, format); + vsnprintf(buffer + len, 255 - len, format, args); + va_end(args); + + printk("%s", buffer); +} + +#define __bswap_16(x) \ + (__extension__ \ + ({ register unsigned short int __v, __x = (x); \ + __asm__ ("rorw $8, %w0" \ + : "=r" (__v) \ + : "0" (__x) \ + : "cc"); \ + __v; })) + +static inline void swap_buf(u16 *buf, int len) { + int n; + len = (len + 1) / 2; + n = (len + 7) / 8; + switch (len % 8) { + case 0: do { *buf = __bswap_16(*buf); buf++; + case 7: *buf = __bswap_16(*buf); buf++; + case 6: *buf = __bswap_16(*buf); buf++; + case 5: *buf = __bswap_16(*buf); buf++; + case 4: *buf = __bswap_16(*buf); buf++; + case 3: *buf = __bswap_16(*buf); buf++; + case 2: *buf = __bswap_16(*buf); buf++; + case 1: *buf = __bswap_16(*buf); buf++; + } while (--n > 0); + } +} + +#ifdef CONFIG_DEBUG_FS + +static ssize_t gtm501l_read_gpio(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[32]; + unsigned int len = 0; + if (gpio_in >= 0) { + len += snprintf(buf+len, sizeof(buf)-len, + "GPIO%d = %d\n", gpio_in, (gpio_get_value(gpio_in))?1:0); + } else { + len += snprintf(buf+len, sizeof(buf)-len, + "GPIO unuse\n"); + } + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static struct file_operations gtm501l_gpio_operations = { + .owner = THIS_MODULE, + .read = gtm501l_read_gpio, +}; + +static ssize_t gtm501l_read_pmic(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[8]; + unsigned int len = sprintf(buf, "%d\n", reset_state); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t gtm501l_write_pmic(struct file *file, const char __user *user, size_t count, loff_t *offset) +{ + reset_state = simple_strtoul(user, NULL, 0); + gtm501l_pmic_set_wwanresetn( reset_state ); + gtm501l_pmic_set_wwandisablen( reset_state ); + return count; +} + +static struct file_operations gtm501l_pmic_operations = { + .owner = THIS_MODULE, + .read = gtm501l_read_pmic, + .write = gtm501l_write_pmic, +}; + +/** + * gtm501l_debugfs_init generates all debugfs file nodes. + * It initialized the frame statistic structure. + */ +static int gtm501l_debugfs_init(struct gtm501l_device *gtm_dev) +{ + gtm_dev->debugfs = debugfs_create_dir("gtm501l", NULL); + if (!gtm_dev->debugfs) + return -ENOMEM; + + debugfs_create_file("gpio", S_IFREG | S_IRUGO, + gtm_dev->debugfs, NULL, >m501l_gpio_operations); + debugfs_create_file("pmic", S_IFREG | S_IRUGO, + gtm_dev->debugfs, NULL, >m501l_pmic_operations); + debugfs_create_u32("backoff_timer", S_IFREG | S_IRUGO, + gtm_dev->debugfs, &backoff_enabled); + debugfs_create_x32("flags", S_IFREG | S_IRUGO, + gtm_dev->debugfs, (unsigned int *)>m_dev->flags); +#ifdef DEBUG + debugfs_create_x32("debug", S_IFREG | S_IRUGO, + gtm_dev->debugfs, >m501l_debug); +#endif + + return 0; +} + +static void gtm501l_debugfs_remove(struct gtm501l_device *gtm_dev) +{ + if (gtm_dev->debugfs) + debugfs_remove_recursive(gtm_dev->debugfs); +} +#endif + +static __be32 gtm501l_get_ip_addr(struct net_device *net) +{ + struct in_device *in_dev; + + if ((in_dev = __in_dev_get_rtnl(net)) != NULL) { + if(in_dev->ifa_list) { + return in_dev->ifa_list->ifa_local; + } + } + + return 0; +} + +/* +static void gtm501l_pmic_init_voltages( void ) +{ + struct ipc_pmic_reg_data reg_data; + + reg_data.num_entries = 3; + + // Set VDDQCNT + + reg_data.pmic_reg_data[0].register_address = 0x037; + reg_data.pmic_reg_data[0].value = 0x07; + + // Set YMX3GDCNT + + reg_data.pmic_reg_data[1].register_address = 0x03c; + reg_data.pmic_reg_data[1].value = 0x47; + + // Set CLKOUT + + reg_data.pmic_reg_data[2].register_address = 0x021; + reg_data.pmic_reg_data[2].value = 0x00; + ipc_pmic_register_write(®_data, 0); + +} +*/ + +static void gtm501l_pmic_set_wwanresetn( unsigned char level ) +{ + struct ipc_pmic_mod_reg_data mod_reg_data; + + // WWAN_RESET_N is connected to COMMS_GPO5 + + mod_reg_data.num_entries = 1; + mod_reg_data.pmic_mod_reg_data[0].bit_map = 0x20; + mod_reg_data.pmic_mod_reg_data[0].register_address = 0x0f4; + + if (level) + { + mod_reg_data.pmic_mod_reg_data[0].value = 0x20; + } + else + { + mod_reg_data.pmic_mod_reg_data[0].value = 0x00; + } + + ipc_pmic_register_read_modify(&mod_reg_data); +} + +static void gtm501l_pmic_set_wwandisablen( unsigned char level ) +{ + struct ipc_pmic_mod_reg_data mod_reg_data; + + // WWAN_DISABLE_N is connected to COMMS_GPO0 + + mod_reg_data.num_entries = 1; + mod_reg_data.pmic_mod_reg_data[0].bit_map = 0x01; + mod_reg_data.pmic_mod_reg_data[0].register_address = 0x0f4; + + if (level) + { + mod_reg_data.pmic_mod_reg_data[0].value = 0x01; + } + else + { + mod_reg_data.pmic_mod_reg_data[0].value = 0x00; + } + + ipc_pmic_register_read_modify(&mod_reg_data); +} + + +static void gtm501l_rx_netchar(struct gtm501l_net *gtm_net, + unsigned char *in_buf, int size) +{ + struct net_device *net = gtm_net->net; + unsigned int temp_bytes; + unsigned int buffer_offset = 0, n; + unsigned int frame_len; + unsigned char *tmp_rx_buf; + unsigned char c; + int header_invalid; + + while(size) { + switch (gtm_net->rx_state) { + case WAIT_IP: + /* waiting for IP header. */ + /* wanted bytes - size of ip header */ + temp_bytes = (size < gtm_net->rx_buf_missing) ? + size : gtm_net->rx_buf_missing; + + memcpy(((unsigned char *)(>m_net->rx_ip_hdr)) + + gtm_net->rx_buf_size, in_buf + buffer_offset, + temp_bytes); + + gtm_net->rx_buf_size += temp_bytes; + buffer_offset += temp_bytes; + gtm_net->rx_buf_missing -= temp_bytes; + size -= temp_bytes; + + if (!gtm_net->rx_buf_missing) { + /* header is complete allocate an sk_buffer and + * continue to WAIT_DATA */ + + header_invalid = 0; + + frame_len = ntohs(gtm_net->rx_ip_hdr.tot_len); + if ((frame_len > GTM501L_DEFAULT_MRU) || + (frame_len < sizeof(struct iphdr))) { + if(!gtm_net->sync_lost) + dev_err(&net->dev, + "Invalid frame length (%d)\n", + frame_len); + header_invalid = 1; + } + + /* Validate destination address */ + if (!header_invalid && + (gtm501l_get_ip_addr(net) != gtm_net->rx_ip_hdr.daddr)) { + if(!gtm_net->sync_lost) + dev_err(&net->dev, + "Not our address (" NIPQUAD_FMT ")\n", + NIPQUAD(gtm_net->rx_ip_hdr.daddr)); + header_invalid = 1; + } + + if (header_invalid) { + /* This header is not valid, roll back + * for sizeof(header) bytes - 1 and + * wait for sync */ + gtm_net->rx_state = WAIT_SYNC; + n = min(buffer_offset, + sizeof(struct iphdr) - 1); + buffer_offset -= n; + size += n; + continue; + } + + /* Allocate an sk_buff */ + gtm_net->rx_skb = dev_alloc_skb(frame_len); + if (!gtm_net->rx_skb) { + /* We got no receive buffer. */ + //D1("ddcould not allocate memory"); + gtm_net->rx_state = WAIT_SYNC; + return; + } + + if(gtm_net->sync_lost) { + dev_info(&net->dev, "Back in sync. (%d stray bytes)\n", + gtm_net->sync_lost); + gtm_net->sync_lost = 0; + } + + /* Here's where it came from */ + gtm_net->rx_skb->dev = net; + + /* Copy what we got so far. make room for iphdr + * after tail. */ + tmp_rx_buf = + skb_put(gtm_net->rx_skb, + sizeof(struct iphdr)); + memcpy(tmp_rx_buf, (char *)&(gtm_net->rx_ip_hdr), + sizeof(struct iphdr)); + + /* ETH_HLEN */ + gtm_net->rx_buf_size = sizeof(struct iphdr); + + gtm_net->rx_buf_missing = + frame_len - sizeof(struct iphdr); + gtm_net->rx_state = WAIT_DATA; + } + break; + + case WAIT_DATA: + temp_bytes = (size < gtm_net->rx_buf_missing) + ? size : gtm_net->rx_buf_missing; + + /* Copy the rest of the bytes that are left in the + * buffer into the waiting sk_buf. */ + /* Make room for temp_bytes after tail. */ + tmp_rx_buf = skb_put(gtm_net->rx_skb, temp_bytes); + memcpy(tmp_rx_buf, in_buf + buffer_offset, temp_bytes); + + gtm_net->rx_buf_missing -= temp_bytes; + size -= temp_bytes; + buffer_offset += temp_bytes; + gtm_net->rx_buf_size += temp_bytes; + if (!gtm_net->rx_buf_missing) { + /* Packet is complete. Inject into stack. */ + /* We have IP packet here */ + gtm_net->rx_skb->protocol = + __constant_htons(ETH_P_IP); + /* don't check it */ + gtm_net->rx_skb->ip_summed = + CHECKSUM_UNNECESSARY; + + skb_reset_mac_header(gtm_net->rx_skb); + + /* Ship it off to the kernel */ + netif_rx(gtm_net->rx_skb); + /* No longer our buffer. */ + gtm_net->rx_skb = NULL; + + /* update out statistics */ + STATS(net).rx_packets++; + STATS(net).rx_bytes += gtm_net->rx_buf_size; + + gtm_net->rx_buf_size = 0; + gtm_net->rx_buf_missing = sizeof(struct iphdr); + gtm_net->rx_state = WAIT_IP; + } + break; + + case WAIT_SYNC: + if(!gtm_net->sync_lost) { + dev_err(&net->dev, "Lost sync...\n"); + } + gtm_net->sync_lost++; + /* Look for the next possible IP header */ + c = *(in_buf + buffer_offset); + if(c >= 0x45 && c <= 0x4f) { + /* This might be the begin of a new ip pkt */ + gtm_net->rx_state = WAIT_IP; + gtm_net->rx_buf_size = 0; + gtm_net->rx_buf_missing = sizeof(struct iphdr); + } + else { + size--; + buffer_offset++; + } + break; + + default: + size--; + break; + } + } +} + +/* mux functions */ + +/* function that tells you how much characters you can feed to fill your mux buffer*/ +static inline int gtm501l_mux_to_demux(int count) +{ + int burst_cnt, remain_cnt; + if (count & 0x1) { + printk("Error - space in frame can't be an odd number\n"); + return -1; + } + /* We've got 2 extra bytes of framing per 512 bytes of data */ + burst_cnt = (count / (MUX_BURST_SIZE + 2)) * MUX_BURST_SIZE; + remain_cnt = count % (MUX_BURST_SIZE + 2); + + if (remain_cnt > 2) + return remain_cnt - 2 + burst_cnt; + else + return burst_cnt + 1; +} + +/* multiplexes the data into the output buffer. output buffer is expected to be large enough to fit the data. */ +static int gtm501l_mux_data(int chan_id, unsigned char *in_buf, int in_size, + unsigned char *out_buf) +{ + int odd, cnt, result_size = 0; + + /* check for an odd number of bytes */ + odd = in_size & 0x1; + + /* make it even */ + in_size &= ~1; + + /* First fill up with as much burst frames as possible */ + while (in_size) { + + cnt = (in_size >= MUX_BURST_SIZE) ? MUX_BURST_SIZE : in_size; + *(out_buf + result_size) = + MUX_CONTROL_BYTE(chan_id, MUX_BURST_TRANSFER, + MUX_MASTER_TO_SLAVE); + //printk("Burst frame %d bytes\n", cnt); + result_size++; + *(out_buf + result_size) = cnt / 2; + result_size++; + memcpy(out_buf + result_size, in_buf, cnt); + result_size += cnt; + in_size -= cnt; + in_buf += cnt; + } + + /* Then tackle the odd byte */ + if (odd) { + *(out_buf + result_size) = + MUX_CONTROL_BYTE(chan_id, MUX_DATA_TRANSFER, + MUX_MASTER_TO_SLAVE); + result_size++; + *(out_buf + result_size) = *in_buf; + result_size++; + + } + + return result_size; +} + +/* kfifo put theoretically cannot fail to copy all buffer here */ +void gtm501l_tty_insert_flip_string(struct gtm501l_serial *gtm_ser, + unsigned char *chars,size_t size) +{ + int chars_inserted; + int copylen; + struct tty_struct *tty; + + if (gtm_ser && gtm_ser->tty) { + tty= gtm_ser->tty; + if (test_bit(TTY_THROTTLED, &tty->flags)) { + dprintk(DEBUG_TTY, "received %d bytes while throttled\n", size); + copylen=kfifo_in(gtm_ser->throttle_fifo,chars,size); + if(copylen!=size) + dprintk(DEBUG_TTY, "kfifo_put failed got %d expected %d\n", + copylen, size); + } + else { + chars_inserted=tty_insert_flip_string(tty, chars, size); + if(chars_inserted!=size) { + + size -= chars_inserted; + copylen=kfifo_in(gtm_ser->throttle_fifo, + &chars[chars_inserted], + size); + if(copylen!=size) + dprintk(DEBUG_TTY, "%s kfifo_put failed got %d expected %d\n", + copylen, size); + } + /* The flip here should force the tty layer + * to throttle if neccessary + */ + tty_flip_buffer_push(tty); + } + } + +} + +#define PORT_SPEC_FLAGS_ENABLED 0x80 +#define PORT_SPEC_FLAGS_TYPE_MASK 0x7f +#define PORT_SPEC_FLAGS_SERIAL 0x01 +#define PORT_SPEC_FLAGS_NET 0x02 + +static void gtm501l_decode_version_info(struct gtm501l_device *gtm_dev, + unsigned char *buf, int size) +{ + int i = 0, chan, flags; + u16 version; + u16 framelength; + struct gtm501l_port_spec *new_port_spec; + + /* Protocol version */ + memcpy(&version, buf + i, 2); + i += 2; + + if(version != 0 || size != 260) { + /* Unknown version or size is wrong.. */ + return; + } + + /* Frame length */ + memcpy(&framelength, buf + i, 2); + i += 2; + + /* Channel descriptors */ + new_port_spec = kzalloc(sizeof(struct gtm501l_port_spec) * 16, GFP_KERNEL); + for(chan = 0; chan < 16; chan++) { + flags = buf[i++]; + + if(flags | PORT_SPEC_FLAGS_ENABLED) { + new_port_spec[chan].enabled = 1; + switch(flags & PORT_SPEC_FLAGS_TYPE_MASK) { + case PORT_SPEC_FLAGS_SERIAL: + new_port_spec[chan].type = GTM501L_PORT_SPEC_SERIAL; + break; + case PORT_SPEC_FLAGS_NET: + new_port_spec[chan].type = GTM501L_PORT_SPEC_NET; + break; + default: + /* Unknown channel type: disable this channel */ + new_port_spec[chan].enabled = 0; + } + + /* Copy the name */ + memcpy(&new_port_spec[chan].name, buf + i, 15); + } + + i += 15; + } + + /* Activate the new port spec */ + gtm501l_create_ports(gtm_dev, new_port_spec); + kfree(new_port_spec); +} + + +/** + * gtm501l_demux walks through the received SPI message given in in_buf with the length size and copies all + * found data into the opened channels given in the gtm_dev structure. The SPI buffer length must be always + * a multiple of 2 bytes! + * It returns finally the real found useful data inside the SPI frame length + */ +static int gtm501l_demux(struct gtm501l_device *gtm_dev, unsigned char *in_buf, + int size) +{ + int i = 0, valid = 0, copy; + unsigned char temp; + struct gtm501l_port_data *port_data; + struct gtm501l_serial *gtm_ser = NULL; + struct gtm501l_net *gtm_net = NULL; + unsigned char old_dcd; + + gtm_dev->empty_transfers++; + + while (i < size) { + gtm_ser = NULL; + gtm_net = NULL; + copy = 0; + + if(spi_b16) + swap_buf((u16 *)(in_buf + i), 2); + + /* check for an end of frame sequence */ + if((in_buf[i]==0) && (in_buf[i+1]==0)) break; + + if(0 && in_buf[i] == 0xFF) { /* TODO: Fill in correct check for version info */ + copy = in_buf[++i] * 2; + i++; + if(spi_b16) + swap_buf((u16 *)(in_buf + i), copy); + gtm501l_decode_version_info(gtm_dev, in_buf + i, copy); + i += copy; + continue; + } + + /* verify valid packet */ + temp = MUX_DEVICE(in_buf[i]); + if (temp != MUX_SLAVE_TO_MASTER) { + /* + * ##PH: That should never happen and should counted as errorness data + */ + i += 2; + continue; + } + +#ifdef DEBUG + if(!valid && (i > 0) && (gtm501l_debug & DEBUG_DEMUX)) { + int j; + dprintk(DEBUG_DEMUX, "First valid byte found at offset %d\n", i); + for(j=0 ; j= GTM501L_PORT_PER_DEV || !gtm_dev->port_data[temp]) { + i += 2; + continue; + } + port_data = gtm_dev->port_data[temp]; + + if (port_data->spec.type == GTM501L_PORT_SPEC_NET) + gtm_net = &port_data->type.net; + else if (port_data->spec.type == GTM501L_PORT_SPEC_SERIAL) + gtm_ser = &port_data->type.serial; + //dprintk(DEBUG_DEMUX, "For channel %d\n", temp); + + /* start decoding data */ + temp = MUX_BLOCK_TYPE(in_buf[i]); + if (temp == MUX_BURST_TRANSFER) { + copy = in_buf[++i] * 2; + if( 0 == copy ) copy = MUX_BURST_SIZE; + if(spi_b16) + swap_buf((u16 *)(in_buf + i + 1), copy); + } else if (temp == MUX_DATA_TRANSFER) { + copy = 1; + } + + if (copy) { + gtm_dev->empty_transfers = 0; + //dprintk(DEBUG_DEMUX, "\tNeed to copy %d data bytes\n", copy); + /* regular data */ + if( gtm_ser && gtm_ser->tty ) { + gtm501l_tty_insert_flip_string(gtm_ser, &in_buf[++i], copy); + } + else if (gtm_net) { +/* + int j; + for(j=i+1;j<(i+1+copy);j++) printk("0x%.2X ", in_buf[j]); + printk("\n"); +*/ + gtm501l_rx_netchar(gtm_net, &in_buf[++i], copy); + } else { + i++; + } + + i += copy; + continue; + } + + if (temp == MUX_CONTROL_TRANSFER) { + //dprintk(DEBUG_DEMUX, "Is a control byte\n"); + /* control data */ + temp = in_buf[i + 1]; + if (MUX_LINK(temp)) { + set_bit(GTM501L_TX_FC, + &port_data->signal_state); + dprintk(DEBUG_DEMUX, "FC set\n"); + } else { + clear_bit(GTM501L_TX_FC, + &port_data->signal_state); + dprintk(DEBUG_DEMUX, "FC cleared\n"); + } + + if (gtm_ser) { + old_dcd = + test_bit(GTM501L_DCD, + &port_data->signal_state); + + if (MUX_DCD(temp)) { + set_bit(GTM501L_DCD, + &port_data->signal_state); + dprintk(DEBUG_DEMUX, "DCD set\n"); + } else { + clear_bit(GTM501L_DCD, + &port_data->signal_state); + dprintk(DEBUG_DEMUX, "DCD cleared\n"); + } + if (MUX_CTS(temp)) { + set_bit(GTM501L_CTS, + &port_data->signal_state); + dprintk(DEBUG_DEMUX, "CTS set\n"); + } else { + clear_bit(GTM501L_CTS, + &port_data->signal_state); + dprintk(DEBUG_DEMUX, "CTS cleared\n"); + } + + if (MUX_DSR(temp)) { + set_bit(GTM501L_DSR, + &port_data->signal_state); + dprintk(DEBUG_DEMUX, "DSR set\n"); + } else { + clear_bit(GTM501L_DSR, + &port_data->signal_state); + dprintk(DEBUG_DEMUX, "DSR cleared\n"); + } + + if (MUX_RI(temp)) { + set_bit(GTM501L_RI, + &port_data->signal_state); + dprintk(DEBUG_DEMUX, "RI set\n"); + } else { + clear_bit(GTM501L_RI, + &port_data->signal_state); + dprintk(DEBUG_DEMUX, "RI cleared\n"); + } + + if (old_dcd + && !test_bit(GTM501L_DCD, + &port_data->signal_state)) + if (gtm_ser->tty) + tty_hangup(gtm_ser->tty); + } + + i += 2; + } + + } + return i; +} + +static int gtm501l_mux_flow_control(struct gtm501l_port_data *port_data, + unsigned char *out_buf) +{ + *out_buf = + MUX_CONTROL_BYTE(port_data->port_id, MUX_CONTROL_TRANSFER, + MUX_MASTER_TO_SLAVE); + *(out_buf + 1) = + (test_bit(GTM501L_DTR, &port_data->signal_state) + ? (1 << MUX_DTR_SHIFT) : 0) + | (test_bit(GTM501L_RTS, &port_data->signal_state) + ? (1 << MUX_RTS_SHIFT): 0) + | (test_bit(GTM501L_RX_FC, &port_data->signal_state) + ? (1 << MUX_LINK_SHIFT): 0); + + return 2; +} + +static void gtm501l_timer(unsigned long arg) +{ + struct tasklet_struct *tasklet = (struct tasklet_struct *)arg; + //printk("Timer fired\n"); + tasklet_hi_schedule(tasklet); +} + +/* gpio interrupt handler */ + +static irqreturn_t gtm501l_gpio_interrupt(int irq, void *dev) +{ + struct gtm501l_device *gtm_dev = (struct gtm501l_device *)dev; + static int count = 0; + + if(!gtm_dev || !test_bit(GTM501L_STATE_PRESENT, >m_dev->flags)) + return IRQ_HANDLED; + + /* + * Using the GPE layer it will set the + * irq to the requested gpio_in line + */ + + if(gtm_dev->stats) gtm_dev->stats->wait_finished(gtm_dev->frame_stats); + + if (!(count % 5000)) + dprintk(DEBUG_GPIO, "Scheduling\n"); + + if(!test_bit(GTM501L_STATE_IO_IN_PROGRESS, >m_dev->flags)) { + + /* If we received no data for the last x + * frames, delay the next transfer */ + if(gtm_dev->empty_transfers > GTM501L_MAX_EMPTY && backoff_enabled) { + if(!timer_pending(>m_dev->timer)) { + gtm_dev->timer.expires = jiffies + GTM501L_BACKOFF_TIMER; + gtm_dev->timer.function = gtm501l_timer; + gtm_dev->timer.data = (unsigned long)>m_dev->io_work_tasklet; + add_timer(>m_dev->timer); + } + } + else + tasklet_hi_schedule(>m_dev->io_work_tasklet); + } + else set_bit(GTM501L_STATE_IO_READY, >m_dev->flags); + + count++; + + return IRQ_HANDLED; +} + +static int gtm501l_prepare_tx_buffer(struct gtm501l_device *gtm_dev) +{ + int tx_count = 0; + int i, j; + unsigned int temp_count; + struct gtm501l_port_data *port_data; + int round_robin_index; + unsigned char *tx_buffer; + int len; + + if(gtm_dev->stats) gtm_dev->stats->encode_start_idle_finished(gtm_dev->frame_stats); + + tx_buffer = gtm_dev->tx_buffer[gtm_dev->tx_buffer_used]; + + /* First add flow control events for all ports */ + for (i = 0; i < GTM501L_PORT_PER_DEV; i++) { + port_data = gtm_dev->port_data[i]; + if(!port_data) + continue; + + if (test_and_clear_bit (GTM501L_UPDATE, &port_data->signal_state)) { + tx_count += gtm501l_mux_flow_control(port_data, + tx_buffer + tx_count); + } + } + + /* assemble data from buffers from all ports */ + round_robin_index = gtm_dev->round_robin_index; + for (j = round_robin_index; j < + (GTM501L_PORT_PER_DEV + round_robin_index) ; j++) { + i = j % GTM501L_PORT_PER_DEV; + port_data = gtm_dev->port_data[i]; + if(!port_data) + continue; + + /* check if this port is flow controlled */ + if (test_bit(GTM501L_TX_FC, &port_data->signal_state)) + continue; + + /* check for data to be sent */ + temp_count = gtm501l_mux_to_demux(GTM501L_TRANSFER_SIZE - tx_count); + temp_count = min(kfifo_len(port_data->tx_fifo), temp_count); + if (temp_count) { + len = kfifo_out(port_data->tx_fifo, scratch_buf, temp_count); +#ifdef GTM501L_DEBUG + sprintf(debug_buff_name, "gtm501l tx buf port %d %d", i, len); + GTM501L_BUFFER_DUMP(debug_buff_name, scratch_buf, temp_count); +#endif + tx_count += gtm501l_mux_data(i, scratch_buf, temp_count, + tx_buffer + tx_count); + total_spi_write += temp_count; + gtm_dev->empty_transfers = 0; + } + if( port_data->spec.type == GTM501L_PORT_SPEC_NET) + gtm501l_push_skb(port_data); + else if(port_data->type.serial.tty) + tty_wakeup(port_data->type.serial.tty); + } + gtm_dev->round_robin_index = gtm_dev->round_robin_index + 1; + if (gtm_dev->round_robin_index == GTM501L_PORT_PER_DEV) + gtm_dev->round_robin_index = 0; + + /* End-Of-Frame marker */ + temp_count = min(2, GTM501L_TRANSFER_SIZE - tx_count); + memset(tx_buffer + tx_count, 0, temp_count); + tx_count += temp_count; + + if(spi_b16) + swap_buf((u16 *)(tx_buffer), tx_count); + + gtm_dev->tx_count = tx_count; + + if(gtm_dev->stats) gtm_dev->stats->encode_finished(gtm_dev->frame_stats, tx_count - temp_count); + + return tx_count; +} + +/* serial functions */ +static void gtm501l_io(unsigned long data) +{ + struct gtm501l_device *gtm_dev = (struct gtm501l_device *)data; + int retval; +#ifdef GTM501L_DEBUG + char debug_buff_name[80]; +#endif + + if (!test_bit(GTM501L_STATE_PRESENT, >m_dev->flags)) + return; + + if (!test_and_set_bit(GTM501L_STATE_IO_IN_PROGRESS, >m_dev->flags)) { + gtm501l_prepare_tx_buffer(gtm_dev); + + if(gtm_dev->stats) gtm_dev->stats->transfer_start(gtm_dev->frame_stats); + + spi_message_init(>m_dev->spi_msg); + gtm_dev->spi_msg.context = gtm_dev; + gtm_dev->spi_msg.complete = gtm501l_spi_complete; + gtm_dev->spi_msg.is_dma_mapped = 1; + + /* set up our spi transfer */ + gtm_dev->spi_xfer.len = GTM501L_TRANSFER_SIZE; + gtm_dev->spi_xfer.cs_change = 0; +#if 0 + gtm_dev->tx_dma[gtm_dev->tx_buffer_used] = + dma_map_single(>m_dev->spi_dev->dev, gtm_dev->tx_buffer[gtm_dev->tx_buffer_used], + GTM501L_TRANSFER_SIZE, DMA_TO_DEVICE); + gtm_dev->rx_dma = dma_map_single(>m_dev->spi_dev->dev, gtm_dev->rx_buffer, + GTM501L_TRANSFER_SIZE, DMA_FROM_DEVICE); +#else + gtm_dev->tx_dma[gtm_dev->tx_buffer_used] = virt_to_phys(gtm_dev->tx_buffer[gtm_dev->tx_buffer_used]); + gtm_dev->rx_dma = virt_to_phys(gtm_dev->rx_buffer); +#endif + + gtm_dev->spi_xfer.tx_dma = gtm_dev->tx_dma[gtm_dev->tx_buffer_used]; + gtm_dev->spi_xfer.tx_buf = gtm_dev->tx_buffer[gtm_dev->tx_buffer_used]; + gtm_dev->tx_buffer_used = (++gtm_dev->tx_buffer_used) % 2; + gtm_dev->tx_count = 0; + + gtm_dev->spi_xfer.rx_dma = gtm_dev->rx_dma; + gtm_dev->spi_xfer.rx_buf = gtm_dev->rx_buffer; + + spi_message_add_tail(>m_dev->spi_xfer, >m_dev->spi_msg); + + retval = spi_async(gtm_dev->spi_dev, >m_dev->spi_msg); + + if (retval) { + dprintk(DEBUG_SPI, "ERROR: spi_async failed (%d)\n", retval); + clear_bit(GTM501L_STATE_IO_IN_PROGRESS, + >m_dev->flags); + tasklet_hi_schedule(>m_dev->io_work_tasklet); + return; + } + } else { + dprintk(DEBUG_SPI, "ERROR - gtm501l_io called, but spi still busy\n"); + } + +} + +static ssize_t gtm501l_sysfs_channel(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct gtm501l_port_data *port_data = NULL; + int i; + + /* Look for the port_data matching this device. */ + if(strcmp("tty", dev->class->name) == 0) { + for (i = 0; i < GTM501L_MAX_MINORS; i++) { + if (gtm501l_serial_ports[i] && + gtm501l_serial_ports[i]->type.serial.dev == dev) { + port_data = gtm501l_serial_ports[i]; + break; + } + } + } + else if(strcmp("net", dev->class->name) == 0) { + port_data = net_to_gtm501l_data(to_net_dev(dev)); + } + + return sprintf(buf, "%s\n", (port_data ? port_data->spec.name : "unknown")); +} + +static DEVICE_ATTR(channel, S_IRUGO, gtm501l_sysfs_channel, NULL); + +static void gtm501l_free_port(struct gtm501l_port_data *port_data) +{ + /* do device type specific cleanup */ + if (port_data->spec.type == GTM501L_PORT_SPEC_SERIAL) { + /* serial device cleanup */ + device_remove_file(port_data->type.serial.dev, &dev_attr_channel); + gtm501l_serial_ports[port_data->type.serial.minor] = 0; + tty_unregister_device(tty_drv, port_data->type.serial.minor); + kfifo_free(port_data->type.serial.throttle_fifo); + } else if (port_data->spec.type == GTM501L_PORT_SPEC_NET) { + /* net device cleanup */ + device_remove_file(&port_data->type.net.net->dev, &dev_attr_channel); + unregister_netdev(port_data->type.net.net); + free_netdev(port_data->type.net.net); + } + + /* do common device deinitialization */ + kfifo_free(port_data->tx_fifo); + kfree(port_data); +} + +static int gtm501l_get_free_port(void) +{ + int i; + + for (i = 0; i < GTM501L_MAX_MINORS; i++) { + if (!gtm501l_serial_ports[i]) + return i; + } + return -1; +} + +static void gtm501l_create_ports(struct gtm501l_device *gtm_dev, + struct gtm501l_port_spec *specs) +{ + struct net_device *net; + struct gtm501l_serial *gtm_ser; + struct gtm501l_port_data *port_data = NULL; + int minor = -1; + int i; + int status; + + for(i = 0; i < GTM501L_PORT_PER_DEV; i++) { + port_data = gtm_dev->port_data[i]; + + if(port_data) { + if(!specs[i].enabled) { + /* A port did exist, but it's gone now */ + gtm501l_free_port(port_data); + gtm_dev->port_data[i] = NULL; + continue; + } + else if (specs[i].type == port_data->spec.type) { + /* Old and new port are of the same type, + * only update the name */ + memcpy(&port_data->spec.name, &specs[i].name, 16); + continue; + } + else { + /* Old and new port have different types */ + gtm501l_free_port(port_data); + gtm_dev->port_data[i] = NULL; + } + } + + /* If this port is not enabled, skip it */ + if(!specs[i].enabled) { + continue; + } + + dprintk(DEBUG_INIT, "%d: (%d) %s\n", i, specs[i].type, specs[i].name); + + port_data = kzalloc(sizeof(struct gtm501l_port_data), GFP_KERNEL); + if (!port_data) + continue; + + memcpy(&port_data->spec, &specs[i], sizeof(struct gtm501l_port_spec)); + + spin_lock_init(&port_data->fifo_lock); + lockdep_set_class_and_subclass(&port_data->fifo_lock, >m501l_key, 0); + + /* common initialization */ + port_data->spi_itf = gtm_dev; + port_data->port_id = i; + port_data->tx_fifo = + status = kfifo_alloc(port_data->tx_fifo, GTM501L_FIFO_SIZE, GFP_KERNEL); + if (status) { + printk(KERN_ERR "GTM501 failed kfifo tx alloc %d\n", status); + kfree(port_data); + continue; + } + /* device specific initialization */ + if (port_data->spec.type == GTM501L_PORT_SPEC_SERIAL) { + /* serial device */ + if ((minor = gtm501l_get_free_port()) == -1) { + kfree(port_data); + continue; + } + gtm_ser = &port_data->type.serial; + gtm_ser->minor = minor; + gtm_ser->dev = + tty_register_device(tty_drv, minor, + >m_dev->spi_dev->dev); + if (!gtm_ser->dev) { + dprintk(DEBUG_INIT, "Registering tty device failed\n"); + kfree(port_data); + continue; + } + gtm501l_serial_ports[minor] = port_data; + spin_lock_init(>m_ser->throttle_fifo_lock); + lockdep_set_class_and_subclass(>m_ser->throttle_fifo_lock, >m501l_key, 0); + status = kfifo_alloc(gtm_ser->throttle_fifo, GTM501l_THROTTLE_FIFO_SIZE, + GFP_KERNEL); + if (status) { + tty_unregister_device(tty_drv, + gtm_ser->minor); + kfree(port_data); + continue; + } + + if (device_create_file(gtm_ser->dev, &dev_attr_channel)) + dev_err(gtm_ser->dev, "Could not create sysfs file for channel\n"); + } + else if (port_data->spec.type == GTM501L_PORT_SPEC_NET) { + /* net device */ + net = alloc_netdev(sizeof(struct gtm501l_port_data *), "gtm%d", + gtm501l_net_init); + if (!net) { + kfifo_free(port_data->tx_fifo); + kfree(port_data); + continue; + } + + *((struct gtm501l_port_data **)netdev_priv(net)) = port_data; + port_data->type.net.net = net; + + if (register_netdev(net)) { + free_netdev(net); + kfifo_free(port_data->tx_fifo); + kfree(port_data); + continue; + } + + if (device_create_file(&net->dev, &dev_attr_channel)) + dev_err(&net->dev, "Could not create sysfs file for channel\n"); + } + + gtm_dev->port_data[i] = port_data; + + } +} + +static void gtm501l_free_device(struct kref *ref) +{ + int i; + struct gtm501l_device *gtm_dev = + container_of(ref, struct gtm501l_device, ref); + struct gtm501l_port_data *port_data; + + tasklet_kill(>m_dev->io_work_tasklet); + + for (i = 0; i < GTM501L_PORT_PER_DEV; i++) { + port_data = gtm_dev->port_data[i]; + if(port_data) + gtm501l_free_port(port_data); + } +#ifdef CONFIG_DEBUG_FS + gtm501l_debugfs_remove(gtm_dev); +#endif + kfree(gtm_dev); +} + +static void gtm501l_spi_complete(void *ctx) +{ + struct gtm501l_device *gtm_dev = (struct gtm501l_device *)ctx; + unsigned int rx_count = 0; + + if(gtm_dev->stats) { + gtm_dev->stats->transfer_finished_wait_start(gtm_dev->frame_stats); + gtm_dev->stats->transfer_decode_start(gtm_dev->frame_stats); + } + + /* did we get removed meanwhile ? */ + if (!test_bit(GTM501L_STATE_PRESENT, >m_dev->flags)) + return; + + if (!gtm_dev->spi_msg.status) { +#if 0 + dma_unmap_single(>m_dev->spi_dev->dev, + gtm_dev->tx_dma[(gtm_dev->tx_buffer_used + 1) % 2], + GTM501L_TRANSFER_SIZE, DMA_TO_DEVICE); + dma_unmap_single(>m_dev->spi_dev->dev, gtm_dev->rx_dma, + GTM501L_TRANSFER_SIZE, DMA_FROM_DEVICE); +#endif + rx_count = gtm501l_demux(gtm_dev, gtm_dev->rx_buffer, + gtm_dev->spi_msg.actual_length); + } else { + spi_err_count++; + printk("SPI transfer error %d - (%d)\n", + gtm_dev->spi_msg.status, spi_err_count); + } + + if(gtm_dev->stats) gtm_dev->stats->decode_finished_may_idle_start(gtm_dev->frame_stats, rx_count); + + clear_bit(GTM501L_STATE_IO_IN_PROGRESS, >m_dev->flags); + + //gtm501l_prepare_tx_buffer(gtm_dev); + + if(test_and_clear_bit(GTM501L_STATE_IO_READY, >m_dev->flags)) + tasklet_hi_schedule(>m_dev->io_work_tasklet); +} + +/* char/tty operations */ + +static void gtm501l_throttle(struct tty_struct *tty) +{ + struct gtm501l_port_data *port_data = + (struct gtm501l_port_data *)tty->driver_data; + + func_enter(); + + if (port_data) { + func_exit(); + return; + } + + if(!test_bit(GTM501L_RX_FC, &port_data->signal_state)) { + set_bit(GTM501L_RX_FC, &port_data->signal_state); + set_bit(GTM501L_UPDATE, &port_data->signal_state); + } + + func_exit(); +} + +/* To be checked... I can't remember the exact details but the hso driver + * needed a hso_unthrottle_tasklet to prevent hso_throttle being + * called recursively, I am not sure whether this can happen here. + */ +#define UNTHROTTLE_STACK_BUF_SIZE (512) +static void gtm501l_unthrottle(struct tty_struct *tty) +{ + struct gtm501l_port_data *port_data = + (struct gtm501l_port_data *)tty->driver_data; + struct gtm501l_serial *gtm_ser; + int write_length_remaining, curr_write_len; + char stack_buff[UNTHROTTLE_STACK_BUF_SIZE]; + struct gtm501l_device *gtm_dev = port_data->spi_itf; + + func_enter(); + + if (!port_data) { + func_exit(); + return; + } + + gtm_ser=&port_data->type.serial; + write_length_remaining=kfifo_len(gtm_ser->throttle_fifo); + while (write_length_remaining) { + if (test_bit(TTY_THROTTLED, &tty->flags)) { + func_exit(); + return; + } + curr_write_len = min(write_length_remaining, + UNTHROTTLE_STACK_BUF_SIZE); + curr_write_len = kfifo_out(gtm_ser->throttle_fifo, + stack_buff, curr_write_len); + curr_write_len = tty_insert_flip_string + (tty, stack_buff, + curr_write_len); + write_length_remaining -= curr_write_len; + tty_flip_buffer_push(tty); + } + + clear_bit(GTM501L_RX_FC, &port_data->signal_state); + set_bit(GTM501L_UPDATE, &port_data->signal_state); + + /* If the timer is currently running, stop it and try to initiate a + * transfer immediately */ + if(timer_pending(>m_dev->timer)) { + del_timer_sync(>m_dev->timer); + gtm501l_io((unsigned long)gtm_dev); + } + + func_exit(); +} + +static int gtm501l_tiocmget(struct tty_struct *tty, struct file *filp) +{ + unsigned int value; + struct gtm501l_port_data *port_data = + (struct gtm501l_port_data *)tty->driver_data; + + func_enter(); + + if (!port_data) { + func_exit(); + return 0; + } + + value = + (test_bit(GTM501L_RTS, &port_data->signal_state) ? TIOCM_RTS : 0) | + (test_bit(GTM501L_DTR, &port_data->signal_state) ? TIOCM_DTR : 0) | + (test_bit(GTM501L_CTS, &port_data->signal_state) ? TIOCM_CTS : 0) | + (test_bit(GTM501L_DSR, &port_data->signal_state) ? TIOCM_DSR : 0) | + (test_bit(GTM501L_DCD, &port_data->signal_state) ? TIOCM_CAR : 0) | + (test_bit(GTM501L_RI, &port_data->signal_state) ? TIOCM_RNG : 0); + + func_exit(); + return value; +} + +static int gtm501l_tiocmset(struct tty_struct *tty, struct file *filp, + unsigned int set, unsigned int clear) +{ + struct gtm501l_port_data *port_data = + (struct gtm501l_port_data *)tty->driver_data; + + func_enter(); + + if (!port_data) { + func_exit(); + return -ENODEV; + } + + if (set & TIOCM_RTS) + set_bit(GTM501L_RTS, &port_data->signal_state); + if (set & TIOCM_DTR) + set_bit(GTM501L_DTR, &port_data->signal_state); + + if (clear & TIOCM_RTS) + clear_bit(GTM501L_RTS, &port_data->signal_state); + if (clear & TIOCM_DTR) + clear_bit(GTM501L_DTR, &port_data->signal_state); + + set_bit(GTM501L_UPDATE, &port_data->signal_state); + + func_exit(); + return 0; +} + +static int gtm501l_open(struct tty_struct *tty, struct file *filp) +{ + struct gtm501l_serial *gtm_ser = NULL; + struct gtm501l_port_data *port_data; + + func_enter(); + + if ((tty->index > GTM501L_MAX_MINORS) + || (!gtm501l_serial_ports[tty->index])) { + func_exit(); + return -ENODEV; + } + + port_data = gtm501l_serial_ports[tty->index]; + gtm_ser = &port_data->type.serial; + + if (!test_bit(GTM501L_STATE_PRESENT, &port_data->spi_itf->flags)) { + func_exit(); + return -ENODEV; + } + + gtm_ser->open++; + tty->driver_data = port_data; + tty->low_latency = 1; + gtm_ser->tty = tty; + _gtm501l_set_termios(tty, NULL); + + /* signal_update_needed flag will be set by tiocmset */ + clear_bit(GTM501L_RX_FC, &port_data->signal_state); + gtm501l_tiocmset(tty, filp, TIOCM_DTR | TIOCM_RTS, 0); + + kref_get(&port_data->spi_itf->ref); + func_exit(); + return 0; +} + +static void gtm501l_close(struct tty_struct *tty, struct file *filp) +{ + struct gtm501l_serial *gtm_ser = NULL; + struct gtm501l_port_data *port_data = + (struct gtm501l_port_data *)tty->driver_data; + + func_enter(); + + if ((tty->index > GTM501L_MAX_MINORS) || !port_data) { + func_exit(); + return; + } + + gtm_ser = &port_data->type.serial; + + /* + * ugh, the refcounting... unfortunately open() & close()'s aren't always executed symmetrically. + * There are cases where after a failed open you can still get a close(). We can't handle those + * here. File a tty layer bug. + */ + if(--gtm_ser->open >= 0) { + kref_put(&port_data->spi_itf->ref, gtm501l_free_device); + if( gtm_ser->open == 0) { + kfifo_reset(port_data->tx_fifo); + /* signal_update_needed flag will be set by tiocmset */ + set_bit(GTM501L_RX_FC, &port_data->signal_state); + gtm501l_tiocmset(tty, filp, 0, TIOCM_DTR | TIOCM_RTS); + gtm_ser->tty = NULL; + } + } else gtm_ser->open = 0; + + func_exit(); +} + +static int gtm501l_write(struct tty_struct *tty, const unsigned char *buf, + int count) +{ + struct gtm501l_port_data *port_data = + (struct gtm501l_port_data *)tty->driver_data; + struct gtm501l_serial *gtm_ser; + unsigned int tx_count; + unsigned char *tmp_buf = (unsigned char *)buf; + struct gtm501l_device *gtm_dev = port_data->spi_itf; + + func_enter(); + + if (!port_data) { + func_exit(); + return -ENODEV; + } + + gtm_ser = &port_data->type.serial; + + tx_count = kfifo_in(port_data->tx_fifo, tmp_buf, count); + total_tty_write+=tx_count; + + /* If the timer is currently running, stop it and try to initiate a + * transfer immediately */ + if(timer_pending(>m_dev->timer)) { + del_timer_sync(>m_dev->timer); + gtm501l_io((unsigned long)gtm_dev); + } + + //printk("Write: wrote %d bytes in fifo (total = %d)\n", tx_count, total_tty_write); + + func_exit(); + + return tx_count; +} + +static int gtm501l_write_room(struct tty_struct *tty) +{ + struct gtm501l_port_data *port_data = + (struct gtm501l_port_data *)tty->driver_data; + + if (!port_data) { + return -ENODEV; + } + + //func_enter(); + + return GTM501L_FIFO_SIZE - kfifo_len(port_data->tx_fifo); +} + +static void _gtm501l_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + struct gtm501l_port_data *port_data = + (struct gtm501l_port_data *)tty->driver_data; + struct gtm501l_serial *serial; + struct ktermios *termios; + + if ((!tty) || (!tty->termios) || (!port_data)) { + printk(KERN_ERR "%s: no tty structures", __func__); + return; + } + + serial = &port_data->type.serial; + /* + * * The default requirements for this device are: + * */ + termios = tty->termios; + termios->c_iflag &= ~(IGNBRK /* disable ignore break */ + | BRKINT /* disable break causes interrupt */ + | PARMRK /* disable mark parity errors */ + | ISTRIP /* disable clear high bit of input characters */ + | INLCR /* disable translate NL to CR */ + | IGNCR /* disable ignore CR */ + | ICRNL /* disable translate CR to NL */ + | IXON); /* disable enable XON/XOFF flow control */ + + /* disable postprocess output characters */ + termios->c_oflag &= ~OPOST; + + termios->c_lflag &= ~(ECHO /* disable echo input characters */ + | ECHONL /* disable echo new line */ + | ICANON /* disable erase, kill, werase, and rprnt + special characters */ + | ISIG /* disable interrupt, quit, and suspend special + characters */ + | IEXTEN); /* disable non-POSIX special characters */ + + termios->c_cflag &= ~(CSIZE /* no size */ + | PARENB /* disable parity bit */ + | CBAUD /* clear current baud rate */ + | CBAUDEX); /* clear current buad rate */ + termios->c_cflag |= CS8; /* character size 8 bits */ + + tty_encode_baud_rate(serial->tty, 115200, 115200); + /* + * Force low_latency on; otherwise the pushes are scheduled; + * this is bad as it opens up the possibility of dropping bytes + * on the floor. We don't want to drop bytes on the floor. :) + */ + serial->tty->low_latency = 1; + serial->tty->termios->c_cflag |= B115200; /* baud rate 115200 */ + return; +} + +static void gtm501l_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + struct gtm501l_port_data *port_data = + (struct gtm501l_port_data *)tty->driver_data; + struct gtm501l_serial *serial; + + func_enter(); + + if (!port_data) { + func_exit(); + return; + } + serial = &port_data->type.serial; + + /* the actual setup */ + if (serial->tty) + _gtm501l_set_termios(tty, old); + else + tty->termios = old; + + /* done */ + func_exit(); + return; +} + +static int gtm501l_chars_in_buffer(struct tty_struct *tty) +{ + struct gtm501l_port_data *port_data = + (struct gtm501l_port_data *)tty->driver_data; + + if (!port_data) + return -ENODEV; + + //func_enter(); + + return kfifo_len(port_data->tx_fifo); +} + +static struct mrst_spi_chip mrst_gtm501l = { + .poll_mode = 0, + .enable_dma = 1, + .type = SPI_FRF_SPI, +}; + +/* spi operations */ + +static int gtm501l_spi_probe(struct spi_device *spi) +{ + struct gtm501l_device *gtm_dev; + int i; + + func_enter(); + + /* we check here only the SPI mode and correct them, if needed */ + if (GTM501L_SPI_MODE != (spi->mode & (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | SPI_LSB_FIRST | SPI_3WIRE))) { + pr_warning("%s: SPI mode wrong setup, found %d, correct to %d\n", + DRVNAME, spi->mode, GTM501L_SPI_MODE); + spi->mode = GTM501L_SPI_MODE | (SPI_LOOP & spi->mode); + } + + if (spi->mode & SPI_LOOP) { + pr_warning("%s: SPI device in loop back\n", DRVNAME); + } + + /* The Bit_per_word and the maximum speed has to be setup by us, the protocol driver */ + if(spi_b16) + spi->bits_per_word = 16; + else + spi->bits_per_word = 8; + + spi->max_speed_hz = GTM501L_SPI_SPEED; + + spi->controller_data = &mrst_gtm501l; + + if (spi_setup(spi)) { + pr_err("%s: SPI setup does wasn't successful\n", DRVNAME); + func_exit(); + return -ENODEV; + } + + /* initialize structure to hold our device variables */ + gtm_dev = kzalloc(sizeof(struct gtm501l_device), GFP_ATOMIC); + + if (!gtm_dev) { + func_exit(); + return -ENOMEM; + } + gtm_dev->spi_dev = spi; + kref_init(>m_dev->ref); + + /*initialize transfer and dma buffers */ + for(i = 0; i < 2; i++) { + gtm_dev->tx_buffer[i] = kzalloc(GTM501L_TRANSFER_SIZE, GFP_KERNEL | GFP_DMA); + if( 0 == gtm_dev->tx_buffer[i]) { + pr_err("%s: DMA-TX[%d] buffer allocation failed\n", DRVNAME, i); + func_exit(); + return -EIO; + } + } + gtm_dev->rx_buffer = kzalloc(GTM501L_TRANSFER_SIZE, GFP_KERNEL | GFP_DMA); + if( 0 == gtm_dev->rx_buffer) { + pr_err("%s: DMA-RX buffer allocation failed\n", DRVNAME); + func_exit(); + return -EIO; + } + + /* create our tty/net ports */ + gtm501l_create_ports(gtm_dev, gtm501l_default_port_spec); + + spi_set_drvdata(spi, gtm_dev); + + tasklet_init(>m_dev->io_work_tasklet, + (void (*)(unsigned long))gtm501l_io, + (unsigned long)gtm_dev); + + init_timer(>m_dev->timer); + +#ifdef CONFIG_DEBUG_FS + gtm501l_debugfs_init(gtm_dev); +#endif + + /* + * Init GPIO and IRQ, if at least the gpio parameter is set + */ + if (gpio_in < 0) + gpio_in = GTM501L_GPIO0; + + if (request_irq(spi->irq, gtm501l_gpio_interrupt, GTM501L_IRQ_TYPE, + "option", (void *)gtm_dev)) { + kref_put(>m_dev->ref, gtm501l_free_device); + func_exit(); + return -EIO; + } + + set_bit(GTM501L_STATE_PRESENT, >m_dev->flags); + /* + * Schedule tasklet once in case the gpio is active at probe time. + * Otherwise wait for the next interrupt + */ + gtm501l_gpio_interrupt(spi->irq, (void *)gtm_dev); + + func_exit(); + return 0; +} + +static int gtm501l_spi_remove(struct spi_device *spi) +{ + struct gtm501l_device *gtm_dev = + (struct gtm501l_device *)spi_get_drvdata(spi); + + func_enter(); + + del_timer_sync(>m_dev->timer); + + clear_bit(GTM501L_STATE_PRESENT, >m_dev->flags); + free_irq(spi->irq, gtm_dev); + kfree(gtm_dev->tx_buffer[0]); + kfree(gtm_dev->tx_buffer[1]); + kfree(gtm_dev->rx_buffer); + spi_set_drvdata(spi, NULL); + kref_put(>m_dev->ref, gtm501l_free_device); + + func_exit(); + return 0; +} + +static void gtm501l_spi_shutdown(struct spi_device *spi) +{ + func_enter(); +} + +static int gtm501l_spi_suspend(struct spi_device *spi, pm_message_t msg) +{ + func_enter(); + return 0; +} + +static int gtm501l_spi_resume(struct spi_device *spi) +{ + func_enter(); + return 0; +} + +static struct tty_operations gtm501l_serial_ops = { + .open = gtm501l_open, + .close = gtm501l_close, + .write = gtm501l_write, + .write_room = gtm501l_write_room, + .set_termios = gtm501l_set_termios, + .chars_in_buffer = gtm501l_chars_in_buffer, + .tiocmget = gtm501l_tiocmget, + .tiocmset = gtm501l_tiocmset, + .throttle = gtm501l_throttle, + .unthrottle = gtm501l_unthrottle +}; + +static struct spi_driver gtm501l_spi_driver = { + .driver = { + .name = "spi_opt_modem", + .bus = &spi_bus_type, + .owner = THIS_MODULE + }, + .probe = gtm501l_spi_probe, + .remove = __devexit_p(gtm501l_spi_remove), + .shutdown = gtm501l_spi_shutdown, + .suspend = gtm501l_spi_suspend, + .resume = gtm501l_spi_resume, +}; + +/* module exit point */ +static void __exit gtm501l_exit(void) +{ + func_enter(); + tty_unregister_driver(tty_drv); + spi_unregister_driver(>m501l_spi_driver); + dprintk(DEBUG_CLEANUP, "GTM501L driver removed\n"); + func_exit(); +} + +/* module entry point */ +static int __init gtm501l_init(void) +{ + int result = 0; + + func_enter(); + +/* gtm501l_pmic_init_voltages();*/ + gtm501l_pmic_set_wwandisablen(1); + + gtm501l_pmic_set_wwanresetn(0); + msleep(100); + gtm501l_pmic_set_wwanresetn(1); + + memset(gtm501l_serial_ports, 0, sizeof(gtm501l_serial_ports)); + memset(gtm501l_termios, 0, sizeof(gtm501l_termios)); + memset(gtm501l_termios_locked, 0, sizeof(gtm501l_termios_locked)); + + /* initialize lower-edge tty driver */ + tty_drv = alloc_tty_driver(GTM501L_MAX_MINORS); + if (!tty_drv) { + func_exit(); + return -ENOMEM; + } + + tty_drv->magic = TTY_DRIVER_MAGIC; + tty_drv->owner = THIS_MODULE; + tty_drv->driver_name = "gtm501l"; + tty_drv->name = "ttyGTM"; + tty_drv->minor_start = 0; + tty_drv->num = GTM501L_MAX_MINORS; + tty_drv->type = TTY_DRIVER_TYPE_SERIAL; + tty_drv->subtype = SERIAL_TYPE_NORMAL; + tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + tty_drv->init_termios = tty_std_termios; + tty_drv->init_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL; + tty_drv->termios = gtm501l_termios; + tty_drv->termios_locked = gtm501l_termios_locked; + + tty_set_operations(tty_drv, >m501l_serial_ops); + + result = tty_register_driver(tty_drv); + if (result) { + printk(KERN_ERR "%s - tty_register_driver failed(%d)\n", + __func__, result); + func_exit(); + return result; + } + + /* + initialize upper-edge spi driver. needs to be done after tty initialization because the spi probe will + race + */ + result = spi_register_driver(>m501l_spi_driver); + if (result) { + printk(KERN_ERR "%s - spi_register_driver failed(%d)\n", + __func__, result); + tty_unregister_driver(tty_drv); + func_exit(); + return result; + } + + dprintk(DEBUG_INIT, "GTM501L driver initialized successfully\n"); + func_exit(); + return 0; +} + +static int gtm501l_net_open(struct net_device *net) +{ + struct gtm501l_port_data *port_data = net_to_gtm501l_data(net); + + func_enter(); + + port_data->type.net.rx_state = WAIT_IP; + port_data->type.net.sync_lost = 0; + port_data->type.net.rx_buf_size = 0; + port_data->type.net.rx_buf_missing = sizeof(struct iphdr); + + /* update remote side it's ok to send us data */ + clear_bit(GTM501L_RX_FC, &port_data->signal_state); + set_bit(GTM501L_UPDATE, &port_data->signal_state); + netif_start_queue(net); + func_exit(); + return 0; +} + +static int gtm501l_net_close(struct net_device *net) +{ + struct gtm501l_port_data *port_data = net_to_gtm501l_data(net); + + func_enter(); + + /* stop remote side from sending us data */ + set_bit(GTM501L_RX_FC, &port_data->signal_state); + set_bit(GTM501L_UPDATE, &port_data->signal_state); + netif_stop_queue(net); + func_exit(); + return 0; +} + +static void gtm501l_push_skb(struct gtm501l_port_data *port_data) +{ + struct gtm501l_net *gtm_net = &port_data->type.net; + struct sk_buff *skb = gtm_net->tx_skb; + unsigned int len; + + func_enter(); + + if (skb && gtm_net->net->flags & IFF_UP) { + len = kfifo_in(port_data->tx_fifo, skb->data, skb->len); + skb_pull(skb, len); + if (skb->len == 0) { + // dev_kfree_skb(skb); // TODO: This causes a crash... + gtm_net->tx_skb = NULL; + netif_start_queue(gtm_net->net); + } + } + + func_exit(); +} + +static int gtm501l_net_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + int result = 0; + struct gtm501l_port_data *port_data = net_to_gtm501l_data(net); + struct gtm501l_net *gtm_net = &port_data->type.net; + + func_enter(); + + netif_stop_queue(net); + + if (gtm_net->tx_skb) { + printk(KERN_ERR "%s tx_skb not null\n", __func__); + result = -EIO; + } else { + gtm_net->tx_skb = skb; + gtm501l_push_skb(port_data); + } + if (result) { + STATS(net).tx_errors++; + netif_start_queue(net); + } else { + STATS(net).tx_packets++; + STATS(net).tx_bytes += skb->len; + /* And tell the kernel when the last transmit started. */ + net->trans_start = jiffies; + } + /* we're done */ + func_exit(); + return result; +} + +#ifndef NETDEVICE_HAS_STATS +static struct net_device_stats *gtm501l_net_get_stats(struct net_device *net) +{ + return &STATS(net); +} +#endif + +/* called when a packet did not ack after watchdogtimeout */ +static void gtm501l_net_tx_timeout(struct net_device *net) +{ + func_enter(); + + /* Tell syslog we are hosed. */ + dev_warn(&net->dev, "Tx timed out.\n"); + + /* Update statistics */ + STATS(net).tx_errors++; + + func_exit(); +} + +static const struct net_device_ops gtm501l_netdev_ops = { + .ndo_open = gtm501l_net_open, + .ndo_stop = gtm501l_net_close, + .ndo_start_xmit = gtm501l_net_start_xmit, +#ifndef NETDEVICE_HAS_STATS + .ndo_get_stats = gtm501l_net_get_stats, +#endif + .ndo_tx_timeout = gtm501l_net_tx_timeout, +}; + +static void gtm501l_net_init(struct net_device *net) +{ + func_enter(); + + /* fill in the other fields */ + net->netdev_ops = >m501l_netdev_ops; + net->watchdog_timeo = GTM501L_NET_TX_TIMEOUT; + net->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + net->type = ARPHRD_NONE; + net->mtu = GTM501L_DEFAULT_MTU; + net->tx_queue_len = 10; + + func_exit(); +} + +struct gtm501l_device *gtm501l_set_stats_ops(struct gtm501_stats_ops *stats) +{ + struct gtm501l_device *gtm_dev = NULL; + int i; + + /* Look for gtm_dev */ + for (i = 0; i < GTM501L_MAX_MINORS; i++) { + if (gtm501l_serial_ports[i] && + gtm501l_serial_ports[i]->spi_itf) { + gtm_dev = gtm501l_serial_ports[i]->spi_itf; + break; + } + } + + if(gtm_dev) + gtm_dev->stats = stats; + + return gtm_dev; +} + +/* module definitions */ +module_init(gtm501l_init); +module_exit(gtm501l_exit); + +module_param_named(backoff, backoff_enabled, uint, S_IRUGO); +MODULE_PARM_DESC(backoff, "Enable (1) or disable (0) backoff timer."); + +module_param_named(gpi, gpio_in, uint, S_IRUGO); +MODULE_PARM_DESC(gpi, "GPIO input base address. (default: -1 => automatic)"); + +module_param_named(b16, spi_b16, bool, S_IRUGO); +MODULE_PARM_DESC(b16, "SPI 16Bit/word or 8Bit/word, default 16Bit"); + +#ifdef DEBUG +module_param_named(debug, gtm501l_debug, uint, S_IRUGO); +MODULE_PARM_DESC(debug, "Debug flags"); +#endif + +MODULE_AUTHOR("Option Wireless"); +MODULE_DESCRIPTION("GTM501L spi driver"); +MODULE_LICENSE("GPL"); +MODULE_INFO(Version, "0.5pre1-option"); + +EXPORT_SYMBOL_GPL(gtm501l_debug); +EXPORT_SYMBOL_GPL(gtm501l_debug_printk); +EXPORT_SYMBOL_GPL(gtm501l_set_stats_ops); + Index: linux-2.6.33/drivers/spi/gtm501l_spi.h =================================================================== --- /dev/null +++ linux-2.6.33/drivers/spi/gtm501l_spi.h @@ -0,0 +1,329 @@ +/**************************************************************************** + * + * Driver for the Option GTM501L spi modem. + * + * Copyright (C) 2008 Option International + * Copyright (C) 2008 Filip Aben + * Denis Joseph Barrow + * Jan Dumon + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA + * + * + * + *****************************************************************************/ + +#ifndef _GTM501L_SPI_H +#define _GTM501L_SPI_H +#include +#include +#include +#include + +#include +#include + +#define DRVNAME "gtm501l" + +#define DEBUG + +#ifdef DEBUG +#define DEBUG_FLOW (1 << 0) +#define DEBUG_INIT (1 << 1) +#define DEBUG_CLEANUP (1 << 2) +#define DEBUG_TTY (1 << 3) +#define DEBUG_NET (1 << 4) +#define DEBUG_MUX (1 << 5) +#define DEBUG_DEMUX (1 << 6) +#define DEBUG_SPI (1 << 7) +#define DEBUG_GPIO (1 << 8) + +#define dprintk(f, str...) if(gtm501l_debug & f) gtm501l_debug_printk(__func__, __LINE__, str) + +#define GTM501L_BUFFER_DUMP(prefix_str,buf,len) \ + print_hex_dump(KERN_DEBUG,prefix_str, DUMP_PREFIX_OFFSET,16,1,buf,len,1) + +void gtm501l_debug_printk(const char *function, int line, char *format, ...); +extern int gtm501l_debug; + +#else +#define dprintk(f, str...) +#define GTM501L_BUFFER_DUMP(prefix_str,buf,len) +#endif + +#define func_enter() dprintk(DEBUG_FLOW, "enter\n") +#define func_exit() dprintk(DEBUG_FLOW, "exit\n") + +#define GTM501L_DEFAULT_MTU 1500 +#define GTM501L_DEFAULT_MRU 2500 +#define GTM501L_NET_TX_TIMEOUT (HZ * 10) + +#define GTM501L_IRQ_TYPE IRQ_TYPE_EDGE_FALLING +#define GTM501L_GPIO_TARGET 0 +#define GTM501L_GPIO0 0x3c /* default use Langwell GPIO60 */ + +/* various macro definitions */ +#define GTM501L_MAX_MINORS 256 +#define GTM501L_PORT_PER_DEV 16 +#define GTM501L_TRANSFER_SIZE 2040 +/* GTM501l_THROTTLE_FIFO_SIZE must be a power of 2 + * & larger than GTM501L_TRANSFER_SIZE */ +#define GTM501l_THROTTLE_FIFO_SIZE 4096 +#define GTM501L_FIFO_SIZE 4096 + +/* device flags bitfield definitions */ +#define GTM501L_STATE_PRESENT 0 +#define GTM501L_STATE_IO_IN_PROGRESS 1 +#define GTM501L_STATE_IO_READY 2 + +#define MUX_CHANNEL(x) ((x >> MUX_CHANNEL_SHIFT) & 0xF) +#define MUX_CHANNEL_SHIFT 0 +#define MUX_BLOCK_TYPE(x) ((x >> MUX_BLOCK_TYPE_SHIFT) & 0x3) +#define MUX_BLOCK_TYPE_SHIFT 4 +#define MUX_DEVICE(x) ((x >> MUX_DEVICE_SHIFT) & 0x3) +#define MUX_DEVICE_SHIFT 6 +#define MUX_BURST_SIZE 512 + +#define MUX_DATA_TRANSFER 0 +#define MUX_BURST_TRANSFER 1 +#define MUX_CONTROL_TRANSFER 2 + +#define MUX_CONTROL_BYTE(channel,type,device) ( \ + (channel<> MUX_DCD_SHIFT) & 0x1) +#define MUX_DCD_SHIFT 0 +#define MUX_CTS(x) ((x >> MUX_CTS_SHIFT) & 0x1) +#define MUX_CTS_SHIFT 1 +#define MUX_DSR(x) ((x >> MUX_DSR_SHIFT) & 0x1) +#define MUX_DSR_SHIFT 2 +#define MUX_RI(x) ((x >> MUX_RI_SHIFT) & 0x1) +#define MUX_RI_SHIFT 3 +#define MUX_DTR(x) ((x >> MUX_DTR_SHIFT) & 0x1) +#define MUX_DTR_SHIFT 4 +#define MUX_RTS(x) ((x >> MUX_RTS_SHIFT) & 0x1) +#define MUX_RTS_SHIFT 5 +#define MUX_LINK(x) ((x >> MUX_LINK_SHIFT) & 0x1) +#define MUX_LINK_SHIFT 7 + +#define MUX_INVALID 0 +#define MUX_SLAVE_TO_MASTER 1 +#define MUX_MASTER_TO_SLAVE 2 +#define MUX_INVALID2 3 + +#define GTM501L_SPI_MODE SPI_MODE_1 /* SPI Mode 1 currently used */ + +#define GTM501L_SPI_SPEED 12500000 + +/* flow control bitfields */ +#define GTM501L_DCD 0 +#define GTM501L_CTS 1 +#define GTM501L_DSR 2 +#define GTM501L_RI 3 +#define GTM501L_DTR 4 +#define GTM501L_RTS 5 +#define GTM501L_TX_FC 6 +#define GTM501L_RX_FC 7 +#define GTM501L_UPDATE 8 + +#define GTM501L_MAX_EMPTY 500 +#define GTM501L_BACKOFF_TIMER (HZ / 2) + +struct gtm501l_device { + struct spi_device *spi_dev; + struct kref ref; + struct gtm501l_port_data *port_data[GTM501L_PORT_PER_DEV]; + struct tasklet_struct io_work_tasklet; + unsigned long flags; + dma_addr_t rx_dma; + dma_addr_t tx_dma[2]; + + unsigned char *rx_buffer; + unsigned char *tx_buffer[2]; + int tx_buffer_used; + int tx_count; + + struct spi_message spi_msg; + struct spi_transfer spi_xfer; + + int gpio_irq; + int round_robin_index; + + struct timer_list timer; + int empty_transfers; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; /* debugfs parent directory */ + struct gtm501l_frame_stats *frame_stats; +#endif + + struct gtm501_stats_ops *stats; +}; + +struct gtm501l_serial { + struct device *dev; + struct tty_struct *tty; + struct kfifo *throttle_fifo; + spinlock_t throttle_fifo_lock; + int minor; + int open; +}; + +enum rx_parse_state { + syncing, + getting_frame_len, + filling_skb, + WAIT_IP, + WAIT_DATA, + WAIT_SYNC +}; + +#undef NETDEVICE_HAS_STATS + +struct gtm501l_net { + enum rx_parse_state rx_state; + int sync_lost; + struct sk_buff *tx_skb; + struct sk_buff *rx_skb; + unsigned short rx_frame_len; + struct net_device *net; + unsigned short rx_buf_size; + unsigned short rx_buf_missing; + struct iphdr rx_ip_hdr; +#ifndef NETDEVICE_HAS_STATS + struct net_device_stats stats; +#endif +}; + +#define GTM501L_PORT_SPEC_SERIAL 0 +#define GTM501L_PORT_SPEC_NET 1 + +struct gtm501l_port_spec { + int enabled; + int type; + char name[16]; +}; + +struct gtm501l_port_data { + struct gtm501l_device *spi_itf; + int port_id; + struct gtm501l_port_spec spec; + struct kfifo *tx_fifo; + spinlock_t fifo_lock; + unsigned long signal_state; + union { + struct gtm501l_serial serial; + struct gtm501l_net net; + } type; +}; + +#define net_to_gtm501l_data(net) *((struct gtm501l_port_data **)netdev_priv(net)) + +#ifdef NETDEVICE_HAS_STATS +#define STATS(net) ((net)->stats) +#else +#define STATS(net) (((struct gtm501l_port_data *)net_to_gtm501l_data(net))->type.net.stats) +#endif + +#ifdef CONFIG_DEBUG_FS + +/** + * transfer SPI frame sequence, can be used for global sequence state or for per CPU seq state variable + */ +enum gtm501l_fsequence { /* frame sequence start states */ + none, /* undefined state */ + idle, /* idle state host driver waits */ + encode, /* encoding SPI frame */ + encode_interrupt_decode, /* encoding SPI frame started and interrupts decoding frame */ + decode, /* decoding SPI frame */ + decode_interrupt_encode /* decoding SPI frame started and interrupts decoding frame */ +}; + +/** + * job time with the support for interrupt time correction, which me be used only for encoding and decoding time + * measurements + */ +struct gtm501l_jtime { /* job time */ + ktime_t start; /* start time for that job */ + ktime_t correct; /* correction time, if job was interrupted */ + u32 dt; /* delta time need for that job in us */ + u32 min_dt; /* min time need for that job is us */ + u32 max_dt; /* max time need for that job is us */ + u64 total; /* total time need for that job is us */ + u32 bug; /* bug counter for negative time delta */ +}; + +/** + * frame statistics + */ +struct gtm501l_frame_stats { /* frame transfer statistics */ + spinlock_t lock; /* lock for that structure */ + enum gtm501l_fsequence seq[NR_CPUS]; /* current sequence for each CPU separate */ + struct gtm501l_jtime idle; /* timings for idle, just waiting for the application or GTM501L become busy */ + struct gtm501l_jtime encode; /* timings for encoding SPI frame */ + struct gtm501l_jtime transceive; /* timings for tranceiving SPI frame */ + struct gtm501l_jtime decode; /* timings for decoding SPI frame */ + struct gtm501l_jtime wait; /* timings for waiting for GTM501L become ready */ + struct gtm501l_jtime cycle; /* timings for a SPI frame cycle without idle time */ + struct kfifo *transmit_id_pipe; /* fifo pipe frame id to transmit task */ + struct kfifo *decode_id_pipe; /* fifo pipe frame id to decode task */ + struct kfifo *decode_txb_pipe; /* fifo pipe number of transmit byte to decode task for analysis */ + struct kfifo *decode_dt_pipe; /* fifo pipe SPI frame transfer time to decode task for analysis */ + u32 transmit_id; /* id and number of transmit SPI frames */ + u32 receive_id; /* id and number of received SPI frames */ + u32 encode_start_id; /* id and number of started encoded frames */ + u32 encode_end_id; /* id and number of finished encoded frames */ + u32 decode_start_id; /* id and number of started decoded frames */ + u32 decode_end_id; /* id and number of started decoded frames */ + u32 idles; /* number of entered idle states */ + u32 waits; /* number of entered wait states */ + u32 max_tx_bytes; /* maximum transmitted bytes in a frame */ + u32 max_rx_bytes; /* maximum received bytes in a frame */ + u64 total_tx_bytes; /* total transmitted bytes in a frame for calculating average */ + u64 total_rx_bytes; /* total received bytes in a frame for calculating average */ + u32 first_tx_bytes; /* first transmitted bytes in a frame for calculating average */ + u32 first_rx_bytes; /* first received bytes in a frame for calculating average */ + u32 max_tx_rate; /* maximum transmitted bytes per time rate in bytes/sec */ + u32 max_rx_rate; /* maximum received bytes per time rate in bytes/sec */ + u32 encode_pass_decode; /* encode task pass decode task */ + u32 encode_interrupts_decode; /* encode task interrupts decode task on the same CPU */ + u32 decode_pass_encode; /* decode task pass encode task */ + u32 decode_interrupts_encode; /* decode task interrupts encode task on the same CPU */ + u32 encode_bug; /* number of counted bugs for encode process */ + int encode_buffers_used; /* number of need encode buffers */ + u32 decode_bug; /* number of counted bugs for the decode process */ + int decode_buffers_used; /* number of need decode buffers */ + struct dentry *debugfs; /* debugfs entry for the frame_stats file */ +}; + +#endif + +struct gtm501_stats_ops { + void (*wait_finished)(struct gtm501l_frame_stats *fstats); + void (*encode_start_idle_finished)(struct gtm501l_frame_stats *fstats); + void (*encode_finished)(struct gtm501l_frame_stats *fstats, unsigned int tx_bytes); + void (*transfer_start)(struct gtm501l_frame_stats *fstats); + void (*transfer_finished_wait_start)(struct gtm501l_frame_stats *fstats); + void (*transfer_decode_start)(struct gtm501l_frame_stats *fstats); + void (*decode_finished_may_idle_start)(struct gtm501l_frame_stats *fstats, unsigned int rx_bytes); +}; + +/* Prototypes */ +struct gtm501l_device *gtm501l_set_stats_ops(struct gtm501_stats_ops *stats); + +#endif