>From 80d279ec11492ca6729f1421983f52b8e7144cd4 Mon Sep 17 00:00:00 2001 From: Doron Cohen <doronc@xxxxxxxxxxxx> Date: Sun, 25 Sep 2011 17:47:12 +0300 Subject: [PATCH v2] Add smsspi driver to support Siano SPI connected device using SPI generic driver modified: drivers/media/dvb/siano/smsspidrv.c modified: drivers/media/dvb/siano/smsspiphy.c new file: drivers/media/dvb/siano/smsspidrv.c new file: drivers/media/dvb/siano/smsspiphy.c new file: drivers/media/dvb/siano/smsspidrv.c new file: drivers/media/dvb/siano/smsspiphy.c new file: drivers/media/dvb/siano/smsspiphy.h modified: drivers/media/dvb/siano/Kconfig new file: drivers/media/dvb/siano/smsspiphy.c Signed-off-by: Doron Cohen <doronc@xxxxxxxxxxxx> --- drivers/media/dvb/siano/Kconfig | 23 ++ drivers/media/dvb/siano/Makefile | 2 + drivers/media/dvb/siano/smscoreapi.c | 2 +- drivers/media/dvb/siano/smsdbg_prn.h | 56 ++++ drivers/media/dvb/siano/smsspicommon.c | 407 +++++++++++++++++++++++++++ drivers/media/dvb/siano/smsspicommon.h | 96 +++++++ drivers/media/dvb/siano/smsspidrv.c | 472 ++++++++++++++++++++++++++++++++ drivers/media/dvb/siano/smsspiphy.c | 218 +++++++++++++++ drivers/media/dvb/siano/smsspiphy.h | 38 +++ 9 files changed, 1313 insertions(+), 1 deletions(-) create mode 100644 drivers/media/dvb/siano/smsdbg_prn.h create mode 100644 drivers/media/dvb/siano/smsspicommon.c create mode 100644 drivers/media/dvb/siano/smsspicommon.h create mode 100644 drivers/media/dvb/siano/smsspidrv.c create mode 100644 drivers/media/dvb/siano/smsspiphy.c create mode 100644 drivers/media/dvb/siano/smsspiphy.h diff --git a/drivers/media/dvb/siano/Kconfig b/drivers/media/dvb/siano/Kconfig index bc6456e..a47a131 100644 --- a/drivers/media/dvb/siano/Kconfig +++ b/drivers/media/dvb/siano/Kconfig @@ -17,6 +17,15 @@ config SMS_SIANO_MDTV if SMS_SIANO_MDTV menu "Siano module components" +# Kernel sub systems support + +config SMS_NET_SUBSYS + tristate "Siano Network Adapter" + depends on NET + default n + ---help--- + Choose if you would like to have Siano's network adapter support. + # Hardware interfaces support config SMS_USB_DRV @@ -30,5 +39,19 @@ config SMS_SDIO_DRV depends on DVB_CORE && MMC ---help--- Choose if you would like to have Siano's support for SDIO interface + +config SMS_SPI_DRV + tristate "SPI interface support" + depends on SPI + default y if SPI + ---help--- + Choose if you would like to have Siano's support for SPI interface + +config SMS_I2C_DRV + tristate "I2C interface support" + depends on DVB_CORE && I2C + ---help--- + Choose if you would like to have Siano's support for I2C interface + endmenu endif # SMS_SIANO_MDTV diff --git a/drivers/media/dvb/siano/Makefile b/drivers/media/dvb/siano/Makefile index c54140b..affaf01 100644 --- a/drivers/media/dvb/siano/Makefile +++ b/drivers/media/dvb/siano/Makefile @@ -1,9 +1,11 @@ smsmdtv-objs := smscoreapi.o sms-cards.o smsendian.o smsir.o +smsspi-objs := smsspicommon.o smsspidrv.o smsspiphy.o obj-$(CONFIG_SMS_SIANO_MDTV) += smsmdtv.o smsdvb.o obj-$(CONFIG_SMS_USB_DRV) += smsusb.o obj-$(CONFIG_SMS_SDIO_DRV) += smssdio.o +obj-$(CONFIG_SMS_SPI_DRV) += smsspi.o EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core diff --git a/drivers/media/dvb/siano/smscoreapi.c b/drivers/media/dvb/siano/smscoreapi.c index 78765ed..239f453 100644 --- a/drivers/media/dvb/siano/smscoreapi.c +++ b/drivers/media/dvb/siano/smscoreapi.c @@ -39,7 +39,7 @@ #include "smsir.h" #include "smsendian.h" -static int sms_dbg; +int sms_dbg; module_param_named(debug, sms_dbg, int, 0644); MODULE_PARM_DESC(debug, "set debug level (info=1, adv=2 (or-able))"); diff --git a/drivers/media/dvb/siano/smsdbg_prn.h b/drivers/media/dvb/siano/smsdbg_prn.h new file mode 100644 index 0000000..ea157da --- /dev/null +++ b/drivers/media/dvb/siano/smsdbg_prn.h @@ -0,0 +1,56 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. +Copyright (C) 2006-2008, Uri Shkolnik + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + + 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, see <http://www.gnu.org/licenses/>. + +****************************************************************/ + +#ifndef _SMS_DBG_H_ +#define _SMS_DBG_H_ + +#include <linux/kernel.h> +#include <linux/module.h> + +/********************************************************************** **/ +/* Debug Zones definitions. */ +/********************************************************************** **/ +#undef PERROR +# define PERROR(fmt, args...) \ + printk(KERN_ERR "spibus error: line %d- %s(): " fmt, __LINE__,\ + __func__, ## args) +#undef PWARNING +# define PWARNING(fmt, args...) \ + printk(KERN_WARNING "spibus warning: line %d- %s(): " fmt, __LINE__, \ + __func__, ## args) + +/* the debug macro - conditional compilation from the makefile */ +#undef PDEBUG /* undef it, just in case */ +#ifdef SPIBUS_DEBUG +# define PDEBUG(fmt, args...) \ + printk(KERN_DEBUG "spibus: line %d- %s(): " fmt, __LINE__, \ + __func__, ## args) +#else +# define PDEBUG(fmt, args...) /* not debugging: nothing */ +#endif + +/* The following defines are used for printing and +are mandatory for compilation. */ +#define TXT(str) str +#define PRN_DBG(str) PDEBUG str +#define PRN_ERR(str) PERROR str + +#endif /*_SMS_DBG_H_*/ diff --git a/drivers/media/dvb/siano/smsspicommon.c b/drivers/media/dvb/siano/smsspicommon.c new file mode 100644 index 0000000..9fd9508 --- /dev/null +++ b/drivers/media/dvb/siano/smsspicommon.c @@ -0,0 +1,407 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. +Copyright (C) 2006-2008, Uri Shkolnik + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + + 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, see <http://www.gnu.org/licenses/>. + +****************************************************************/ +#include "smsspicommon.h" +#include "smsdbg_prn.h" + +static struct _rx_buffer_st *smsspi_handle_unused_bytes_buf( + struct _spi_dev *dev, + struct _rx_buffer_st *buf, int offset, int len, + int unused_bytes) +{ + struct _rx_buffer_st *tmp_buf; + tmp_buf = dev->cb.allocate_rx_buf(dev->context, + RX_PACKET_SIZE); + if (!tmp_buf) { + PRN_ERR((TXT + ("Failed to allocate RX buffer.\n"))); + return NULL; + } + if (unused_bytes > 0) { + /* Copy the remaining bytes to the end of + alignment block (256 bytes) so next read + will be aligned. */ + int align_block = + (((unused_bytes + SPI_PACKET_SIZE - + 1) >> SPI_PACKET_SIZE_BITS) << + SPI_PACKET_SIZE_BITS); + memset(tmp_buf->ptr, 0, + align_block - unused_bytes); + memcpy((char *)tmp_buf->ptr + + (align_block - unused_bytes), + (char *)buf->ptr + offset + len - + unused_bytes, unused_bytes); + } + return tmp_buf; +} + +static struct _rx_buffer_st *smsspi_common_find_msg(struct _spi_dev *dev, + struct _rx_buffer_st *buf, int offset, int len, + int *unused_bytes, int *missing_bytes) +{ + int i; + int recieved_bytes, padded_msg_len; + int align_fix; + int msg_offset; + unsigned char *ptr = (unsigned char *)buf->ptr + offset; + if (unused_bytes == NULL || missing_bytes == NULL) + return NULL; + + *missing_bytes = 0; + *unused_bytes = 0; + + PRN_DBG((TXT("entering with %d bytes.\n"), len)); + for (i = 0; i < len; i++, ptr++) { + switch (dev->rxState) { + case RxsWait_a5: + dev->rxState = + ((*ptr & 0xff) == 0xa5) ? RxsWait_5a : RxsWait_a5; + dev->rxPacket.msg_offset = + (unsigned long)ptr - (unsigned long)buf->ptr + 4; + break; + case RxsWait_5a: + if ((*ptr & 0xff) == 0x5a) { + dev->rxState = RxsWait_e7; + } else{ + dev->rxState = RxsWait_a5; + i--; + ptr--; /* re-scan current byte*/ + } + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + case RxsWait_e7: + if ((*ptr & 0xff) == 0xe7) { + dev->rxState = RxsWait_7e; + } else{ + dev->rxState = RxsWait_a5; + i--; + ptr--; /* re-scan current byte*/ + } + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + case RxsWait_7e: + if ((*ptr & 0xff) == 0x7e) { + dev->rxState = RxsTypeH; + } else{ + dev->rxState = RxsWait_a5; + i--; + ptr--; /* re-scan current byte*/ + } + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + case RxsTypeH: + dev->rxPacket.msg_buf = buf; + dev->rxPacket.msg_offset = + (unsigned long)ptr - (unsigned long)buf->ptr; + dev->rxState = RxsTypeL; + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + case RxsTypeL: + dev->rxState = RxsGetSrcId; + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + case RxsGetSrcId: + dev->rxState = RxsGetDstId; + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + case RxsGetDstId: + dev->rxState = RxsGetLenL; + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + case RxsGetLenL: + dev->rxState = RxsGetLenH; + dev->rxPacket.msg_len = (*ptr & 0xff); + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + case RxsGetLenH: + dev->rxState = RxsFlagsL; + dev->rxPacket.msg_len += (*ptr & 0xff) << 8; + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + case RxsFlagsL: + dev->rxState = RxsFlagsH; + dev->rxPacket.msg_flags = (*ptr & 0xff); + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + case RxsFlagsH: + dev->rxState = RxsData; + dev->rxPacket.msg_flags += (*ptr & 0xff) << 8; + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + case RxsData: + recieved_bytes = + len + offset - dev->rxPacket.msg_offset; + padded_msg_len = + ((dev->rxPacket.msg_len + 4 + SPI_PACKET_SIZE - + 1) >> SPI_PACKET_SIZE_BITS) << + SPI_PACKET_SIZE_BITS; + if (recieved_bytes < padded_msg_len) { + *unused_bytes = 0; + *missing_bytes = padded_msg_len - + recieved_bytes; + return buf; + } + dev->rxState = RxsWait_a5; + if (dev->cb.msg_found_cb) { + align_fix = 0; + if (dev->rxPacket. + msg_flags & MSG_HDR_FLAG_SPLIT_MSG_HDR) { + align_fix = + (dev->rxPacket. + msg_flags >> 8) & 0x3; + /* The FW aligned the message data + therefore - alignment bytes should be + thrown away. Throw the alignment bytes + by moving the header ahead over the + alignment bytes. */ + if (align_fix) { + int length; + ptr = (unsigned char *) + dev->rxPacket. + msg_buf->ptr + + dev->rxPacket.msg_offset; + + /* Restore header to original + state before alignment changes + */ + length = + (ptr[5] << 8) | ptr[4]; + length -= align_fix; + ptr[5] = length >> 8; + ptr[4] = length & 0xff; + /* Zero alignment flags */ + ptr[7] &= 0xfc; + + for (i = MSG_HDR_LEN - 1; + i >= 0; i--) { + ptr[i + align_fix] = + ptr[i]; + } + dev->rxPacket.msg_offset += + align_fix; + } + } + + PRN_DBG((TXT + ("Msg found and sent to callback func.\n"))); + + /* force all messages to start on + * 4-byte boundary */ + msg_offset = dev->rxPacket.msg_offset; + if (msg_offset & 0x3) { + msg_offset &= (~0x3); + memmove((unsigned char *) + (dev->rxPacket.msg_buf->ptr) + + msg_offset, + (unsigned char *) + (dev->rxPacket.msg_buf->ptr) + + dev->rxPacket.msg_offset, + dev->rxPacket.msg_len - + align_fix); + } + + *unused_bytes = + len + offset - dev->rxPacket.msg_offset - + dev->rxPacket.msg_len; + + /* In any case we got here - unused_bytes + * should not be 0 Because we want to force + * reading at least 256 after the end + * of any found message */ + if (*unused_bytes == 0) + *unused_bytes = -1; + + buf = smsspi_handle_unused_bytes_buf(dev, buf, + offset, len, *unused_bytes); + + dev->cb.msg_found_cb(dev->context, + dev->rxPacket.msg_buf, + msg_offset, + dev->rxPacket.msg_len - + align_fix); + *missing_bytes = 0; + return buf; + } else { + PRN_DBG((TXT + ("Msg found but no callback. therefore - thrown away.\n"))); + } + PRN_DBG((TXT("state %d.\n"), dev->rxState)); + break; + } + } + + if (dev->rxState == RxsWait_a5) { + *unused_bytes = 0; + *missing_bytes = 0; + return buf; + } else { + /* Workaround to corner case: if the last byte of the buffer + is "a5" (first byte of the preamble), the host thinks it should + send another 256 bytes. In case the a5 is the firmware + underflow byte, this will cause an infinite loop, so we check + for this case explicitly. */ + if (dev->rxState == RxsWait_5a) { + if ((*(ptr - 2) == 0xa5) || + (*((unsigned int *)(void *)(ptr-4)) == + *((unsigned int *)(void *)(ptr-8)))) { + dev->rxState = RxsWait_a5; + *unused_bytes = 0; + *missing_bytes = 0; + + return buf; + } + } + + if (dev->rxPacket.msg_offset >= (SPI_PACKET_SIZE + 4)) + /* adding 4 for the preamble. */ + { /*The packet will be copied to a new buffer + and rescaned by the state machine */ + struct _rx_buffer_st *tmp_buf = buf; + *unused_bytes = dev->rxState - RxsWait_a5; + tmp_buf = smsspi_handle_unused_bytes_buf(dev, buf, + offset, len, *unused_bytes); + dev->rxState = RxsWait_a5; + dev->cb.free_rx_buf(dev->context, buf); + *missing_bytes = 0; + return tmp_buf; + } else { + /* report missing bytes and continue + with message scan. */ + *unused_bytes = 0; + *missing_bytes = SPI_PACKET_SIZE; + return buf; + } + } +} + +void smsspi_common_transfer_msg(struct _spi_dev *dev, struct _spi_msg *txmsg, + int padding_allowed) +{ + int len, bytes_to_transfer; + unsigned long tx_phy_addr; + int missing_bytes, tx_bytes; + int offset, unused_bytes; + int align_block; + char *txbuf; + struct _rx_buffer_st *buf, *tmp_buf; + + len = 0; + if (!dev->cb.transfer_data_cb) { + PRN_ERR((TXT + ("function called while module is not initialized.\n"))); + return; + } + if (txmsg == 0) { + bytes_to_transfer = SPI_PACKET_SIZE; + txbuf = 0; + tx_phy_addr = 0; + tx_bytes = 0; + } else { + tx_bytes = txmsg->len; + if (padding_allowed) + bytes_to_transfer = + (((tx_bytes + SPI_PACKET_SIZE - + 1) >> SPI_PACKET_SIZE_BITS) << + SPI_PACKET_SIZE_BITS); + else + bytes_to_transfer = (((tx_bytes + 3) >> 2) << 2); + txbuf = txmsg->buf; + tx_phy_addr = txmsg->buf_phy_addr; + } + offset = 0; + unused_bytes = 0; + buf = + dev->cb.allocate_rx_buf(dev->context, + RX_PACKET_SIZE + SPI_PACKET_SIZE*2); + if (!buf) { + PRN_ERR((TXT("Failed to allocate RX buffer.\n"))); + return; + } + while (bytes_to_transfer || unused_bytes) { + if ((unused_bytes <= 0) && (bytes_to_transfer > 0)) { + len = min(bytes_to_transfer, RX_PACKET_SIZE); + PRN_DBG((TXT("transfering block of %d bytes\n"), len)); + dev->cb.transfer_data_cb(dev->phy_context, + (unsigned char *)txbuf, + tx_phy_addr, + (unsigned char *)buf->ptr + offset, + buf->phy_addr + offset, len); + } + + tmp_buf = + smsspi_common_find_msg(dev, buf, offset, len, + &unused_bytes, &missing_bytes); + if (bytes_to_transfer) + bytes_to_transfer -= len; + + if (tx_bytes) + tx_bytes -= len; + + if (missing_bytes) + offset += len; + + if (unused_bytes) { + /* In this case tmp_buf is a new buffer allocated + * in smsspi_common_find_msg + * and it already contains the unused bytes */ + if (unused_bytes > 0) { + align_block = + (((unused_bytes + SPI_PACKET_SIZE - + 1) >> SPI_PACKET_SIZE_BITS) << + SPI_PACKET_SIZE_BITS); + len = align_block; + } + offset = 0; + buf = tmp_buf; + } + if (tx_bytes <= 0) { + txbuf = 0; + tx_bytes = 0; + } + if (bytes_to_transfer < missing_bytes) { + bytes_to_transfer = + (((missing_bytes + SPI_PACKET_SIZE - + 1) >> SPI_PACKET_SIZE_BITS) << + SPI_PACKET_SIZE_BITS); + PRN_DBG((TXT + ("a message was found, adding bytes to transfer, txmsg %d, total %d\n") + , tx_bytes, bytes_to_transfer)); + } + } + dev->cb.free_rx_buf(dev->context, buf); +} + +int smsspicommon_init(struct _spi_dev *dev, void *context, void *phy_context, + struct _spi_dev_cb_st *cb) +{ + PRN_DBG((TXT("entering.\n"))); + if (cb->transfer_data_cb == 0 || + cb->msg_found_cb == 0 || + cb->allocate_rx_buf == 0 || cb->free_rx_buf == 0) { + PRN_ERR((TXT("Invalid input parameters of init routine.\n"))); + return -1; + } + dev->context = context; + dev->phy_context = phy_context; + memcpy(&dev->cb, cb, sizeof(struct _spi_dev_cb_st)); + dev->rxState = RxsWait_a5; + PRN_DBG((TXT("exiting.\n"))); + return 0; +} diff --git a/drivers/media/dvb/siano/smsspicommon.h b/drivers/media/dvb/siano/smsspicommon.h new file mode 100644 index 0000000..8976d04 --- /dev/null +++ b/drivers/media/dvb/siano/smsspicommon.h @@ -0,0 +1,96 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. +Copyright (C) 2006-2008, Uri Shkolnik + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + + 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, see <http://www.gnu.org/licenses/>. + +****************************************************************/ +#ifndef _SMS_SPI_COMMON_H_ +#define _SMS_SPI_COMMON_H_ + +#define RX_PACKET_SIZE 0x1000 +#define SPI_PACKET_SIZE_BITS 8 +#define SPI_PACKET_SIZE (1<<SPI_PACKET_SIZE_BITS) +#define SPI_MAX_CTRL_MSG_SIZE 0x100 + +#define MSG_HDR_FLAG_SPLIT_MSG_HDR 0x0004 +#define MSG_HDR_LEN 8 + +enum _spi_rx_state { + RxsWait_a5 = 0, + RxsWait_5a, + RxsWait_e7, + RxsWait_7e, + RxsTypeH, + RxsTypeL, + RxsGetSrcId, + RxsGetDstId, + RxsGetLenL, + RxsGetLenH, + RxsFlagsL, + RxsFlagsH, + RxsData +}; + +struct _rx_buffer_st { + void *ptr; + unsigned long phy_addr; +}; + +struct _rx_packet_request { + struct _rx_buffer_st *msg_buf; + int msg_offset; + int msg_len; + int msg_flags; +}; + +struct _spi_dev_cb_st { + void (*transfer_data_cb) (void *context, unsigned char *, + unsigned long, unsigned char *, unsigned long, int); + void (*msg_found_cb) (void *, void *, int, int); + struct _rx_buffer_st *(*allocate_rx_buf) (void *, int); + void (*free_rx_buf) (void *, struct _rx_buffer_st *); +}; + +struct _spi_dev { + void *context; + void *phy_context; + struct _spi_dev_cb_st cb; + char *rxbuf; + enum _spi_rx_state rxState; + struct _rx_packet_request rxPacket; + char *internal_tx_buf; +}; + +struct _spi_msg { + char *buf; + unsigned long buf_phy_addr; + int len; +}; + +void smsspi_common_transfer_msg(struct _spi_dev *dev, struct _spi_msg *txmsg, + int padding_allowed); +int smsspicommon_init(struct _spi_dev *dev, void *contex, void *phy_context, + struct _spi_dev_cb_st *cb); + +#if defined HEXDUMP_DEBUG && defined SPIBUS_DEBUG +/*! dump a human readable print of a binary buffer */ +void smsspi_khexdump(char *buf, int len); +#else +#define smsspi_khexdump(buf, len) +#endif + +#endif /*_SMS_SPI_COMMON_H_*/ diff --git a/drivers/media/dvb/siano/smsspidrv.c b/drivers/media/dvb/siano/smsspidrv.c new file mode 100644 index 0000000..4526cb8 --- /dev/null +++ b/drivers/media/dvb/siano/smsspidrv.c @@ -0,0 +1,472 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. + Copyright (C) 2006-2010, Erez Cohen + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + + 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, see <http://www.gnu.org/licenses/>. + +****************************************************************/ +/*! + \file spibusdrv.c + + \brief spi bus driver module + + This file contains implementation of the spi bus driver. +*/ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include "smscoreapi.h" +#include "smsspicommon.h" +#include "smsspiphy.h" + +#define SMS_INTR_PIN 19 /* 0 for nova sip, 26 for vega */ +#define TX_BUFFER_SIZE 0x200 +#define RX_BUFFER_SIZE (0x1000 + SPI_PACKET_SIZE + 0x100) +#define NUM_RX_BUFFERS 72 + +struct _spi_device_st { + struct _spi_dev dev; + void *phy_dev; + + struct completion write_operation; + struct list_head tx_queue; + int allocatedPackets; + int padding_allowed; + char *rxbuf; + + struct smscore_device_t *coredev; + struct list_head txqueue; + char *txbuf; + dma_addr_t txbuf_phy_addr; +}; + +struct _smsspi_txmsg { + struct list_head node; /*! internal management */ + void *buffer; + size_t size; + int alignment; + int add_preamble; + struct completion completion; + void (*prewrite) (void *); + void (*postwrite) (void *); +}; + +struct _Msg { + struct SmsMsgHdr_ST hdr; + u32 data[3]; +}; + +struct _spi_device_st *spi_dev; + +int sms_dbg; +static void spi_worker_thread(void *arg); +static DECLARE_WORK(spi_work_queue, (void *)spi_worker_thread); +static u8 smsspi_preamble[] = { 0xa5, 0x5a, 0xe7, 0x7e }; +static u8 smsspi_startup[] = { 0, 0, 0xde, 0xc1, 0xa5, 0x51, 0xf1, 0xed }; +static u32 sms_intr_pin = SMS_INTR_PIN; + +static u32 default_type = SMS_NOVA_B0; + +module_param_named(debug, sms_dbg, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(debug, "set debug level (info=1, adv=2 (or-able))"); + +module_param(default_type, int, S_IRUGO); +MODULE_PARM_DESC(default_type, "default SMS device type."); + +module_param(sms_intr_pin, int, S_IRUGO); +MODULE_PARM_DESC(sms_intr_pin, "interrupt pin number used by SMS chip."); + +module_param(host_intr_pin, int, S_IRUGO); +MODULE_PARM_DESC(host_intr_pin, "interrupt pin number used by Host."); + +/******************************************/ +static void spi_worker_thread(void *arg) +{ + struct _spi_device_st *spi_device = spi_dev; + struct _smsspi_txmsg *msg = NULL; + struct _spi_msg txmsg; + + sms_info("worker start\n"); + do { + /* do we have a msg to write ? */ + if (!msg && !list_empty(&spi_device->txqueue)) + msg = (struct _smsspi_txmsg *) + list_entry(spi_device->txqueue. + next, struct _smsspi_txmsg, node); + + if (msg) { + if (msg->add_preamble) { + txmsg.len = + min(msg->size + sizeof(smsspi_preamble), + (size_t) TX_BUFFER_SIZE); + txmsg.buf = spi_device->txbuf; + txmsg.buf_phy_addr = + spi_device->txbuf_phy_addr; + memcpy(txmsg.buf, smsspi_preamble, + sizeof(smsspi_preamble)); + memcpy(&txmsg.buf[sizeof(smsspi_preamble)], + msg->buffer, + txmsg.len - sizeof(smsspi_preamble)); + msg->add_preamble = 0; + msg->buffer += + txmsg.len - sizeof(smsspi_preamble); + msg->size -= + txmsg.len - sizeof(smsspi_preamble); + /* zero out the rest of aligned buffer */ + memset(&txmsg.buf[txmsg.len], 0, + TX_BUFFER_SIZE - txmsg.len); + smsspi_common_transfer_msg(&spi_device->dev, + &txmsg, 1); + } else { + txmsg.len = + min(msg->size, (size_t) TX_BUFFER_SIZE); + txmsg.buf = spi_device->txbuf; + txmsg.buf_phy_addr = + spi_device->txbuf_phy_addr; + memcpy(txmsg.buf, msg->buffer, txmsg.len); + + msg->buffer += txmsg.len; + msg->size -= txmsg.len; + /* zero out the rest of aligned buffer */ + memset(&txmsg.buf[txmsg.len], 0, + TX_BUFFER_SIZE - txmsg.len); + smsspi_common_transfer_msg(&spi_device->dev, + &txmsg, 0); + } + + } else { + smsspi_common_transfer_msg(&spi_device->dev, NULL, 1); + } + + /* if there was write, have we finished ? */ + if (msg && !msg->size) { + /* call postwrite call back */ + if (msg->postwrite) + msg->postwrite(spi_device); + + list_del(&msg->node); + complete(&msg->completion); + msg = NULL; + } + /* if there was read, did we read anything ? */ + + } while (!list_empty(&spi_device->txqueue) || msg); + + sms_info("worker end\n"); + +} + +static void msg_found(void *context, void *buf, int offset, int len) +{ + struct _spi_device_st *spi_device = (struct _spi_device_st *) context; + struct smscore_buffer_t *cb = + (struct smscore_buffer_t + *)(container_of(buf, struct smscore_buffer_t, p)); + + sms_info("entering\n"); + cb->offset = offset; + cb->size = len; + /* sms_err ("buffer %p is sent back to core databuf=%p, + offset=%d.\n", cb, cb->p, cb->offset); */ + smscore_onresponse(spi_device->coredev, cb); + + sms_info("exiting\n"); + +} + +static void smsspi_int_handler(void *context) +{ + sms_info("interrupt\n"); + PREPARE_WORK(&spi_work_queue, (void *)spi_worker_thread); + schedule_work(&spi_work_queue); +} + +static int smsspi_queue_message_and_wait(struct _spi_device_st *spi_device, + struct _smsspi_txmsg *msg) +{ + init_completion(&msg->completion); + list_add_tail(&msg->node, &spi_device->txqueue); + schedule_work(&spi_work_queue); + wait_for_completion(&msg->completion); + + return 0; +} + +static int smsspi_preload(void *context) +{ + struct _smsspi_txmsg msg; + struct _spi_device_st *spi_device = (struct _spi_device_st *) context; + struct _Msg Msg = { + { + MSG_SMS_SPI_INT_LINE_SET_REQ, 0, HIF_TASK, + sizeof(struct _Msg), 0}, { + 0, sms_intr_pin, 0} + }; + int rc; + + sms_err("preparing for download\n"); + prepareForFWDnl(spi_device->phy_dev); + sms_err("Sending SPI init sequence\n"); + msg.buffer = smsspi_startup; + msg.size = sizeof(smsspi_startup); + msg.alignment = 4; + msg.add_preamble = 0; + msg.prewrite = NULL; /* smsspiphy_reduce_clock; */ + msg.postwrite = NULL; /* smsspiphy_restore_clock; */ + + rc = smsspi_queue_message_and_wait(context, &msg); + if (rc < 0) { + sms_err("smsspi_queue_message_and_wait error, rc = %d\n", rc); + return rc; + } + + sms_debug("sending MSG_SMS_SPI_INT_LINE_SET_REQ"); + sms_info("Sending SPI Set Interrupt command sequence\n"); + msg.buffer = &Msg; + msg.size = sizeof(Msg); + msg.alignment = SPI_PACKET_SIZE; + msg.add_preamble = 1; + + rc = smsspi_queue_message_and_wait(context, &msg); + if (rc < 0) { + sms_err("set interrupt line failed, rc = %d\n", rc); + return rc; + } + + return rc; +} + +static int smsspi_postload(void *context) +{ + struct _spi_device_st *spi_device = (struct _spi_device_st *) context; + int mode = smscore_registry_getmode(spi_device->coredev->devpath); + if ((mode != DEVICE_MODE_ISDBT) && + (mode != DEVICE_MODE_ISDBT_BDA)) { + fwDnlComplete(spi_device->phy_dev, 0); + + } + + return 0; +} + +static int smsspi_write(void *context, void *txbuf, size_t len) +{ + struct _smsspi_txmsg msg; + msg.buffer = txbuf; + msg.size = len; + msg.prewrite = NULL; + msg.postwrite = NULL; + if (len > 0x1000) { + /* The FW is the only long message. Do not add preamble, + and do not padd it */ + msg.alignment = 4; + msg.add_preamble = 0; + msg.prewrite = smschipreset; + } else { + msg.alignment = SPI_PACKET_SIZE; + msg.add_preamble = 1; + } + sms_info("Writing message to SPI.\n"); + sms_info("msg hdr: 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x.\n", + ((u8 *) txbuf)[0], ((u8 *) txbuf)[1], ((u8 *) txbuf)[2], + ((u8 *) txbuf)[3], ((u8 *) txbuf)[4], ((u8 *) txbuf)[5], + ((u8 *) txbuf)[6], ((u8 *) txbuf)[7]); + return smsspi_queue_message_and_wait(context, &msg); +} + +struct _rx_buffer_st *allocate_rx_buf(void *context, int size) +{ + struct smscore_buffer_t *buf; + struct _spi_device_st *spi_device = (struct _spi_device_st *) context; + if (size > RX_BUFFER_SIZE) { + sms_err("Requested size is bigger than max buffer size.\n"); + return NULL; + } + buf = smscore_getbuffer(spi_device->coredev); + sms_info("Recieved Rx buf %p physical 0x%x (contained in %p)\n", + buf->p, buf->phys, buf); + + /* note: this is not mistake! the rx_buffer_st is identical to part of + smscore_buffer_t and we return the address of the start of the + identical part */ + return (struct _rx_buffer_st *) &buf->p; +} + +static void free_rx_buf(void *context, struct _rx_buffer_st *buf) +{ + struct _spi_device_st *spi_device = (struct _spi_device_st *) context; + struct smscore_buffer_t *cb = + (struct smscore_buffer_t + *)(container_of(((void *)buf), struct smscore_buffer_t, p)); + sms_info("buffer %p is released.\n", cb); + smscore_putbuffer(spi_device->coredev, cb); +} + +/*! Release device STUB + +\param[in] dev: device control block +\return void +*/ +static void smsspi_release(struct device *dev) +{ + sms_info("nothing to do\n"); + /* Nothing to release */ +} + +static struct platform_device smsspi_device = { + .name = "smsspi", + .id = 1, + .dev = { + .release = smsspi_release, + }, +}; + +static int __init smsspi_module_init(void) +{ + struct smsdevice_params_t params; + int ret; + struct _spi_device_st *spi_device; + struct _spi_dev_cb_st common_cb; + + sms_info("entering\n"); + + spi_device = + kmalloc(sizeof(struct _spi_device_st), GFP_KERNEL); + spi_dev = spi_device; + + INIT_LIST_HEAD(&spi_device->txqueue); + + ret = platform_device_register(&smsspi_device); + if (ret < 0) { + sms_err("platform_device_register failed\n"); + return ret; + } + + spi_device->txbuf = + dma_alloc_coherent(NULL, TX_BUFFER_SIZE, + &spi_device->txbuf_phy_addr, + GFP_KERNEL | GFP_DMA); + if (!spi_device->txbuf) { + printk(KERN_INFO "%s dma_alloc_coherent(...) failed\n", + __func__); + ret = -ENOMEM; + goto txbuf_error; + } + + spi_device->phy_dev = + smsspiphy_init(NULL, smsspi_int_handler, spi_device); + if (spi_device->phy_dev == 0) { + printk(KERN_INFO "%s smsspiphy_init(...) failed\n", __func__); + goto phy_error; + } + + common_cb.allocate_rx_buf = allocate_rx_buf; + common_cb.free_rx_buf = free_rx_buf; + common_cb.msg_found_cb = msg_found; + common_cb.transfer_data_cb = smsspibus_xfer; + + ret = + smsspicommon_init(&spi_device->dev, spi_device, + spi_device->phy_dev, &common_cb); + if (ret) { + printk(KERN_INFO "%s smsspiphy_init(...) failed\n", __func__); + goto common_error; + } + + /* register in smscore */ + memset(¶ms, 0, sizeof(params)); + params.context = spi_device; + params.device = &smsspi_device.dev; + params.buffer_size = RX_BUFFER_SIZE; + params.num_buffers = NUM_RX_BUFFERS; + params.flags = SMS_DEVICE_NOT_READY; + params.sendrequest_handler = smsspi_write; + strcpy(params.devpath, "spi"); + params.device_type = default_type; + + params.flags = + SMS_DEVICE_FAMILY2 | SMS_DEVICE_NOT_READY; + params.preload_handler = smsspi_preload; + params.postload_handler = smsspi_postload; + sms_info("registering spi device type %d", params.device_type); + ret = smscore_register_device(¶ms, &spi_device->coredev); + if (ret < 0) { + printk(KERN_INFO "%s smscore_register_device(...) failed\n", + __func__); + goto reg_device_error; + } + + ret = smscore_start_device(spi_device->coredev); + if (ret < 0) { + printk(KERN_INFO "%s smscore_start_device(...) failed\n", + __func__); + goto start_device_error; + } + + sms_info("exiting\n"); + return 0; + +start_device_error: + smscore_unregister_device(spi_device->coredev); + +reg_device_error: + +common_error: + smsspiphy_deinit(spi_device->phy_dev); + +phy_error: + dma_free_coherent(NULL, TX_BUFFER_SIZE, spi_device->txbuf, + spi_device->txbuf_phy_addr); + +txbuf_error: + platform_device_unregister(&smsspi_device); + + sms_info("exiting error %d\n", ret); + + return ret; +} + +static void __exit smsspi_module_exit(void) +{ + struct _spi_device_st *spi_device = spi_dev; + sms_info("entering\n"); + + /* stop interrupts */ + smsspiphy_deinit(spi_device->phy_dev); + smscore_unregister_device(spi_device->coredev); + + dma_free_coherent(NULL, TX_BUFFER_SIZE, spi_device->txbuf, + spi_device->txbuf_phy_addr); + + platform_device_unregister(&smsspi_device); + sms_info("exiting\n"); +} + + +module_init(smsspi_module_init); +module_exit(smsspi_module_exit); + +MODULE_DESCRIPTION("Siano MDTV SPI device driver"); +MODULE_AUTHOR("Siano Mobile Silicon, Inc. (doronc@xxxxxxxxxxxx)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/siano/smsspiphy.c b/drivers/media/dvb/siano/smsspiphy.c new file mode 100644 index 0000000..f55131c --- /dev/null +++ b/drivers/media/dvb/siano/smsspiphy.c @@ -0,0 +1,218 @@ +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/dma-mapping.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> + +#include "smscoreapi.h" +#include "smsspiphy.h" + +#define MAX_SPEED_DURING_DOWNLOAD 6000000 +#define MAX_SPEED_DURING_WORK 6000000 +#define SPI_PACKET_SIZE 256 + +int sms_spi_interrupt = 135; +module_param_named(debug, sms_spi_interrupt, int, 0644); +MODULE_PARM_DESC(debug, "set interrupt gpio pin for spi device."); + + +int spi_max_speed = MAX_SPEED_DURING_WORK; + +struct sms_spi { + struct spi_device *spi_dev; + char *zero_txbuf; + dma_addr_t zero_txbuf_phy_addr; + int bus_speed; + void (*interruptHandler) (void *); + void *intr_context; +}; + +/*! +invert the endianness of a single 32it integer + +\param[in] u: word to invert + +\return the inverted word +*/ +static inline u32 invert_bo(u32 u) +{ + return ((u & 0xff) << 24) | ((u & 0xff00) << 8) | ((u & 0xff0000) >> 8) + | ((u & 0xff000000) >> 24); +} + +/*! +invert the endianness of a data buffer + +\param[in] buf: buffer to invert +\param[in] len: buffer length + +\return the inverted word +*/ + + +static int invert_endianness(char *buf, int len) +{ + int i; + u32 *ptr = (u32 *) buf; + + len = (len + 3) / 4; + for (i = 0; i < len; i++, ptr++) + *ptr = invert_bo(*ptr); + + return 4 * ((len + 3) & (~3)); +} + +static irqreturn_t spibus_interrupt(int irq, void *context) +{ + struct sms_spi *sms_spi = (struct sms_spi *)context; + if (sms_spi->interruptHandler) + sms_spi->interruptHandler(sms_spi->intr_context); + return IRQ_HANDLED; + +} + +void prepareForFWDnl(void *context) +{ + /*Reduce clock rate for FW download*/ + struct sms_spi *sms_spi = (struct sms_spi *)context; + sms_spi->bus_speed = MAX_SPEED_DURING_DOWNLOAD; + sms_err("Start FW download."); + msleep(100); + sms_err("done sleeping."); +} + +void fwDnlComplete(void *context, int App) +{ + /*Set clock rate for working mode*/ + struct sms_spi *sms_spi = (struct sms_spi *)context; + sms_spi->bus_speed = spi_max_speed; + sms_err("FW download complete."); + msleep(100); +} + + +void smsspibus_xfer(void *context, unsigned char *txbuf, + unsigned long txbuf_phy_addr, unsigned char *rxbuf, + unsigned long rxbuf_phy_addr, int len) +{ + struct sms_spi *sms_spi = (struct sms_spi *)context; + struct spi_message msg; + struct spi_transfer xfer = { + .tx_buf = txbuf, + .rx_buf = rxbuf, + .len = len, + .tx_dma = txbuf_phy_addr, + .rx_dma = rxbuf_phy_addr, + .cs_change = 0, + .speed_hz = sms_spi->bus_speed, + .bits_per_word = 0, + }; + + if (txbuf) + invert_endianness(txbuf, len); + + + if (!txbuf) { + xfer.tx_buf = sms_spi->zero_txbuf; + xfer.tx_dma = sms_spi->zero_txbuf_phy_addr; + + } + + spi_message_init(&msg); + msg.is_dma_mapped = 1; + spi_message_add_tail(&xfer, &msg); + spi_sync(sms_spi->spi_dev, &msg); + invert_endianness(rxbuf, len); + +} + + + +void *smsspiphy_init(void *context, void (*smsspi_interruptHandler) (void *), + void *intr_context) +{ + int ret; + struct sms_spi *sms_spi; + struct spi_device *sms_device; + + struct spi_master *master = spi_busnum_to_master(3); + struct spi_board_info sms_chip = { + .modalias = "SmsSPI", + .platform_data = NULL, + .controller_data = NULL, + .irq = 0, /*OMAP_GPIO_IRQ(4)*/ + .max_speed_hz = spi_max_speed, + .bus_num = 3, + .chip_select = 1, + .mode = SPI_MODE_0, + }; + + sms_err("sms_debug = %d\n", sms_dbg); + + sms_device = spi_new_device(master, &sms_chip); + if (!sms_device) { + sms_err("Failed on allocating new SPI device for SMS"); + return NULL; + } + sms_device->bits_per_word = 32; + if (spi_setup(sms_device)) { + sms_err("SMS device setup failed"); + return NULL; + } + + sms_spi = kzalloc(sizeof(struct sms_spi), GFP_KERNEL); + + sms_spi->zero_txbuf = dma_alloc_coherent(NULL, SPI_PACKET_SIZE, + &sms_spi->zero_txbuf_phy_addr, + GFP_KERNEL | GFP_DMA); + if (!sms_spi->zero_txbuf) { + sms_err("dma_alloc_coherent(...) failed\n"); + kfree(sms_spi); + return NULL; + } + memset(sms_spi->zero_txbuf, 0, SPI_PACKET_SIZE); + sms_spi->interruptHandler = smsspi_interruptHandler; + sms_spi->intr_context = intr_context; + + + if ((gpio_request(sms_spi_interrupt, "SMSSPI") == 0) && + (gpio_direction_input(sms_spi_interrupt) == 0)) { + gpio_export(sms_spi_interrupt, 0); + } + + irq_set_irq_type(gpio_to_irq(sms_spi_interrupt), + IRQ_TYPE_EDGE_FALLING); + ret = request_irq(gpio_to_irq(sms_spi_interrupt), + spibus_interrupt, IRQF_TRIGGER_FALLING, + "SMSSPI", sms_spi); + if (ret) { + sms_err("Could not get intrpt for SMS device. status =%d\n", + ret); + return NULL; + } + + sms_spi->spi_dev = sms_device; + sms_spi->bus_speed = spi_max_speed; + sms_err("after init sms_spi=0x%x, spi_dev = 0x%x", + (int)sms_spi, (int)sms_spi->spi_dev); + + return sms_spi; +} + +void smsspiphy_deinit(void *context) +{ + struct sms_spi *sms_spi = (struct sms_spi *)context; + printk(KERN_INFO "smsspiphy_deinit\n"); + kfree(sms_spi); + +} + +void smschipreset(void *context) +{ + sms_err("sms chip reset"); +} + + + diff --git a/drivers/media/dvb/siano/smsspiphy.h b/drivers/media/dvb/siano/smsspiphy.h new file mode 100644 index 0000000..a70198b --- /dev/null +++ b/drivers/media/dvb/siano/smsspiphy.h @@ -0,0 +1,38 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. +Copyright (C) 2006-2008, Uri Shkolnik + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + + 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, see <http://www.gnu.org/licenses/>. + +****************************************************************/ + +#ifndef __SMS_SPI_PHY_H__ +#define __SMS_SPI_PHY_H__ + +extern int host_intr_pin; +extern int sms_dbg; +void smsspibus_xfer(void *context, unsigned char *txbuf, + unsigned long txbuf_phy_addr, unsigned char *rxbuf, + unsigned long rxbuf_phy_addr, int len); +void *smsspiphy_init(void *context, void (*smsspi_interruptHandler) (void *), + void *intr_context); +void smsspiphy_deinit(void *context); +void smschipreset(void *context); +void WriteFWtoStellar(void *pSpiPhy, unsigned char *pFW, unsigned long Len); +void prepareForFWDnl(void *pSpiPhy); +void fwDnlComplete(void *context, int App); + +#endif /* __SMS_SPI_PHY_H__ */ -- 1.7.4.1 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html