diff options
author | Mauro Carvalho Chehab <mchehab@redhat.com> | 2012-06-14 16:35:56 -0300 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2012-08-13 23:26:31 -0300 |
commit | 786baecfe78f8e25547c628b48a60fc8e5636056 (patch) | |
tree | bb4101ce010f55cbbfc6d93ee13b44b496a028cc /drivers/media/usb/siano | |
parent | 616300bd51bee80d2d122c205866aa4c20adbaa8 (diff) | |
download | linux-stable-786baecfe78f8e25547c628b48a60fc8e5636056.tar.gz linux-stable-786baecfe78f8e25547c628b48a60fc8e5636056.tar.bz2 linux-stable-786baecfe78f8e25547c628b48a60fc8e5636056.zip |
[media] dvb-usb: move it to drivers/media/usb/dvb-usb
As media/dvb will be removed, move it to a proper place.
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/usb/siano')
-rw-r--r-- | drivers/media/usb/siano/Kconfig | 34 | ||||
-rw-r--r-- | drivers/media/usb/siano/Makefile | 11 | ||||
-rw-r--r-- | drivers/media/usb/siano/sms-cards.c | 311 | ||||
-rw-r--r-- | drivers/media/usb/siano/sms-cards.h | 123 | ||||
-rw-r--r-- | drivers/media/usb/siano/smscoreapi.c | 1637 | ||||
-rw-r--r-- | drivers/media/usb/siano/smscoreapi.h | 775 | ||||
-rw-r--r-- | drivers/media/usb/siano/smsdvb.c | 1078 | ||||
-rw-r--r-- | drivers/media/usb/siano/smsendian.c | 103 | ||||
-rw-r--r-- | drivers/media/usb/siano/smsendian.h | 32 | ||||
-rw-r--r-- | drivers/media/usb/siano/smsir.c | 114 | ||||
-rw-r--r-- | drivers/media/usb/siano/smsir.h | 55 | ||||
-rw-r--r-- | drivers/media/usb/siano/smssdio.c | 365 | ||||
-rw-r--r-- | drivers/media/usb/siano/smsusb.c | 568 |
13 files changed, 5206 insertions, 0 deletions
diff --git a/drivers/media/usb/siano/Kconfig b/drivers/media/usb/siano/Kconfig new file mode 100644 index 000000000000..bc6456eb2c4f --- /dev/null +++ b/drivers/media/usb/siano/Kconfig @@ -0,0 +1,34 @@ +# +# Siano Mobile Silicon Digital TV device configuration +# + +config SMS_SIANO_MDTV + tristate "Siano SMS1xxx based MDTV receiver" + depends on DVB_CORE && RC_CORE && HAS_DMA + ---help--- + Choose Y or M here if you have MDTV receiver with a Siano chipset. + + To compile this driver as a module, choose M here + (The module will be called smsmdtv). + + Further documentation on this driver can be found on the WWW + at http://www.siano-ms.com/ + +if SMS_SIANO_MDTV +menu "Siano module components" + +# Hardware interfaces support + +config SMS_USB_DRV + tristate "USB interface support" + depends on DVB_CORE && USB + ---help--- + Choose if you would like to have Siano's support for USB interface + +config SMS_SDIO_DRV + tristate "SDIO interface support" + depends on DVB_CORE && MMC + ---help--- + Choose if you would like to have Siano's support for SDIO interface +endmenu +endif # SMS_SIANO_MDTV diff --git a/drivers/media/usb/siano/Makefile b/drivers/media/usb/siano/Makefile new file mode 100644 index 000000000000..14756bdb6eaa --- /dev/null +++ b/drivers/media/usb/siano/Makefile @@ -0,0 +1,11 @@ + +smsmdtv-objs := smscoreapi.o sms-cards.o smsendian.o smsir.o + +obj-$(CONFIG_SMS_SIANO_MDTV) += smsmdtv.o smsdvb.o +obj-$(CONFIG_SMS_USB_DRV) += smsusb.o +obj-$(CONFIG_SMS_SDIO_DRV) += smssdio.o + +ccflags-y += -Idrivers/media/dvb-core + +ccflags-y += $(extra-cflags-y) $(extra-cflags-m) + diff --git a/drivers/media/usb/siano/sms-cards.c b/drivers/media/usb/siano/sms-cards.c new file mode 100644 index 000000000000..680c781c8dd6 --- /dev/null +++ b/drivers/media/usb/siano/sms-cards.c @@ -0,0 +1,311 @@ +/* + * Card-specific functions for the Siano SMS1xxx USB dongle + * + * Copyright (c) 2008 Michael Krufky <mkrufky@linuxtv.org> + * + * 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; + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "sms-cards.h" +#include "smsir.h" +#include <linux/module.h> + +static int sms_dbg; +module_param_named(cards_dbg, sms_dbg, int, 0644); +MODULE_PARM_DESC(cards_dbg, "set debug level (info=1, adv=2 (or-able))"); + +static struct sms_board sms_boards[] = { + [SMS_BOARD_UNKNOWN] = { + .name = "Unknown board", + }, + [SMS1XXX_BOARD_SIANO_STELLAR] = { + .name = "Siano Stellar Digital Receiver", + .type = SMS_STELLAR, + }, + [SMS1XXX_BOARD_SIANO_NOVA_A] = { + .name = "Siano Nova A Digital Receiver", + .type = SMS_NOVA_A0, + }, + [SMS1XXX_BOARD_SIANO_NOVA_B] = { + .name = "Siano Nova B Digital Receiver", + .type = SMS_NOVA_B0, + }, + [SMS1XXX_BOARD_SIANO_VEGA] = { + .name = "Siano Vega Digital Receiver", + .type = SMS_VEGA, + }, + [SMS1XXX_BOARD_HAUPPAUGE_CATAMOUNT] = { + .name = "Hauppauge Catamount", + .type = SMS_STELLAR, + .fw[DEVICE_MODE_DVBT_BDA] = "sms1xxx-stellar-dvbt-01.fw", + }, + [SMS1XXX_BOARD_HAUPPAUGE_OKEMO_A] = { + .name = "Hauppauge Okemo-A", + .type = SMS_NOVA_A0, + .fw[DEVICE_MODE_DVBT_BDA] = "sms1xxx-nova-a-dvbt-01.fw", + }, + [SMS1XXX_BOARD_HAUPPAUGE_OKEMO_B] = { + .name = "Hauppauge Okemo-B", + .type = SMS_NOVA_B0, + .fw[DEVICE_MODE_DVBT_BDA] = "sms1xxx-nova-b-dvbt-01.fw", + }, + [SMS1XXX_BOARD_HAUPPAUGE_WINDHAM] = { + .name = "Hauppauge WinTV MiniStick", + .type = SMS_NOVA_B0, + .fw[DEVICE_MODE_ISDBT_BDA] = "sms1xxx-hcw-55xxx-isdbt-02.fw", + .fw[DEVICE_MODE_DVBT_BDA] = "sms1xxx-hcw-55xxx-dvbt-02.fw", + .rc_codes = RC_MAP_HAUPPAUGE, + .board_cfg.leds_power = 26, + .board_cfg.led0 = 27, + .board_cfg.led1 = 28, + .board_cfg.ir = 9, + .led_power = 26, + .led_lo = 27, + .led_hi = 28, + }, + [SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD] = { + .name = "Hauppauge WinTV MiniCard", + .type = SMS_NOVA_B0, + .fw[DEVICE_MODE_DVBT_BDA] = "sms1xxx-hcw-55xxx-dvbt-02.fw", + .lna_ctrl = 29, + .board_cfg.foreign_lna0_ctrl = 29, + .rf_switch = 17, + .board_cfg.rf_switch_uhf = 17, + }, + [SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2] = { + .name = "Hauppauge WinTV MiniCard", + .type = SMS_NOVA_B0, + .fw[DEVICE_MODE_DVBT_BDA] = "sms1xxx-hcw-55xxx-dvbt-02.fw", + .lna_ctrl = -1, + }, + [SMS1XXX_BOARD_SIANO_NICE] = { + /* 11 */ + .name = "Siano Nice Digital Receiver", + .type = SMS_NOVA_B0, + }, + [SMS1XXX_BOARD_SIANO_VENICE] = { + /* 12 */ + .name = "Siano Venice Digital Receiver", + .type = SMS_VEGA, + }, +}; + +struct sms_board *sms_get_board(unsigned id) +{ + BUG_ON(id >= ARRAY_SIZE(sms_boards)); + + return &sms_boards[id]; +} +EXPORT_SYMBOL_GPL(sms_get_board); +static inline void sms_gpio_assign_11xx_default_led_config( + struct smscore_gpio_config *pGpioConfig) { + pGpioConfig->Direction = SMS_GPIO_DIRECTION_OUTPUT; + pGpioConfig->InputCharacteristics = + SMS_GPIO_INPUT_CHARACTERISTICS_NORMAL; + pGpioConfig->OutputDriving = SMS_GPIO_OUTPUT_DRIVING_4mA; + pGpioConfig->OutputSlewRate = SMS_GPIO_OUTPUT_SLEW_RATE_0_45_V_NS; + pGpioConfig->PullUpDown = SMS_GPIO_PULL_UP_DOWN_NONE; +} + +int sms_board_event(struct smscore_device_t *coredev, + enum SMS_BOARD_EVENTS gevent) { + struct smscore_gpio_config MyGpioConfig; + + sms_gpio_assign_11xx_default_led_config(&MyGpioConfig); + + switch (gevent) { + case BOARD_EVENT_POWER_INIT: /* including hotplug */ + break; /* BOARD_EVENT_BIND */ + + case BOARD_EVENT_POWER_SUSPEND: + break; /* BOARD_EVENT_POWER_SUSPEND */ + + case BOARD_EVENT_POWER_RESUME: + break; /* BOARD_EVENT_POWER_RESUME */ + + case BOARD_EVENT_BIND: + break; /* BOARD_EVENT_BIND */ + + case BOARD_EVENT_SCAN_PROG: + break; /* BOARD_EVENT_SCAN_PROG */ + case BOARD_EVENT_SCAN_COMP: + break; /* BOARD_EVENT_SCAN_COMP */ + case BOARD_EVENT_EMERGENCY_WARNING_SIGNAL: + break; /* BOARD_EVENT_EMERGENCY_WARNING_SIGNAL */ + case BOARD_EVENT_FE_LOCK: + break; /* BOARD_EVENT_FE_LOCK */ + case BOARD_EVENT_FE_UNLOCK: + break; /* BOARD_EVENT_FE_UNLOCK */ + case BOARD_EVENT_DEMOD_LOCK: + break; /* BOARD_EVENT_DEMOD_LOCK */ + case BOARD_EVENT_DEMOD_UNLOCK: + break; /* BOARD_EVENT_DEMOD_UNLOCK */ + case BOARD_EVENT_RECEPTION_MAX_4: + break; /* BOARD_EVENT_RECEPTION_MAX_4 */ + case BOARD_EVENT_RECEPTION_3: + break; /* BOARD_EVENT_RECEPTION_3 */ + case BOARD_EVENT_RECEPTION_2: + break; /* BOARD_EVENT_RECEPTION_2 */ + case BOARD_EVENT_RECEPTION_1: + break; /* BOARD_EVENT_RECEPTION_1 */ + case BOARD_EVENT_RECEPTION_LOST_0: + break; /* BOARD_EVENT_RECEPTION_LOST_0 */ + case BOARD_EVENT_MULTIPLEX_OK: + break; /* BOARD_EVENT_MULTIPLEX_OK */ + case BOARD_EVENT_MULTIPLEX_ERRORS: + break; /* BOARD_EVENT_MULTIPLEX_ERRORS */ + + default: + sms_err("Unknown SMS board event"); + break; + } + return 0; +} +EXPORT_SYMBOL_GPL(sms_board_event); + +static int sms_set_gpio(struct smscore_device_t *coredev, int pin, int enable) +{ + int lvl, ret; + u32 gpio; + struct smscore_config_gpio gpioconfig = { + .direction = SMS_GPIO_DIRECTION_OUTPUT, + .pullupdown = SMS_GPIO_PULLUPDOWN_NONE, + .inputcharacteristics = SMS_GPIO_INPUTCHARACTERISTICS_NORMAL, + .outputslewrate = SMS_GPIO_OUTPUTSLEWRATE_FAST, + .outputdriving = SMS_GPIO_OUTPUTDRIVING_4mA, + }; + + if (pin == 0) + return -EINVAL; + + if (pin < 0) { + /* inverted gpio */ + gpio = pin * -1; + lvl = enable ? 0 : 1; + } else { + gpio = pin; + lvl = enable ? 1 : 0; + } + + ret = smscore_configure_gpio(coredev, gpio, &gpioconfig); + if (ret < 0) + return ret; + + return smscore_set_gpio(coredev, gpio, lvl); +} + +int sms_board_setup(struct smscore_device_t *coredev) +{ + int board_id = smscore_get_board_id(coredev); + struct sms_board *board = sms_get_board(board_id); + + switch (board_id) { + case SMS1XXX_BOARD_HAUPPAUGE_WINDHAM: + /* turn off all LEDs */ + sms_set_gpio(coredev, board->led_power, 0); + sms_set_gpio(coredev, board->led_hi, 0); + sms_set_gpio(coredev, board->led_lo, 0); + break; + case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2: + case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD: + /* turn off LNA */ + sms_set_gpio(coredev, board->lna_ctrl, 0); + break; + } + return 0; +} +EXPORT_SYMBOL_GPL(sms_board_setup); + +int sms_board_power(struct smscore_device_t *coredev, int onoff) +{ + int board_id = smscore_get_board_id(coredev); + struct sms_board *board = sms_get_board(board_id); + + switch (board_id) { + case SMS1XXX_BOARD_HAUPPAUGE_WINDHAM: + /* power LED */ + sms_set_gpio(coredev, + board->led_power, onoff ? 1 : 0); + break; + case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2: + case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD: + /* LNA */ + if (!onoff) + sms_set_gpio(coredev, board->lna_ctrl, 0); + break; + } + return 0; +} +EXPORT_SYMBOL_GPL(sms_board_power); + +int sms_board_led_feedback(struct smscore_device_t *coredev, int led) +{ + int board_id = smscore_get_board_id(coredev); + struct sms_board *board = sms_get_board(board_id); + + /* dont touch GPIO if LEDs are already set */ + if (smscore_led_state(coredev, -1) == led) + return 0; + + switch (board_id) { + case SMS1XXX_BOARD_HAUPPAUGE_WINDHAM: + sms_set_gpio(coredev, + board->led_lo, (led & SMS_LED_LO) ? 1 : 0); + sms_set_gpio(coredev, + board->led_hi, (led & SMS_LED_HI) ? 1 : 0); + + smscore_led_state(coredev, led); + break; + } + return 0; +} +EXPORT_SYMBOL_GPL(sms_board_led_feedback); + +int sms_board_lna_control(struct smscore_device_t *coredev, int onoff) +{ + int board_id = smscore_get_board_id(coredev); + struct sms_board *board = sms_get_board(board_id); + + sms_debug("%s: LNA %s", __func__, onoff ? "enabled" : "disabled"); + + switch (board_id) { + case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2: + case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD: + sms_set_gpio(coredev, + board->rf_switch, onoff ? 1 : 0); + return sms_set_gpio(coredev, + board->lna_ctrl, onoff ? 1 : 0); + } + return -EINVAL; +} +EXPORT_SYMBOL_GPL(sms_board_lna_control); + +int sms_board_load_modules(int id) +{ + switch (id) { + case SMS1XXX_BOARD_HAUPPAUGE_CATAMOUNT: + case SMS1XXX_BOARD_HAUPPAUGE_OKEMO_A: + case SMS1XXX_BOARD_HAUPPAUGE_OKEMO_B: + case SMS1XXX_BOARD_HAUPPAUGE_WINDHAM: + case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD: + case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2: + request_module("smsdvb"); + break; + default: + /* do nothing */ + break; + } + return 0; +} +EXPORT_SYMBOL_GPL(sms_board_load_modules); diff --git a/drivers/media/usb/siano/sms-cards.h b/drivers/media/usb/siano/sms-cards.h new file mode 100644 index 000000000000..d8cdf756f7cf --- /dev/null +++ b/drivers/media/usb/siano/sms-cards.h @@ -0,0 +1,123 @@ +/* + * Card-specific functions for the Siano SMS1xxx USB dongle + * + * Copyright (c) 2008 Michael Krufky <mkrufky@linuxtv.org> + * + * 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; + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __SMS_CARDS_H__ +#define __SMS_CARDS_H__ + +#include <linux/usb.h> +#include "smscoreapi.h" +#include "smsir.h" + +#define SMS_BOARD_UNKNOWN 0 +#define SMS1XXX_BOARD_SIANO_STELLAR 1 +#define SMS1XXX_BOARD_SIANO_NOVA_A 2 +#define SMS1XXX_BOARD_SIANO_NOVA_B 3 +#define SMS1XXX_BOARD_SIANO_VEGA 4 +#define SMS1XXX_BOARD_HAUPPAUGE_CATAMOUNT 5 +#define SMS1XXX_BOARD_HAUPPAUGE_OKEMO_A 6 +#define SMS1XXX_BOARD_HAUPPAUGE_OKEMO_B 7 +#define SMS1XXX_BOARD_HAUPPAUGE_WINDHAM 8 +#define SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD 9 +#define SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2 10 +#define SMS1XXX_BOARD_SIANO_NICE 11 +#define SMS1XXX_BOARD_SIANO_VENICE 12 + +struct sms_board_gpio_cfg { + int lna_vhf_exist; + int lna_vhf_ctrl; + int lna_uhf_exist; + int lna_uhf_ctrl; + int lna_uhf_d_ctrl; + int lna_sband_exist; + int lna_sband_ctrl; + int lna_sband_d_ctrl; + int foreign_lna0_ctrl; + int foreign_lna1_ctrl; + int foreign_lna2_ctrl; + int rf_switch_vhf; + int rf_switch_uhf; + int rf_switch_sband; + int leds_power; + int led0; + int led1; + int led2; + int led3; + int led4; + int ir; + int eeprom_wp; + int mrc_sense; + int mrc_pdn_resetn; + int mrc_gp0; /* mrcs spi int */ + int mrc_gp1; + int mrc_gp2; + int mrc_gp3; + int mrc_gp4; + int host_spi_gsp_ts_int; +}; + +struct sms_board { + enum sms_device_type_st type; + char *name, *fw[DEVICE_MODE_MAX]; + struct sms_board_gpio_cfg board_cfg; + char *rc_codes; /* Name of IR codes table */ + + /* gpios */ + int led_power, led_hi, led_lo, lna_ctrl, rf_switch; +}; + +struct sms_board *sms_get_board(unsigned id); + +extern struct smscore_device_t *coredev; + +enum SMS_BOARD_EVENTS { + BOARD_EVENT_POWER_INIT, + BOARD_EVENT_POWER_SUSPEND, + BOARD_EVENT_POWER_RESUME, + BOARD_EVENT_BIND, + BOARD_EVENT_SCAN_PROG, + BOARD_EVENT_SCAN_COMP, + BOARD_EVENT_EMERGENCY_WARNING_SIGNAL, + BOARD_EVENT_FE_LOCK, + BOARD_EVENT_FE_UNLOCK, + BOARD_EVENT_DEMOD_LOCK, + BOARD_EVENT_DEMOD_UNLOCK, + BOARD_EVENT_RECEPTION_MAX_4, + BOARD_EVENT_RECEPTION_3, + BOARD_EVENT_RECEPTION_2, + BOARD_EVENT_RECEPTION_1, + BOARD_EVENT_RECEPTION_LOST_0, + BOARD_EVENT_MULTIPLEX_OK, + BOARD_EVENT_MULTIPLEX_ERRORS +}; + +int sms_board_event(struct smscore_device_t *coredev, + enum SMS_BOARD_EVENTS gevent); + +int sms_board_setup(struct smscore_device_t *coredev); + +#define SMS_LED_OFF 0 +#define SMS_LED_LO 1 +#define SMS_LED_HI 2 +int sms_board_led_feedback(struct smscore_device_t *coredev, int led); +int sms_board_power(struct smscore_device_t *coredev, int onoff); +int sms_board_lna_control(struct smscore_device_t *coredev, int onoff); + +extern int sms_board_load_modules(int id); + +#endif /* __SMS_CARDS_H__ */ diff --git a/drivers/media/usb/siano/smscoreapi.c b/drivers/media/usb/siano/smscoreapi.c new file mode 100644 index 000000000000..9cc55546cc30 --- /dev/null +++ b/drivers/media/usb/siano/smscoreapi.c @@ -0,0 +1,1637 @@ +/* + * Siano core API module + * + * This file contains implementation for the interface to sms core component + * + * author: Uri Shkolnik + * + * Copyright (c), 2005-2008 Siano Mobile Silicon, Inc. + * + * 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; + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> + +#include <linux/firmware.h> +#include <linux/wait.h> +#include <asm/byteorder.h> + +#include "smscoreapi.h" +#include "sms-cards.h" +#include "smsir.h" +#include "smsendian.h" + +static int sms_dbg; +module_param_named(debug, sms_dbg, int, 0644); +MODULE_PARM_DESC(debug, "set debug level (info=1, adv=2 (or-able))"); + +struct smscore_device_notifyee_t { + struct list_head entry; + hotplug_t hotplug; +}; + +struct smscore_idlist_t { + struct list_head entry; + int id; + int data_type; +}; + +struct smscore_client_t { + struct list_head entry; + struct smscore_device_t *coredev; + void *context; + struct list_head idlist; + onresponse_t onresponse_handler; + onremove_t onremove_handler; +}; + +void smscore_set_board_id(struct smscore_device_t *core, int id) +{ + core->board_id = id; +} + +int smscore_led_state(struct smscore_device_t *core, int led) +{ + if (led >= 0) + core->led_state = led; + return core->led_state; +} +EXPORT_SYMBOL_GPL(smscore_set_board_id); + +int smscore_get_board_id(struct smscore_device_t *core) +{ + return core->board_id; +} +EXPORT_SYMBOL_GPL(smscore_get_board_id); + +struct smscore_registry_entry_t { + struct list_head entry; + char devpath[32]; + int mode; + enum sms_device_type_st type; +}; + +static struct list_head g_smscore_notifyees; +static struct list_head g_smscore_devices; +static struct mutex g_smscore_deviceslock; + +static struct list_head g_smscore_registry; +static struct mutex g_smscore_registrylock; + +static int default_mode = 4; + +module_param(default_mode, int, 0644); +MODULE_PARM_DESC(default_mode, "default firmware id (device mode)"); + +static struct smscore_registry_entry_t *smscore_find_registry(char *devpath) +{ + struct smscore_registry_entry_t *entry; + struct list_head *next; + + kmutex_lock(&g_smscore_registrylock); + for (next = g_smscore_registry.next; + next != &g_smscore_registry; + next = next->next) { + entry = (struct smscore_registry_entry_t *) next; + if (!strcmp(entry->devpath, devpath)) { + kmutex_unlock(&g_smscore_registrylock); + return entry; + } + } + entry = kmalloc(sizeof(struct smscore_registry_entry_t), GFP_KERNEL); + if (entry) { + entry->mode = default_mode; + strcpy(entry->devpath, devpath); + list_add(&entry->entry, &g_smscore_registry); + } else + sms_err("failed to create smscore_registry."); + kmutex_unlock(&g_smscore_registrylock); + return entry; +} + +int smscore_registry_getmode(char *devpath) +{ + struct smscore_registry_entry_t *entry; + + entry = smscore_find_registry(devpath); + if (entry) + return entry->mode; + else + sms_err("No registry found."); + + return default_mode; +} +EXPORT_SYMBOL_GPL(smscore_registry_getmode); + +static enum sms_device_type_st smscore_registry_gettype(char *devpath) +{ + struct smscore_registry_entry_t *entry; + + entry = smscore_find_registry(devpath); + if (entry) + return entry->type; + else + sms_err("No registry found."); + + return -1; +} + +void smscore_registry_setmode(char *devpath, int mode) +{ + struct smscore_registry_entry_t *entry; + + entry = smscore_find_registry(devpath); + if (entry) + entry->mode = mode; + else + sms_err("No registry found."); +} + +static void smscore_registry_settype(char *devpath, + enum sms_device_type_st type) +{ + struct smscore_registry_entry_t *entry; + + entry = smscore_find_registry(devpath); + if (entry) + entry->type = type; + else + sms_err("No registry found."); +} + + +static void list_add_locked(struct list_head *new, struct list_head *head, + spinlock_t *lock) +{ + unsigned long flags; + + spin_lock_irqsave(lock, flags); + + list_add(new, head); + + spin_unlock_irqrestore(lock, flags); +} + +/** + * register a client callback that called when device plugged in/unplugged + * NOTE: if devices exist callback is called immediately for each device + * + * @param hotplug callback + * + * @return 0 on success, <0 on error. + */ +int smscore_register_hotplug(hotplug_t hotplug) +{ + struct smscore_device_notifyee_t *notifyee; + struct list_head *next, *first; + int rc = 0; + + kmutex_lock(&g_smscore_deviceslock); + + notifyee = kmalloc(sizeof(struct smscore_device_notifyee_t), + GFP_KERNEL); + if (notifyee) { + /* now notify callback about existing devices */ + first = &g_smscore_devices; + for (next = first->next; + next != first && !rc; + next = next->next) { + struct smscore_device_t *coredev = + (struct smscore_device_t *) next; + rc = hotplug(coredev, coredev->device, 1); + } + + if (rc >= 0) { + notifyee->hotplug = hotplug; + list_add(¬ifyee->entry, &g_smscore_notifyees); + } else + kfree(notifyee); + } else + rc = -ENOMEM; + + kmutex_unlock(&g_smscore_deviceslock); + + return rc; +} +EXPORT_SYMBOL_GPL(smscore_register_hotplug); + +/** + * unregister a client callback that called when device plugged in/unplugged + * + * @param hotplug callback + * + */ +void smscore_unregister_hotplug(hotplug_t hotplug) +{ + struct list_head *next, *first; + + kmutex_lock(&g_smscore_deviceslock); + + first = &g_smscore_notifyees; + + for (next = first->next; next != first;) { + struct smscore_device_notifyee_t *notifyee = + (struct smscore_device_notifyee_t *) next; + next = next->next; + + if (notifyee->hotplug == hotplug) { + list_del(¬ifyee->entry); + kfree(notifyee); + } + } + + kmutex_unlock(&g_smscore_deviceslock); +} +EXPORT_SYMBOL_GPL(smscore_unregister_hotplug); + +static void smscore_notify_clients(struct smscore_device_t *coredev) +{ + struct smscore_client_t *client; + + /* the client must call smscore_unregister_client from remove handler */ + while (!list_empty(&coredev->clients)) { + client = (struct smscore_client_t *) coredev->clients.next; + client->onremove_handler(client->context); + } +} + +static int smscore_notify_callbacks(struct smscore_device_t *coredev, + struct device *device, int arrival) +{ + struct smscore_device_notifyee_t *elem; + int rc = 0; + + /* note: must be called under g_deviceslock */ + + list_for_each_entry(elem, &g_smscore_notifyees, entry) { + rc = elem->hotplug(coredev, device, arrival); + if (rc < 0) + break; + } + + return rc; +} + +static struct +smscore_buffer_t *smscore_createbuffer(u8 *buffer, void *common_buffer, + dma_addr_t common_buffer_phys) +{ + struct smscore_buffer_t *cb = + kmalloc(sizeof(struct smscore_buffer_t), GFP_KERNEL); + if (!cb) { + sms_info("kmalloc(...) failed"); + return NULL; + } + + cb->p = buffer; + cb->offset_in_common = buffer - (u8 *) common_buffer; + cb->phys = common_buffer_phys + cb->offset_in_common; + + return cb; +} + +/** + * creates coredev object for a device, prepares buffers, + * creates buffer mappings, notifies registered hotplugs about new device. + * + * @param params device pointer to struct with device specific parameters + * and handlers + * @param coredev pointer to a value that receives created coredev object + * + * @return 0 on success, <0 on error. + */ +int smscore_register_device(struct smsdevice_params_t *params, + struct smscore_device_t **coredev) +{ + struct smscore_device_t *dev; + u8 *buffer; + + dev = kzalloc(sizeof(struct smscore_device_t), GFP_KERNEL); + if (!dev) { + sms_info("kzalloc(...) failed"); + return -ENOMEM; + } + + /* init list entry so it could be safe in smscore_unregister_device */ + INIT_LIST_HEAD(&dev->entry); + + /* init queues */ + INIT_LIST_HEAD(&dev->clients); + INIT_LIST_HEAD(&dev->buffers); + + /* init locks */ + spin_lock_init(&dev->clientslock); + spin_lock_init(&dev->bufferslock); + + /* init completion events */ + init_completion(&dev->version_ex_done); + init_completion(&dev->data_download_done); + init_completion(&dev->trigger_done); + init_completion(&dev->init_device_done); + init_completion(&dev->reload_start_done); + init_completion(&dev->resume_done); + init_completion(&dev->gpio_configuration_done); + init_completion(&dev->gpio_set_level_done); + init_completion(&dev->gpio_get_level_done); + init_completion(&dev->ir_init_done); + + /* Buffer management */ + init_waitqueue_head(&dev->buffer_mng_waitq); + + /* alloc common buffer */ + dev->common_buffer_size = params->buffer_size * params->num_buffers; + dev->common_buffer = dma_alloc_coherent(NULL, dev->common_buffer_size, + &dev->common_buffer_phys, + GFP_KERNEL | GFP_DMA); + if (!dev->common_buffer) { + smscore_unregister_device(dev); + return -ENOMEM; + } + + /* prepare dma buffers */ + for (buffer = dev->common_buffer; + dev->num_buffers < params->num_buffers; + dev->num_buffers++, buffer += params->buffer_size) { + struct smscore_buffer_t *cb = + smscore_createbuffer(buffer, dev->common_buffer, + dev->common_buffer_phys); + if (!cb) { + smscore_unregister_device(dev); + return -ENOMEM; + } + + smscore_putbuffer(dev, cb); + } + + sms_info("allocated %d buffers", dev->num_buffers); + + dev->mode = DEVICE_MODE_NONE; + dev->context = params->context; + dev->device = params->device; + dev->setmode_handler = params->setmode_handler; + dev->detectmode_handler = params->detectmode_handler; + dev->sendrequest_handler = params->sendrequest_handler; + dev->preload_handler = params->preload_handler; + dev->postload_handler = params->postload_handler; + + dev->device_flags = params->flags; + strcpy(dev->devpath, params->devpath); + + smscore_registry_settype(dev->devpath, params->device_type); + + /* add device to devices list */ + kmutex_lock(&g_smscore_deviceslock); + list_add(&dev->entry, &g_smscore_devices); + kmutex_unlock(&g_smscore_deviceslock); + + *coredev = dev; + + sms_info("device %p created", dev); + + return 0; +} +EXPORT_SYMBOL_GPL(smscore_register_device); + + +static int smscore_sendrequest_and_wait(struct smscore_device_t *coredev, + void *buffer, size_t size, struct completion *completion) { + int rc = coredev->sendrequest_handler(coredev->context, buffer, size); + if (rc < 0) { + sms_info("sendrequest returned error %d", rc); + return rc; + } + + return wait_for_completion_timeout(completion, + msecs_to_jiffies(SMS_PROTOCOL_MAX_RAOUNDTRIP_MS)) ? + 0 : -ETIME; +} + +/** + * Starts & enables IR operations + * + * @return 0 on success, < 0 on error. + */ +static int smscore_init_ir(struct smscore_device_t *coredev) +{ + int ir_io; + int rc; + void *buffer; + + coredev->ir.dev = NULL; + ir_io = sms_get_board(smscore_get_board_id(coredev))->board_cfg.ir; + if (ir_io) {/* only if IR port exist we use IR sub-module */ + sms_info("IR loading"); + rc = sms_ir_init(coredev); + + if (rc != 0) + sms_err("Error initialization DTV IR sub-module"); + else { + buffer = kmalloc(sizeof(struct SmsMsgData_ST2) + + SMS_DMA_ALIGNMENT, + GFP_KERNEL | GFP_DMA); + if (buffer) { + struct SmsMsgData_ST2 *msg = + (struct SmsMsgData_ST2 *) + SMS_ALIGN_ADDRESS(buffer); + + SMS_INIT_MSG(&msg->xMsgHeader, + MSG_SMS_START_IR_REQ, + sizeof(struct SmsMsgData_ST2)); + msg->msgData[0] = coredev->ir.controller; + msg->msgData[1] = coredev->ir.timeout; + + smsendian_handle_tx_message( + (struct SmsMsgHdr_ST2 *)msg); + rc = smscore_sendrequest_and_wait(coredev, msg, + msg->xMsgHeader. msgLength, + &coredev->ir_init_done); + + kfree(buffer); + } else + sms_err + ("Sending IR initialization message failed"); + } + } else + sms_info("IR port has not been detected"); + + return 0; +} + +/** + * sets initial device mode and notifies client hotplugs that device is ready + * + * @param coredev pointer to a coredev object returned by + * smscore_register_device + * + * @return 0 on success, <0 on error. + */ +int smscore_start_device(struct smscore_device_t *coredev) +{ + int rc = smscore_set_device_mode( + coredev, smscore_registry_getmode(coredev->devpath)); + if (rc < 0) { + sms_info("set device mode faile , rc %d", rc); + return rc; + } + + kmutex_lock(&g_smscore_deviceslock); + + rc = smscore_notify_callbacks(coredev, coredev->device, 1); + smscore_init_ir(coredev); + + sms_info("device %p started, rc %d", coredev, rc); + + kmutex_unlock(&g_smscore_deviceslock); + + return rc; +} +EXPORT_SYMBOL_GPL(smscore_start_device); + + +static int smscore_load_firmware_family2(struct smscore_device_t *coredev, + void *buffer, size_t size) +{ + struct SmsFirmware_ST *firmware = (struct SmsFirmware_ST *) buffer; + struct SmsMsgHdr_ST *msg; + u32 mem_address; + u8 *payload = firmware->Payload; + int rc = 0; + firmware->StartAddress = le32_to_cpu(firmware->StartAddress); + firmware->Length = le32_to_cpu(firmware->Length); + + mem_address = firmware->StartAddress; + + sms_info("loading FW to addr 0x%x size %d", + mem_address, firmware->Length); + if (coredev->preload_handler) { + rc = coredev->preload_handler(coredev->context); + if (rc < 0) + return rc; + } + + /* PAGE_SIZE buffer shall be enough and dma aligned */ + msg = kmalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); + if (!msg) + return -ENOMEM; + + if (coredev->mode != DEVICE_MODE_NONE) { + sms_debug("sending reload command."); + SMS_INIT_MSG(msg, MSG_SW_RELOAD_START_REQ, + sizeof(struct SmsMsgHdr_ST)); + rc = smscore_sendrequest_and_wait(coredev, msg, + msg->msgLength, + &coredev->reload_start_done); + mem_address = *(u32 *) &payload[20]; + } + + while (size && rc >= 0) { + struct SmsDataDownload_ST *DataMsg = + (struct SmsDataDownload_ST *) msg; + int payload_size = min((int) size, SMS_MAX_PAYLOAD_SIZE); + + SMS_INIT_MSG(msg, MSG_SMS_DATA_DOWNLOAD_REQ, + (u16)(sizeof(struct SmsMsgHdr_ST) + + sizeof(u32) + payload_size)); + + DataMsg->MemAddr = mem_address; + memcpy(DataMsg->Payload, payload, payload_size); + + if ((coredev->device_flags & SMS_ROM_NO_RESPONSE) && + (coredev->mode == DEVICE_MODE_NONE)) + rc = coredev->sendrequest_handler( + coredev->context, DataMsg, + DataMsg->xMsgHeader.msgLength); + else + rc = smscore_sendrequest_and_wait( + coredev, DataMsg, + DataMsg->xMsgHeader.msgLength, + &coredev->data_download_done); + + payload += payload_size; + size -= payload_size; + mem_address += payload_size; + } + + if (rc >= 0) { + if (coredev->mode == DEVICE_MODE_NONE) { + struct SmsMsgData_ST *TriggerMsg = + (struct SmsMsgData_ST *) msg; + + SMS_INIT_MSG(msg, MSG_SMS_SWDOWNLOAD_TRIGGER_REQ, + sizeof(struct SmsMsgHdr_ST) + + sizeof(u32) * 5); + + TriggerMsg->msgData[0] = firmware->StartAddress; + /* Entry point */ + TriggerMsg->msgData[1] = 5; /* Priority */ + TriggerMsg->msgData[2] = 0x200; /* Stack size */ + TriggerMsg->msgData[3] = 0; /* Parameter */ + TriggerMsg->msgData[4] = 4; /* Task ID */ + + if (coredev->device_flags & SMS_ROM_NO_RESPONSE) { + rc = coredev->sendrequest_handler( + coredev->context, TriggerMsg, + TriggerMsg->xMsgHeader.msgLength); + msleep(100); + } else + rc = smscore_sendrequest_and_wait( + coredev, TriggerMsg, + TriggerMsg->xMsgHeader.msgLength, + &coredev->trigger_done); + } else { + SMS_INIT_MSG(msg, MSG_SW_RELOAD_EXEC_REQ, + sizeof(struct SmsMsgHdr_ST)); + + rc = coredev->sendrequest_handler(coredev->context, + msg, msg->msgLength); + } + msleep(500); + } + + sms_debug("rc=%d, postload=%p ", rc, + coredev->postload_handler); + + kfree(msg); + + return ((rc >= 0) && coredev->postload_handler) ? + coredev->postload_handler(coredev->context) : + rc; +} + +/** + * loads specified firmware into a buffer and calls device loadfirmware_handler + * + * @param coredev pointer to a coredev object returned by + * smscore_register_device + * @param filename null-terminated string specifies firmware file name + * @param loadfirmware_handler device handler that loads firmware + * + * @return 0 on success, <0 on error. + */ +static int smscore_load_firmware_from_file(struct smscore_device_t *coredev, + char *filename, + loadfirmware_t loadfirmware_handler) +{ + int rc = -ENOENT; + const struct firmware *fw; + u8 *fw_buffer; + + if (loadfirmware_handler == NULL && !(coredev->device_flags & + SMS_DEVICE_FAMILY2)) + return -EINVAL; + + rc = request_firmware(&fw, filename, coredev->device); + if (rc < 0) { + sms_info("failed to open \"%s\"", filename); + return rc; + } + sms_info("read FW %s, size=%zd", filename, fw->size); + fw_buffer = kmalloc(ALIGN(fw->size, SMS_ALLOC_ALIGNMENT), + GFP_KERNEL | GFP_DMA); + if (fw_buffer) { + memcpy(fw_buffer, fw->data, fw->size); + + rc = (coredev->device_flags & SMS_DEVICE_FAMILY2) ? + smscore_load_firmware_family2(coredev, + fw_buffer, + fw->size) : + loadfirmware_handler(coredev->context, + fw_buffer, fw->size); + + kfree(fw_buffer); + } else { + sms_info("failed to allocate firmware buffer"); + rc = -ENOMEM; + } + + release_firmware(fw); + + return rc; +} + +/** + * notifies all clients registered with the device, notifies hotplugs, + * frees all buffers and coredev object + * + * @param coredev pointer to a coredev object returned by + * smscore_register_device + * + * @return 0 on success, <0 on error. + */ +void smscore_unregister_device(struct smscore_device_t *coredev) +{ + struct smscore_buffer_t *cb; + int num_buffers = 0; + int retry = 0; + + kmutex_lock(&g_smscore_deviceslock); + + /* Release input device (IR) resources */ + sms_ir_exit(coredev); + + smscore_notify_clients(coredev); + smscore_notify_callbacks(coredev, NULL, 0); + + /* at this point all buffers should be back + * onresponse must no longer be called */ + + while (1) { + while (!list_empty(&coredev->buffers)) { + cb = (struct smscore_buffer_t *) coredev->buffers.next; + list_del(&cb->entry); + kfree(cb); + num_buffers++; + } + if (num_buffers == coredev->num_buffers) + break; + if (++retry > 10) { + sms_info("exiting although " + "not all buffers released."); + break; + } + + sms_info("waiting for %d buffer(s)", + coredev->num_buffers - num_buffers); + msleep(100); + } + + sms_info("freed %d buffers", num_buffers); + + if (coredev->common_buffer) + dma_free_coherent(NULL, coredev->common_buffer_size, + coredev->common_buffer, coredev->common_buffer_phys); + + if (coredev->fw_buf != NULL) + kfree(coredev->fw_buf); + + list_del(&coredev->entry); + kfree(coredev); + + kmutex_unlock(&g_smscore_deviceslock); + + sms_info("device %p destroyed", coredev); +} +EXPORT_SYMBOL_GPL(smscore_unregister_device); + +static int smscore_detect_mode(struct smscore_device_t *coredev) +{ + void *buffer = kmalloc(sizeof(struct SmsMsgHdr_ST) + SMS_DMA_ALIGNMENT, + GFP_KERNEL | GFP_DMA); + struct SmsMsgHdr_ST *msg = + (struct SmsMsgHdr_ST *) SMS_ALIGN_ADDRESS(buffer); + int rc; + + if (!buffer) + return -ENOMEM; + + SMS_INIT_MSG(msg, MSG_SMS_GET_VERSION_EX_REQ, + sizeof(struct SmsMsgHdr_ST)); + + rc = smscore_sendrequest_and_wait(coredev, msg, msg->msgLength, + &coredev->version_ex_done); + if (rc == -ETIME) { + sms_err("MSG_SMS_GET_VERSION_EX_REQ failed first try"); + + if (wait_for_completion_timeout(&coredev->resume_done, + msecs_to_jiffies(5000))) { + rc = smscore_sendrequest_and_wait( + coredev, msg, msg->msgLength, + &coredev->version_ex_done); + if (rc < 0) + sms_err("MSG_SMS_GET_VERSION_EX_REQ failed " + "second try, rc %d", rc); + } else + rc = -ETIME; + } + + kfree(buffer); + + return rc; +} + +static char *smscore_fw_lkup[][SMS_NUM_OF_DEVICE_TYPES] = { + /*Stellar NOVA A0 Nova B0 VEGA*/ + /*DVBT*/ + {"none", "dvb_nova_12mhz.inp", "dvb_nova_12mhz_b0.inp", "none"}, + /*DVBH*/ + {"none", "dvb_nova_12mhz.inp", "dvb_nova_12mhz_b0.inp", "none"}, + /*TDMB*/ + {"none", "tdmb_nova_12mhz.inp", "tdmb_nova_12mhz_b0.inp", "none"}, + /*DABIP*/ + {"none", "none", "none", "none"}, + /*BDA*/ + {"none", "dvb_nova_12mhz.inp", "dvb_nova_12mhz_b0.inp", "none"}, + /*ISDBT*/ + {"none", "isdbt_nova_12mhz.inp", "isdbt_nova_12mhz_b0.inp", "none"}, + /*ISDBTBDA*/ + {"none", "isdbt_nova_12mhz.inp", "isdbt_nova_12mhz_b0.inp", "none"}, + /*CMMB*/ + {"none", "none", "none", "cmmb_vega_12mhz.inp"} +}; + +static inline char *sms_get_fw_name(struct smscore_device_t *coredev, + int mode, enum sms_device_type_st type) +{ + char **fw = sms_get_board(smscore_get_board_id(coredev))->fw; + return (fw && fw[mode]) ? fw[mode] : smscore_fw_lkup[mode][type]; +} + +/** + * calls device handler to change mode of operation + * NOTE: stellar/usb may disconnect when changing mode + * + * @param coredev pointer to a coredev object returned by + * smscore_register_device + * @param mode requested mode of operation + * + * @return 0 on success, <0 on error. + */ +int smscore_set_device_mode(struct smscore_device_t *coredev, int mode) +{ + void *buffer; + int rc = 0; + enum sms_device_type_st type; + + sms_debug("set device mode to %d", mode); + if (coredev->device_flags & SMS_DEVICE_FAMILY2) { + if (mode < DEVICE_MODE_DVBT || mode >= DEVICE_MODE_RAW_TUNER) { + sms_err("invalid mode specified %d", mode); + return -EINVAL; + } + + smscore_registry_setmode(coredev->devpath, mode); + + if (!(coredev->device_flags & SMS_DEVICE_NOT_READY)) { + rc = smscore_detect_mode(coredev); + if (rc < 0) { + sms_err("mode detect failed %d", rc); + return rc; + } + } + + if (coredev->mode == mode) { + sms_info("device mode %d already set", mode); + return 0; + } + + if (!(coredev->modes_supported & (1 << mode))) { + char *fw_filename; + + type = smscore_registry_gettype(coredev->devpath); + fw_filename = sms_get_fw_name(coredev, mode, type); + + rc = smscore_load_firmware_from_file(coredev, + fw_filename, NULL); + if (rc < 0) { + sms_warn("error %d loading firmware: %s, " + "trying again with default firmware", + rc, fw_filename); + + /* try again with the default firmware */ + fw_filename = smscore_fw_lkup[mode][type]; + rc = smscore_load_firmware_from_file(coredev, + fw_filename, NULL); + + if (rc < 0) { + sms_warn("error %d loading " + "firmware: %s", rc, + fw_filename); + return rc; + } + } + sms_log("firmware download success: %s", fw_filename); + } else + sms_info("mode %d supported by running " + "firmware", mode); + + buffer = kmalloc(sizeof(struct SmsMsgData_ST) + + SMS_DMA_ALIGNMENT, GFP_KERNEL | GFP_DMA); + if (buffer) { + struct SmsMsgData_ST *msg = + (struct SmsMsgData_ST *) + SMS_ALIGN_ADDRESS(buffer); + + SMS_INIT_MSG(&msg->xMsgHeader, MSG_SMS_INIT_DEVICE_REQ, + sizeof(struct SmsMsgData_ST)); + msg->msgData[0] = mode; + + rc = smscore_sendrequest_and_wait( + coredev, msg, msg->xMsgHeader.msgLength, + &coredev->init_device_done); + + kfree(buffer); + } else { + sms_err("Could not allocate buffer for " + "init device message."); + rc = -ENOMEM; + } + } else { + if (mode < DEVICE_MODE_DVBT || mode > DEVICE_MODE_DVBT_BDA) { + sms_err("invalid mode specified %d", mode); + return -EINVAL; + } + + smscore_registry_setmode(coredev->devpath, mode); + + if (coredev->detectmode_handler) + coredev->detectmode_handler(coredev->context, + &coredev->mode); + + if (coredev->mode != mode && coredev->setmode_handler) + rc = coredev->setmode_handler(coredev->context, mode); + } + + if (rc >= 0) { + coredev->mode = mode; + coredev->device_flags &= ~SMS_DEVICE_NOT_READY; + } + + if (rc < 0) + sms_err("return error code %d.", rc); + return rc; +} + +/** + * calls device handler to get current mode of operation + * + * @param coredev pointer to a coredev object returned by + * smscore_register_device + * + * @return current mode + */ +int smscore_get_device_mode(struct smscore_device_t *coredev) +{ + return coredev->mode; +} +EXPORT_SYMBOL_GPL(smscore_get_device_mode); + +/** + * find client by response id & type within the clients list. + * return client handle or NULL. + * + * @param coredev pointer to a coredev object returned by + * smscore_register_device + * @param data_type client data type (SMS_DONT_CARE for all types) + * @param id client id (SMS_DONT_CARE for all id) + * + */ +static struct +smscore_client_t *smscore_find_client(struct smscore_device_t *coredev, + int data_type, int id) +{ + struct list_head *first; + struct smscore_client_t *client; + unsigned long flags; + struct list_head *firstid; + struct smscore_idlist_t *client_id; + + spin_lock_irqsave(&coredev->clientslock, flags); + first = &coredev->clients; + list_for_each_entry(client, first, entry) { + firstid = &client->idlist; + list_for_each_entry(client_id, firstid, entry) { + if ((client_id->id == id) && + (client_id->data_type == data_type || + (client_id->data_type == 0))) + goto found; + } + } + client = NULL; +found: + spin_unlock_irqrestore(&coredev->clientslock, flags); + return client; +} + +/** + * find client by response id/type, call clients onresponse handler + * return buffer to pool on error + * + * @param coredev pointer to a coredev object returned by + * smscore_register_device + * @param cb pointer to response buffer descriptor + * + */ +void smscore_onresponse(struct smscore_device_t *coredev, + struct smscore_buffer_t *cb) { + struct SmsMsgHdr_ST *phdr = (struct SmsMsgHdr_ST *) ((u8 *) cb->p + + cb->offset); + struct smscore_client_t *client; + int rc = -EBUSY; + static unsigned long last_sample_time; /* = 0; */ + static int data_total; /* = 0; */ + unsigned long time_now = jiffies_to_msecs(jiffies); + + if (!last_sample_time) + last_sample_time = time_now; + + if (time_now - last_sample_time > 10000) { + sms_debug("\ndata rate %d bytes/secs", + (int)((data_total * 1000) / + (time_now - last_sample_time))); + + last_sample_time = time_now; + data_total = 0; + } + + data_total += cb->size; + /* Do we need to re-route? */ + if ((phdr->msgType == MSG_SMS_HO_PER_SLICES_IND) || + (phdr->msgType == MSG_SMS_TRANSMISSION_IND)) { + if (coredev->mode == DEVICE_MODE_DVBT_BDA) + phdr->msgDstId = DVBT_BDA_CONTROL_MSG_ID; + } + + + client = smscore_find_client(coredev, phdr->msgType, phdr->msgDstId); + + /* If no client registered for type & id, + * check for control client where type is not registered */ + if (client) + rc = client->onresponse_handler(client->context, cb); + + if (rc < 0) { + switch (phdr->msgType) { + case MSG_SMS_GET_VERSION_EX_RES: + { + struct SmsVersionRes_ST *ver = + (struct SmsVersionRes_ST *) phdr; + sms_debug("MSG_SMS_GET_VERSION_EX_RES " + "id %d prots 0x%x ver %d.%d", + ver->FirmwareId, ver->SupportedProtocols, + ver->RomVersionMajor, ver->RomVersionMinor); + + coredev->mode = ver->FirmwareId == 255 ? + DEVICE_MODE_NONE : ver->FirmwareId; + coredev->modes_supported = ver->SupportedProtocols; + + complete(&coredev->version_ex_done); + break; + } + case MSG_SMS_INIT_DEVICE_RES: + sms_debug("MSG_SMS_INIT_DEVICE_RES"); + complete(&coredev->init_device_done); + break; + case MSG_SW_RELOAD_START_RES: + sms_debug("MSG_SW_RELOAD_START_RES"); + complete(&coredev->reload_start_done); + break; + case MSG_SMS_DATA_DOWNLOAD_RES: + complete(&coredev->data_download_done); + break; + case MSG_SW_RELOAD_EXEC_RES: + sms_debug("MSG_SW_RELOAD_EXEC_RES"); + break; + case MSG_SMS_SWDOWNLOAD_TRIGGER_RES: + sms_debug("MSG_SMS_SWDOWNLOAD_TRIGGER_RES"); + complete(&coredev->trigger_done); + break; + case MSG_SMS_SLEEP_RESUME_COMP_IND: + complete(&coredev->resume_done); + break; + case MSG_SMS_GPIO_CONFIG_EX_RES: + sms_debug("MSG_SMS_GPIO_CONFIG_EX_RES"); + complete(&coredev->gpio_configuration_done); + break; + case MSG_SMS_GPIO_SET_LEVEL_RES: + sms_debug("MSG_SMS_GPIO_SET_LEVEL_RES"); + complete(&coredev->gpio_set_level_done); + break; + case MSG_SMS_GPIO_GET_LEVEL_RES: + { + u32 *msgdata = (u32 *) phdr; + coredev->gpio_get_res = msgdata[1]; + sms_debug("MSG_SMS_GPIO_GET_LEVEL_RES gpio level %d", + coredev->gpio_get_res); + complete(&coredev->gpio_get_level_done); + break; + } + case MSG_SMS_START_IR_RES: + complete(&coredev->ir_init_done); + break; + case MSG_SMS_IR_SAMPLES_IND: + sms_ir_event(coredev, + (const char *) + ((char *)phdr + + sizeof(struct SmsMsgHdr_ST)), + (int)phdr->msgLength + - sizeof(struct SmsMsgHdr_ST)); + break; + + default: + break; + } + smscore_putbuffer(coredev, cb); + } +} +EXPORT_SYMBOL_GPL(smscore_onresponse); + +/** + * return pointer to next free buffer descriptor from core pool + * + * @param coredev pointer to a coredev object returned by + * smscore_register_device + * + * @return pointer to descriptor on success, NULL on error. + */ + +struct smscore_buffer_t *get_entry(struct smscore_device_t *coredev) +{ + struct smscore_buffer_t *cb = NULL; + unsigned long flags; + + spin_lock_irqsave(&coredev->bufferslock, flags); + if (!list_empty(&coredev->buffers)) { + cb = (struct smscore_buffer_t *) coredev->buffers.next; + list_del(&cb->entry); + } + spin_unlock_irqrestore(&coredev->bufferslock, flags); + return cb; +} + +struct smscore_buffer_t *smscore_getbuffer(struct smscore_device_t *coredev) +{ + struct smscore_buffer_t *cb = NULL; + + wait_event(coredev->buffer_mng_waitq, (cb = get_entry(coredev))); + + return cb; +} +EXPORT_SYMBOL_GPL(smscore_getbuffer); + +/** + * return buffer descriptor to a pool + * + * @param coredev pointer to a coredev object returned by + * smscore_register_device + * @param cb pointer buffer descriptor + * + */ +void smscore_putbuffer(struct smscore_device_t *coredev, + struct smscore_buffer_t *cb) { + wake_up_interruptible(&coredev->buffer_mng_waitq); + list_add_locked(&cb->entry, &coredev->buffers, &coredev->bufferslock); +} +EXPORT_SYMBOL_GPL(smscore_putbuffer); + +static int smscore_validate_client(struct smscore_device_t *coredev, + struct smscore_client_t *client, + int data_type, int id) +{ + struct smscore_idlist_t *listentry; + struct smscore_client_t *registered_client; + + if (!client) { + sms_err("bad parameter."); + return -EINVAL; + } + registered_client = smscore_find_client(coredev, data_type, id); + if (registered_client == client) + return 0; + + if (registered_client) { + sms_err("The msg ID already registered to another client."); + return -EEXIST; + } + listentry = kzalloc(sizeof(struct smscore_idlist_t), GFP_KERNEL); + if (!listentry) { + sms_err("Can't allocate memory for client id."); + return -ENOMEM; + } + listentry->id = id; + listentry->data_type = data_type; + list_add_locked(&listentry->entry, &client->idlist, + &coredev->clientslock); + return 0; +} + +/** + * creates smsclient object, check that id is taken by another client + * + * @param coredev pointer to a coredev object from clients hotplug + * @param initial_id all messages with this id would be sent to this client + * @param data_type all messages of this type would be sent to this client + * @param onresponse_handler client handler that is called to + * process incoming messages + * @param onremove_handler client handler that is called when device is removed + * @param context client-specific context + * @param client pointer to a value that receives created smsclient object + * + * @return 0 on success, <0 on error. + */ +int smscore_register_client(struct smscore_device_t *coredev, + struct smsclient_params_t *params, + struct smscore_client_t **client) +{ + struct smscore_client_t *newclient; + /* check that no other channel with same parameters exists */ + if (smscore_find_client(coredev, params->data_type, + params->initial_id)) { + sms_err("Client already exist."); + return -EEXIST; + } + + newclient = kzalloc(sizeof(struct smscore_client_t), GFP_KERNEL); + if (!newclient) { + sms_err("Failed to allocate memory for client."); + return -ENOMEM; + } + + INIT_LIST_HEAD(&newclient->idlist); + newclient->coredev = coredev; + newclient->onresponse_handler = params->onresponse_handler; + newclient->onremove_handler = params->onremove_handler; + newclient->context = params->context; + list_add_locked(&newclient->entry, &coredev->clients, + &coredev->clientslock); + smscore_validate_client(coredev, newclient, params->data_type, + params->initial_id); + *client = newclient; + sms_debug("%p %d %d", params->context, params->data_type, + params->initial_id); + + return 0; +} +EXPORT_SYMBOL_GPL(smscore_register_client); + +/** + * frees smsclient object and all subclients associated with it + * + * @param client pointer to smsclient object returned by + * smscore_register_client + * + */ +void smscore_unregister_client(struct smscore_client_t *client) +{ + struct smscore_device_t *coredev = client->coredev; + unsigned long flags; + + spin_lock_irqsave(&coredev->clientslock, flags); + + + while (!list_empty(&client->idlist)) { + struct smscore_idlist_t *identry = + (struct smscore_idlist_t *) client->idlist.next; + list_del(&identry->entry); + kfree(identry); + } + + sms_info("%p", client->context); + + list_del(&client->entry); + kfree(client); + + spin_unlock_irqrestore(&coredev->clientslock, flags); +} +EXPORT_SYMBOL_GPL(smscore_unregister_client); + +/** + * verifies that source id is not taken by another client, + * calls device handler to send requests to the device + * + * @param client pointer to smsclient object returned by + * smscore_register_client + * @param buffer pointer to a request buffer + * @param size size (in bytes) of request buffer + * + * @return 0 on success, <0 on error. + */ +int smsclient_sendrequest(struct smscore_client_t *client, + void *buffer, size_t size) +{ + struct smscore_device_t *coredev; + struct SmsMsgHdr_ST *phdr = (struct SmsMsgHdr_ST *) buffer; + int rc; + + if (client == NULL) { + sms_err("Got NULL client"); + return -EINVAL; + } + + coredev = client->coredev; + + /* check that no other channel with same id exists */ + if (coredev == NULL) { + sms_err("Got NULL coredev"); + return -EINVAL; + } + + rc = smscore_validate_client(client->coredev, client, 0, + phdr->msgSrcId); + if (rc < 0) + return rc; + + return coredev->sendrequest_handler(coredev->context, buffer, size); +} +EXPORT_SYMBOL_GPL(smsclient_sendrequest); + + +/* old GPIO managements implementation */ +int smscore_configure_gpio(struct smscore_device_t *coredev, u32 pin, + struct smscore_config_gpio *pinconfig) +{ + struct { + struct SmsMsgHdr_ST hdr; + u32 data[6]; + } msg; + + if (coredev->device_flags & SMS_DEVICE_FAMILY2) { + msg.hdr.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; + msg.hdr.msgDstId = HIF_TASK; + msg.hdr.msgFlags = 0; + msg.hdr.msgType = MSG_SMS_GPIO_CONFIG_EX_REQ; + msg.hdr.msgLength = sizeof(msg); + + msg.data[0] = pin; + msg.data[1] = pinconfig->pullupdown; + + /* Convert slew rate for Nova: Fast(0) = 3 / Slow(1) = 0; */ + msg.data[2] = pinconfig->outputslewrate == 0 ? 3 : 0; + + switch (pinconfig->outputdriving) { + case SMS_GPIO_OUTPUTDRIVING_16mA: + msg.data[3] = 7; /* Nova - 16mA */ + break; + case SMS_GPIO_OUTPUTDRIVING_12mA: + msg.data[3] = 5; /* Nova - 11mA */ + break; + case SMS_GPIO_OUTPUTDRIVING_8mA: + msg.data[3] = 3; /* Nova - 7mA */ + break; + case SMS_GPIO_OUTPUTDRIVING_4mA: + default: + msg.data[3] = 2; /* Nova - 4mA */ + break; + } + + msg.data[4] = pinconfig->direction; + msg.data[5] = 0; + } else /* TODO: SMS_DEVICE_FAMILY1 */ + return -EINVAL; + + return coredev->sendrequest_handler(coredev->context, + &msg, sizeof(msg)); +} + +int smscore_set_gpio(struct smscore_device_t *coredev, u32 pin, int level) +{ + struct { + struct SmsMsgHdr_ST hdr; + u32 data[3]; + } msg; + + if (pin > MAX_GPIO_PIN_NUMBER) + return -EINVAL; + + msg.hdr.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; + msg.hdr.msgDstId = HIF_TASK; + msg.hdr.msgFlags = 0; + msg.hdr.msgType = MSG_SMS_GPIO_SET_LEVEL_REQ; + msg.hdr.msgLength = sizeof(msg); + + msg.data[0] = pin; + msg.data[1] = level ? 1 : 0; + msg.data[2] = 0; + + return coredev->sendrequest_handler(coredev->context, + &msg, sizeof(msg)); +} + +/* new GPIO management implementation */ +static int GetGpioPinParams(u32 PinNum, u32 *pTranslatedPinNum, + u32 *pGroupNum, u32 *pGroupCfg) { + + *pGroupCfg = 1; + + if (PinNum <= 1) { + *pTranslatedPinNum = 0; + *pGroupNum = 9; + *pGroupCfg = 2; + } else if (PinNum >= 2 && PinNum <= 6) { + *pTranslatedPinNum = 2; + *pGroupNum = 0; + *pGroupCfg = 2; + } else if (PinNum >= 7 && PinNum <= 11) { + *pTranslatedPinNum = 7; + *pGroupNum = 1; + } else if (PinNum >= 12 && PinNum <= 15) { + *pTranslatedPinNum = 12; + *pGroupNum = 2; + *pGroupCfg = 3; + } else if (PinNum == 16) { + *pTranslatedPinNum = 16; + *pGroupNum = 23; + } else if (PinNum >= 17 && PinNum <= 24) { + *pTranslatedPinNum = 17; + *pGroupNum = 3; + } else if (PinNum == 25) { + *pTranslatedPinNum = 25; + *pGroupNum = 6; + } else if (PinNum >= 26 && PinNum <= 28) { + *pTranslatedPinNum = 26; + *pGroupNum = 4; + } else if (PinNum == 29) { + *pTranslatedPinNum = 29; + *pGroupNum = 5; + *pGroupCfg = 2; + } else if (PinNum == 30) { + *pTranslatedPinNum = 30; + *pGroupNum = 8; + } else if (PinNum == 31) { + *pTranslatedPinNum = 31; + *pGroupNum = 17; + } else + return -1; + + *pGroupCfg <<= 24; + + return 0; +} + +int smscore_gpio_configure(struct smscore_device_t *coredev, u8 PinNum, + struct smscore_gpio_config *pGpioConfig) { + + u32 totalLen; + u32 TranslatedPinNum = 0; + u32 GroupNum = 0; + u32 ElectricChar; + u32 groupCfg; + void *buffer; + int rc; + + struct SetGpioMsg { + struct SmsMsgHdr_ST xMsgHeader; + u32 msgData[6]; + } *pMsg; + + + if (PinNum > MAX_GPIO_PIN_NUMBER) + return -EINVAL; + + if (pGpioConfig == NULL) + return -EINVAL; + + totalLen = sizeof(struct SmsMsgHdr_ST) + (sizeof(u32) * 6); + + buffer = kmalloc(totalLen + SMS_DMA_ALIGNMENT, + GFP_KERNEL | GFP_DMA); + if (!buffer) + return -ENOMEM; + + pMsg = (struct SetGpioMsg *) SMS_ALIGN_ADDRESS(buffer); + + pMsg->xMsgHeader.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; + pMsg->xMsgHeader.msgDstId = HIF_TASK; + pMsg->xMsgHeader.msgFlags = 0; + pMsg->xMsgHeader.msgLength = (u16) totalLen; + pMsg->msgData[0] = PinNum; + + if (!(coredev->device_flags & SMS_DEVICE_FAMILY2)) { + pMsg->xMsgHeader.msgType = MSG_SMS_GPIO_CONFIG_REQ; + if (GetGpioPinParams(PinNum, &TranslatedPinNum, &GroupNum, + &groupCfg) != 0) { + rc = -EINVAL; + goto free; + } + + pMsg->msgData[1] = TranslatedPinNum; + pMsg->msgData[2] = GroupNum; + ElectricChar = (pGpioConfig->PullUpDown) + | (pGpioConfig->InputCharacteristics << 2) + | (pGpioConfig->OutputSlewRate << 3) + | (pGpioConfig->OutputDriving << 4); + pMsg->msgData[3] = ElectricChar; + pMsg->msgData[4] = pGpioConfig->Direction; + pMsg->msgData[5] = groupCfg; + } else { + pMsg->xMsgHeader.msgType = MSG_SMS_GPIO_CONFIG_EX_REQ; + pMsg->msgData[1] = pGpioConfig->PullUpDown; + pMsg->msgData[2] = pGpioConfig->OutputSlewRate; + pMsg->msgData[3] = pGpioConfig->OutputDriving; + pMsg->msgData[4] = pGpioConfig->Direction; + pMsg->msgData[5] = 0; + } + + smsendian_handle_tx_message((struct SmsMsgHdr_ST *)pMsg); + rc = smscore_sendrequest_and_wait(coredev, pMsg, totalLen, + &coredev->gpio_configuration_done); + + if (rc != 0) { + if (rc == -ETIME) + sms_err("smscore_gpio_configure timeout"); + else + sms_err("smscore_gpio_configure error"); + } +free: + kfree(buffer); + + return rc; +} + +int smscore_gpio_set_level(struct smscore_device_t *coredev, u8 PinNum, + u8 NewLevel) { + + u32 totalLen; + int rc; + void *buffer; + + struct SetGpioMsg { + struct SmsMsgHdr_ST xMsgHeader; + u32 msgData[3]; /* keep it 3 ! */ + } *pMsg; + + if ((NewLevel > 1) || (PinNum > MAX_GPIO_PIN_NUMBER)) + return -EINVAL; + + totalLen = sizeof(struct SmsMsgHdr_ST) + + (3 * sizeof(u32)); /* keep it 3 ! */ + + buffer = kmalloc(totalLen + SMS_DMA_ALIGNMENT, + GFP_KERNEL | GFP_DMA); + if (!buffer) + return -ENOMEM; + + pMsg = (struct SetGpioMsg *) SMS_ALIGN_ADDRESS(buffer); + + pMsg->xMsgHeader.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; + pMsg->xMsgHeader.msgDstId = HIF_TASK; + pMsg->xMsgHeader.msgFlags = 0; + pMsg->xMsgHeader.msgType = MSG_SMS_GPIO_SET_LEVEL_REQ; + pMsg->xMsgHeader.msgLength = (u16) totalLen; + pMsg->msgData[0] = PinNum; + pMsg->msgData[1] = NewLevel; + + /* Send message to SMS */ + smsendian_handle_tx_message((struct SmsMsgHdr_ST *)pMsg); + rc = smscore_sendrequest_and_wait(coredev, pMsg, totalLen, + &coredev->gpio_set_level_done); + + if (rc != 0) { + if (rc == -ETIME) + sms_err("smscore_gpio_set_level timeout"); + else + sms_err("smscore_gpio_set_level error"); + } + kfree(buffer); + + return rc; +} + +int smscore_gpio_get_level(struct smscore_device_t *coredev, u8 PinNum, + u8 *level) { + + u32 totalLen; + int rc; + void *buffer; + + struct SetGpioMsg { + struct SmsMsgHdr_ST xMsgHeader; + u32 msgData[2]; + } *pMsg; + + + if (PinNum > MAX_GPIO_PIN_NUMBER) + return -EINVAL; + + totalLen = sizeof(struct SmsMsgHdr_ST) + (2 * sizeof(u32)); + + buffer = kmalloc(totalLen + SMS_DMA_ALIGNMENT, + GFP_KERNEL | GFP_DMA); + if (!buffer) + return -ENOMEM; + + pMsg = (struct SetGpioMsg *) SMS_ALIGN_ADDRESS(buffer); + + pMsg->xMsgHeader.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; + pMsg->xMsgHeader.msgDstId = HIF_TASK; + pMsg->xMsgHeader.msgFlags = 0; + pMsg->xMsgHeader.msgType = MSG_SMS_GPIO_GET_LEVEL_REQ; + pMsg->xMsgHeader.msgLength = (u16) totalLen; + pMsg->msgData[0] = PinNum; + pMsg->msgData[1] = 0; + + /* Send message to SMS */ + smsendian_handle_tx_message((struct SmsMsgHdr_ST *)pMsg); + rc = smscore_sendrequest_and_wait(coredev, pMsg, totalLen, + &coredev->gpio_get_level_done); + + if (rc != 0) { + if (rc == -ETIME) + sms_err("smscore_gpio_get_level timeout"); + else + sms_err("smscore_gpio_get_level error"); + } + kfree(buffer); + + /* Its a race between other gpio_get_level() and the copy of the single + * global 'coredev->gpio_get_res' to the function's variable 'level' + */ + *level = coredev->gpio_get_res; + + return rc; +} + +static int __init smscore_module_init(void) +{ + int rc = 0; + + INIT_LIST_HEAD(&g_smscore_notifyees); + INIT_LIST_HEAD(&g_smscore_devices); + kmutex_init(&g_smscore_deviceslock); + + INIT_LIST_HEAD(&g_smscore_registry); + kmutex_init(&g_smscore_registrylock); + + return rc; +} + +static void __exit smscore_module_exit(void) +{ + kmutex_lock(&g_smscore_deviceslock); + while (!list_empty(&g_smscore_notifyees)) { + struct smscore_device_notifyee_t *notifyee = + (struct smscore_device_notifyee_t *) + g_smscore_notifyees.next; + + list_del(¬ifyee->entry); + kfree(notifyee); + } + kmutex_unlock(&g_smscore_deviceslock); + + kmutex_lock(&g_smscore_registrylock); + while (!list_empty(&g_smscore_registry)) { + struct smscore_registry_entry_t *entry = + (struct smscore_registry_entry_t *) + g_smscore_registry.next; + + list_del(&entry->entry); + kfree(entry); + } + kmutex_unlock(&g_smscore_registrylock); + + sms_debug(""); +} + +module_init(smscore_module_init); +module_exit(smscore_module_exit); + +MODULE_DESCRIPTION("Siano MDTV Core module"); +MODULE_AUTHOR("Siano Mobile Silicon, Inc. (uris@siano-ms.com)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/siano/smscoreapi.h b/drivers/media/usb/siano/smscoreapi.h new file mode 100644 index 000000000000..c592ae090397 --- /dev/null +++ b/drivers/media/usb/siano/smscoreapi.h @@ -0,0 +1,775 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. +Copyright (C) 2006-2008, Uri Shkolnik, Anatoly Greenblat + +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_CORE_API_H__ +#define __SMS_CORE_API_H__ + +#include <linux/device.h> +#include <linux/list.h> +#include <linux/mm.h> +#include <linux/scatterlist.h> +#include <linux/types.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/timer.h> + +#include <asm/page.h> + +#include "smsir.h" + +#define kmutex_init(_p_) mutex_init(_p_) +#define kmutex_lock(_p_) mutex_lock(_p_) +#define kmutex_trylock(_p_) mutex_trylock(_p_) +#define kmutex_unlock(_p_) mutex_unlock(_p_) + +#ifndef min +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#define SMS_PROTOCOL_MAX_RAOUNDTRIP_MS (10000) +#define SMS_ALLOC_ALIGNMENT 128 +#define SMS_DMA_ALIGNMENT 16 +#define SMS_ALIGN_ADDRESS(addr) \ + ((((uintptr_t)(addr)) + (SMS_DMA_ALIGNMENT-1)) & ~(SMS_DMA_ALIGNMENT-1)) + +#define SMS_DEVICE_FAMILY2 1 +#define SMS_ROM_NO_RESPONSE 2 +#define SMS_DEVICE_NOT_READY 0x8000000 + +enum sms_device_type_st { + SMS_STELLAR = 0, + SMS_NOVA_A0, + SMS_NOVA_B0, + SMS_VEGA, + SMS_NUM_OF_DEVICE_TYPES +}; + +struct smscore_device_t; +struct smscore_client_t; +struct smscore_buffer_t; + +typedef int (*hotplug_t)(struct smscore_device_t *coredev, + struct device *device, int arrival); + +typedef int (*setmode_t)(void *context, int mode); +typedef void (*detectmode_t)(void *context, int *mode); +typedef int (*sendrequest_t)(void *context, void *buffer, size_t size); +typedef int (*loadfirmware_t)(void *context, void *buffer, size_t size); +typedef int (*preload_t)(void *context); +typedef int (*postload_t)(void *context); + +typedef int (*onresponse_t)(void *context, struct smscore_buffer_t *cb); +typedef void (*onremove_t)(void *context); + +struct smscore_buffer_t { + /* public members, once passed to clients can be changed freely */ + struct list_head entry; + int size; + int offset; + + /* private members, read-only for clients */ + void *p; + dma_addr_t phys; + unsigned long offset_in_common; +}; + +struct smsdevice_params_t { + struct device *device; + + int buffer_size; + int num_buffers; + + char devpath[32]; + unsigned long flags; + + setmode_t setmode_handler; + detectmode_t detectmode_handler; + sendrequest_t sendrequest_handler; + preload_t preload_handler; + postload_t postload_handler; + + void *context; + enum sms_device_type_st device_type; +}; + +struct smsclient_params_t { + int initial_id; + int data_type; + onresponse_t onresponse_handler; + onremove_t onremove_handler; + void *context; +}; + +struct smscore_device_t { + struct list_head entry; + + struct list_head clients; + struct list_head subclients; + spinlock_t clientslock; + + struct list_head buffers; + spinlock_t bufferslock; + int num_buffers; + + void *common_buffer; + int common_buffer_size; + dma_addr_t common_buffer_phys; + + void *context; + struct device *device; + + char devpath[32]; + unsigned long device_flags; + + setmode_t setmode_handler; + detectmode_t detectmode_handler; + sendrequest_t sendrequest_handler; + preload_t preload_handler; + postload_t postload_handler; + + int mode, modes_supported; + + /* host <--> device messages */ + struct completion version_ex_done, data_download_done, trigger_done; + struct completion init_device_done, reload_start_done, resume_done; + struct completion gpio_configuration_done, gpio_set_level_done; + struct completion gpio_get_level_done, ir_init_done; + + /* Buffer management */ + wait_queue_head_t buffer_mng_waitq; + + /* GPIO */ + int gpio_get_res; + + /* Target hardware board */ + int board_id; + + /* Firmware */ + u8 *fw_buf; + u32 fw_buf_size; + + /* Infrared (IR) */ + struct ir_t ir; + + int led_state; +}; + +/* GPIO definitions for antenna frequency domain control (SMS8021) */ +#define SMS_ANTENNA_GPIO_0 1 +#define SMS_ANTENNA_GPIO_1 0 + +#define BW_8_MHZ 0 +#define BW_7_MHZ 1 +#define BW_6_MHZ 2 +#define BW_5_MHZ 3 +#define BW_ISDBT_1SEG 4 +#define BW_ISDBT_3SEG 5 + +#define MSG_HDR_FLAG_SPLIT_MSG 4 + +#define MAX_GPIO_PIN_NUMBER 31 + +#define HIF_TASK 11 +#define SMS_HOST_LIB 150 +#define DVBT_BDA_CONTROL_MSG_ID 201 + +#define SMS_MAX_PAYLOAD_SIZE 240 +#define SMS_TUNE_TIMEOUT 500 + +#define MSG_SMS_GPIO_CONFIG_REQ 507 +#define MSG_SMS_GPIO_CONFIG_RES 508 +#define MSG_SMS_GPIO_SET_LEVEL_REQ 509 +#define MSG_SMS_GPIO_SET_LEVEL_RES 510 +#define MSG_SMS_GPIO_GET_LEVEL_REQ 511 +#define MSG_SMS_GPIO_GET_LEVEL_RES 512 +#define MSG_SMS_RF_TUNE_REQ 561 +#define MSG_SMS_RF_TUNE_RES 562 +#define MSG_SMS_INIT_DEVICE_REQ 578 +#define MSG_SMS_INIT_DEVICE_RES 579 +#define MSG_SMS_ADD_PID_FILTER_REQ 601 +#define MSG_SMS_ADD_PID_FILTER_RES 602 +#define MSG_SMS_REMOVE_PID_FILTER_REQ 603 +#define MSG_SMS_REMOVE_PID_FILTER_RES 604 +#define MSG_SMS_DAB_CHANNEL 607 +#define MSG_SMS_GET_PID_FILTER_LIST_REQ 608 +#define MSG_SMS_GET_PID_FILTER_LIST_RES 609 +#define MSG_SMS_GET_STATISTICS_RES 616 +#define MSG_SMS_GET_STATISTICS_REQ 615 +#define MSG_SMS_HO_PER_SLICES_IND 630 +#define MSG_SMS_SET_ANTENNA_CONFIG_REQ 651 +#define MSG_SMS_SET_ANTENNA_CONFIG_RES 652 +#define MSG_SMS_SLEEP_RESUME_COMP_IND 655 +#define MSG_SMS_DATA_DOWNLOAD_REQ 660 +#define MSG_SMS_DATA_DOWNLOAD_RES 661 +#define MSG_SMS_SWDOWNLOAD_TRIGGER_REQ 664 +#define MSG_SMS_SWDOWNLOAD_TRIGGER_RES 665 +#define MSG_SMS_SWDOWNLOAD_BACKDOOR_REQ 666 +#define MSG_SMS_SWDOWNLOAD_BACKDOOR_RES 667 +#define MSG_SMS_GET_VERSION_EX_REQ 668 +#define MSG_SMS_GET_VERSION_EX_RES 669 +#define MSG_SMS_SET_CLOCK_OUTPUT_REQ 670 +#define MSG_SMS_I2C_SET_FREQ_REQ 685 +#define MSG_SMS_GENERIC_I2C_REQ 687 +#define MSG_SMS_GENERIC_I2C_RES 688 +#define MSG_SMS_DVBT_BDA_DATA 693 +#define MSG_SW_RELOAD_REQ 697 +#define MSG_SMS_DATA_MSG 699 +#define MSG_SW_RELOAD_START_REQ 702 +#define MSG_SW_RELOAD_START_RES 703 +#define MSG_SW_RELOAD_EXEC_REQ 704 +#define MSG_SW_RELOAD_EXEC_RES 705 +#define MSG_SMS_SPI_INT_LINE_SET_REQ 710 +#define MSG_SMS_GPIO_CONFIG_EX_REQ 712 +#define MSG_SMS_GPIO_CONFIG_EX_RES 713 +#define MSG_SMS_ISDBT_TUNE_REQ 776 +#define MSG_SMS_ISDBT_TUNE_RES 777 +#define MSG_SMS_TRANSMISSION_IND 782 +#define MSG_SMS_START_IR_REQ 800 +#define MSG_SMS_START_IR_RES 801 +#define MSG_SMS_IR_SAMPLES_IND 802 +#define MSG_SMS_SIGNAL_DETECTED_IND 827 +#define MSG_SMS_NO_SIGNAL_IND 828 + +#define SMS_INIT_MSG_EX(ptr, type, src, dst, len) do { \ + (ptr)->msgType = type; (ptr)->msgSrcId = src; (ptr)->msgDstId = dst; \ + (ptr)->msgLength = len; (ptr)->msgFlags = 0; \ +} while (0) + +#define SMS_INIT_MSG(ptr, type, len) \ + SMS_INIT_MSG_EX(ptr, type, 0, HIF_TASK, len) + +enum SMS_DVB3_EVENTS { + DVB3_EVENT_INIT = 0, + DVB3_EVENT_SLEEP, + DVB3_EVENT_HOTPLUG, + DVB3_EVENT_FE_LOCK, + DVB3_EVENT_FE_UNLOCK, + DVB3_EVENT_UNC_OK, + DVB3_EVENT_UNC_ERR +}; + +enum SMS_DEVICE_MODE { + DEVICE_MODE_NONE = -1, + DEVICE_MODE_DVBT = 0, + DEVICE_MODE_DVBH, + DEVICE_MODE_DAB_TDMB, + DEVICE_MODE_DAB_TDMB_DABIP, + DEVICE_MODE_DVBT_BDA, + DEVICE_MODE_ISDBT, + DEVICE_MODE_ISDBT_BDA, + DEVICE_MODE_CMMB, + DEVICE_MODE_RAW_TUNER, + DEVICE_MODE_MAX, +}; + +struct SmsMsgHdr_ST { + u16 msgType; + u8 msgSrcId; + u8 msgDstId; + u16 msgLength; /* Length of entire message, including header */ + u16 msgFlags; +}; + +struct SmsMsgData_ST { + struct SmsMsgHdr_ST xMsgHeader; + u32 msgData[1]; +}; + +struct SmsMsgData_ST2 { + struct SmsMsgHdr_ST xMsgHeader; + u32 msgData[2]; +}; + +struct SmsDataDownload_ST { + struct SmsMsgHdr_ST xMsgHeader; + u32 MemAddr; + u8 Payload[SMS_MAX_PAYLOAD_SIZE]; +}; + +struct SmsVersionRes_ST { + struct SmsMsgHdr_ST xMsgHeader; + + u16 ChipModel; /* e.g. 0x1102 for SMS-1102 "Nova" */ + u8 Step; /* 0 - Step A */ + u8 MetalFix; /* 0 - Metal 0 */ + + /* FirmwareId 0xFF if ROM, otherwise the + * value indicated by SMSHOSTLIB_DEVICE_MODES_E */ + u8 FirmwareId; + /* SupportedProtocols Bitwise OR combination of + * supported protocols */ + u8 SupportedProtocols; + + u8 VersionMajor; + u8 VersionMinor; + u8 VersionPatch; + u8 VersionFieldPatch; + + u8 RomVersionMajor; + u8 RomVersionMinor; + u8 RomVersionPatch; + u8 RomVersionFieldPatch; + + u8 TextLabel[34]; +}; + +struct SmsFirmware_ST { + u32 CheckSum; + u32 Length; + u32 StartAddress; + u8 Payload[1]; +}; + +/* Statistics information returned as response for + * SmsHostApiGetStatistics_Req */ +struct SMSHOSTLIB_STATISTICS_ST { + u32 Reserved; /* Reserved */ + + /* Common parameters */ + u32 IsRfLocked; /* 0 - not locked, 1 - locked */ + u32 IsDemodLocked; /* 0 - not locked, 1 - locked */ + u32 IsExternalLNAOn; /* 0 - external LNA off, 1 - external LNA on */ + + /* Reception quality */ + s32 SNR; /* dB */ + u32 BER; /* Post Viterbi BER [1E-5] */ + u32 FIB_CRC; /* CRC errors percentage, valid only for DAB */ + u32 TS_PER; /* Transport stream PER, + 0xFFFFFFFF indicate N/A, valid only for DVB-T/H */ + u32 MFER; /* DVB-H frame error rate in percentage, + 0xFFFFFFFF indicate N/A, valid only for DVB-H */ + s32 RSSI; /* dBm */ + s32 InBandPwr; /* In band power in dBM */ + s32 CarrierOffset; /* Carrier Offset in bin/1024 */ + + /* Transmission parameters */ + u32 Frequency; /* Frequency in Hz */ + u32 Bandwidth; /* Bandwidth in MHz, valid only for DVB-T/H */ + u32 TransmissionMode; /* Transmission Mode, for DAB modes 1-4, + for DVB-T/H FFT mode carriers in Kilos */ + u32 ModemState; /* from SMSHOSTLIB_DVB_MODEM_STATE_ET, + valid only for DVB-T/H */ + u32 GuardInterval; /* Guard Interval from + SMSHOSTLIB_GUARD_INTERVALS_ET, valid only for DVB-T/H */ + u32 CodeRate; /* Code Rate from SMSHOSTLIB_CODE_RATE_ET, + valid only for DVB-T/H */ + u32 LPCodeRate; /* Low Priority Code Rate from + SMSHOSTLIB_CODE_RATE_ET, valid only for DVB-T/H */ + u32 Hierarchy; /* Hierarchy from SMSHOSTLIB_HIERARCHY_ET, + valid only for DVB-T/H */ + u32 Constellation; /* Constellation from + SMSHOSTLIB_CONSTELLATION_ET, valid only for DVB-T/H */ + + /* Burst parameters, valid only for DVB-H */ + u32 BurstSize; /* Current burst size in bytes, + valid only for DVB-H */ + u32 BurstDuration; /* Current burst duration in mSec, + valid only for DVB-H */ + u32 BurstCycleTime; /* Current burst cycle time in mSec, + valid only for DVB-H */ + u32 CalculatedBurstCycleTime;/* Current burst cycle time in mSec, + as calculated by demodulator, valid only for DVB-H */ + u32 NumOfRows; /* Number of rows in MPE table, + valid only for DVB-H */ + u32 NumOfPaddCols; /* Number of padding columns in MPE table, + valid only for DVB-H */ + u32 NumOfPunctCols; /* Number of puncturing columns in MPE table, + valid only for DVB-H */ + u32 ErrorTSPackets; /* Number of erroneous + transport-stream packets */ + u32 TotalTSPackets; /* Total number of transport-stream packets */ + u32 NumOfValidMpeTlbs; /* Number of MPE tables which do not include + errors after MPE RS decoding */ + u32 NumOfInvalidMpeTlbs;/* Number of MPE tables which include errors + after MPE RS decoding */ + u32 NumOfCorrectedMpeTlbs;/* Number of MPE tables which were + corrected by MPE RS decoding */ + /* Common params */ + u32 BERErrorCount; /* Number of errornous SYNC bits. */ + u32 BERBitCount; /* Total number of SYNC bits. */ + + /* Interface information */ + u32 SmsToHostTxErrors; /* Total number of transmission errors. */ + + /* DAB/T-DMB */ + u32 PreBER; /* DAB/T-DMB only: Pre Viterbi BER [1E-5] */ + + /* DVB-H TPS parameters */ + u32 CellId; /* TPS Cell ID in bits 15..0, bits 31..16 zero; + if set to 0xFFFFFFFF cell_id not yet recovered */ + u32 DvbhSrvIndHP; /* DVB-H service indication info, bit 1 - + Time Slicing indicator, bit 0 - MPE-FEC indicator */ + u32 DvbhSrvIndLP; /* DVB-H service indication info, bit 1 - + Time Slicing indicator, bit 0 - MPE-FEC indicator */ + + u32 NumMPEReceived; /* DVB-H, Num MPE section received */ + + u32 ReservedFields[10]; /* Reserved */ +}; + +struct SmsMsgStatisticsInfo_ST { + u32 RequestResult; + + struct SMSHOSTLIB_STATISTICS_ST Stat; + + /* Split the calc of the SNR in DAB */ + u32 Signal; /* dB */ + u32 Noise; /* dB */ + +}; + +struct SMSHOSTLIB_ISDBT_LAYER_STAT_ST { + /* Per-layer information */ + u32 CodeRate; /* Code Rate from SMSHOSTLIB_CODE_RATE_ET, + * 255 means layer does not exist */ + u32 Constellation; /* Constellation from SMSHOSTLIB_CONSTELLATION_ET, + * 255 means layer does not exist */ + u32 BER; /* Post Viterbi BER [1E-5], 0xFFFFFFFF indicate N/A */ + u32 BERErrorCount; /* Post Viterbi Error Bits Count */ + u32 BERBitCount; /* Post Viterbi Total Bits Count */ + u32 PreBER; /* Pre Viterbi BER [1E-5], 0xFFFFFFFF indicate N/A */ + u32 TS_PER; /* Transport stream PER [%], 0xFFFFFFFF indicate N/A */ + u32 ErrorTSPackets; /* Number of erroneous transport-stream packets */ + u32 TotalTSPackets; /* Total number of transport-stream packets */ + u32 TILdepthI; /* Time interleaver depth I parameter, + * 255 means layer does not exist */ + u32 NumberOfSegments; /* Number of segments in layer A, + * 255 means layer does not exist */ + u32 TMCCErrors; /* TMCC errors */ +}; + +struct SMSHOSTLIB_STATISTICS_ISDBT_ST { + u32 StatisticsType; /* Enumerator identifying the type of the + * structure. Values are the same as + * SMSHOSTLIB_DEVICE_MODES_E + * + * This field MUST always be first in any + * statistics structure */ + + u32 FullSize; /* Total size of the structure returned by the modem. + * If the size requested by the host is smaller than + * FullSize, the struct will be truncated */ + + /* Common parameters */ + u32 IsRfLocked; /* 0 - not locked, 1 - locked */ + u32 IsDemodLocked; /* 0 - not locked, 1 - locked */ + u32 IsExternalLNAOn; /* 0 - external LNA off, 1 - external LNA on */ + + /* Reception quality */ + s32 SNR; /* dB */ + s32 RSSI; /* dBm */ + s32 InBandPwr; /* In band power in dBM */ + s32 CarrierOffset; /* Carrier Offset in Hz */ + + /* Transmission parameters */ + u32 Frequency; /* Frequency in Hz */ + u32 Bandwidth; /* Bandwidth in MHz */ + u32 TransmissionMode; /* ISDB-T transmission mode */ + u32 ModemState; /* 0 - Acquisition, 1 - Locked */ + u32 GuardInterval; /* Guard Interval, 1 divided by value */ + u32 SystemType; /* ISDB-T system type (ISDB-T / ISDB-Tsb) */ + u32 PartialReception; /* TRUE - partial reception, FALSE otherwise */ + u32 NumOfLayers; /* Number of ISDB-T layers in the network */ + + /* Per-layer information */ + /* Layers A, B and C */ + struct SMSHOSTLIB_ISDBT_LAYER_STAT_ST LayerInfo[3]; + /* Per-layer statistics, see SMSHOSTLIB_ISDBT_LAYER_STAT_ST */ + + /* Interface information */ + u32 SmsToHostTxErrors; /* Total number of transmission errors. */ +}; + +struct PID_STATISTICS_DATA_S { + struct PID_BURST_S { + u32 size; + u32 padding_cols; + u32 punct_cols; + u32 duration; + u32 cycle; + u32 calc_cycle; + } burst; + + u32 tot_tbl_cnt; + u32 invalid_tbl_cnt; + u32 tot_cor_tbl; +}; + +struct PID_DATA_S { + u32 pid; + u32 num_rows; + struct PID_STATISTICS_DATA_S pid_statistics; +}; + +#define CORRECT_STAT_RSSI(_stat) ((_stat).RSSI *= -1) +#define CORRECT_STAT_BANDWIDTH(_stat) (_stat.Bandwidth = 8 - _stat.Bandwidth) +#define CORRECT_STAT_TRANSMISSON_MODE(_stat) \ + if (_stat.TransmissionMode == 0) \ + _stat.TransmissionMode = 2; \ + else if (_stat.TransmissionMode == 1) \ + _stat.TransmissionMode = 8; \ + else \ + _stat.TransmissionMode = 4; + +struct TRANSMISSION_STATISTICS_S { + u32 Frequency; /* Frequency in Hz */ + u32 Bandwidth; /* Bandwidth in MHz */ + u32 TransmissionMode; /* FFT mode carriers in Kilos */ + u32 GuardInterval; /* Guard Interval from + SMSHOSTLIB_GUARD_INTERVALS_ET */ + u32 CodeRate; /* Code Rate from SMSHOSTLIB_CODE_RATE_ET */ + u32 LPCodeRate; /* Low Priority Code Rate from + SMSHOSTLIB_CODE_RATE_ET */ + u32 Hierarchy; /* Hierarchy from SMSHOSTLIB_HIERARCHY_ET */ + u32 Constellation; /* Constellation from + SMSHOSTLIB_CONSTELLATION_ET */ + + /* DVB-H TPS parameters */ + u32 CellId; /* TPS Cell ID in bits 15..0, bits 31..16 zero; + if set to 0xFFFFFFFF cell_id not yet recovered */ + u32 DvbhSrvIndHP; /* DVB-H service indication info, bit 1 - + Time Slicing indicator, bit 0 - MPE-FEC indicator */ + u32 DvbhSrvIndLP; /* DVB-H service indication info, bit 1 - + Time Slicing indicator, bit 0 - MPE-FEC indicator */ + u32 IsDemodLocked; /* 0 - not locked, 1 - locked */ +}; + +struct RECEPTION_STATISTICS_S { + u32 IsRfLocked; /* 0 - not locked, 1 - locked */ + u32 IsDemodLocked; /* 0 - not locked, 1 - locked */ + u32 IsExternalLNAOn; /* 0 - external LNA off, 1 - external LNA on */ + + u32 ModemState; /* from SMSHOSTLIB_DVB_MODEM_STATE_ET */ + s32 SNR; /* dB */ + u32 BER; /* Post Viterbi BER [1E-5] */ + u32 BERErrorCount; /* Number of erronous SYNC bits. */ + u32 BERBitCount; /* Total number of SYNC bits. */ + u32 TS_PER; /* Transport stream PER, + 0xFFFFFFFF indicate N/A */ + u32 MFER; /* DVB-H frame error rate in percentage, + 0xFFFFFFFF indicate N/A, valid only for DVB-H */ + s32 RSSI; /* dBm */ + s32 InBandPwr; /* In band power in dBM */ + s32 CarrierOffset; /* Carrier Offset in bin/1024 */ + u32 ErrorTSPackets; /* Number of erroneous + transport-stream packets */ + u32 TotalTSPackets; /* Total number of transport-stream packets */ + + s32 MRC_SNR; /* dB */ + s32 MRC_RSSI; /* dBm */ + s32 MRC_InBandPwr; /* In band power in dBM */ +}; + + +/* Statistics information returned as response for + * SmsHostApiGetStatisticsEx_Req for DVB applications, SMS1100 and up */ +struct SMSHOSTLIB_STATISTICS_DVB_S { + /* Reception */ + struct RECEPTION_STATISTICS_S ReceptionData; + + /* Transmission parameters */ + struct TRANSMISSION_STATISTICS_S TransmissionData; + + /* Burst parameters, valid only for DVB-H */ +#define SRVM_MAX_PID_FILTERS 8 + struct PID_DATA_S PidData[SRVM_MAX_PID_FILTERS]; +}; + +struct SRVM_SIGNAL_STATUS_S { + u32 result; + u32 snr; + u32 tsPackets; + u32 etsPackets; + u32 constellation; + u32 hpCode; + u32 tpsSrvIndLP; + u32 tpsSrvIndHP; + u32 cellId; + u32 reason; + + s32 inBandPower; + u32 requestId; +}; + +struct SMSHOSTLIB_I2C_REQ_ST { + u32 DeviceAddress; /* I2c device address */ + u32 WriteCount; /* number of bytes to write */ + u32 ReadCount; /* number of bytes to read */ + u8 Data[1]; +}; + +struct SMSHOSTLIB_I2C_RES_ST { + u32 Status; /* non-zero value in case of failure */ + u32 ReadCount; /* number of bytes read */ + u8 Data[1]; +}; + + +struct smscore_config_gpio { +#define SMS_GPIO_DIRECTION_INPUT 0 +#define SMS_GPIO_DIRECTION_OUTPUT 1 + u8 direction; + +#define SMS_GPIO_PULLUPDOWN_NONE 0 +#define SMS_GPIO_PULLUPDOWN_PULLDOWN 1 +#define SMS_GPIO_PULLUPDOWN_PULLUP 2 +#define SMS_GPIO_PULLUPDOWN_KEEPER 3 + u8 pullupdown; + +#define SMS_GPIO_INPUTCHARACTERISTICS_NORMAL 0 +#define SMS_GPIO_INPUTCHARACTERISTICS_SCHMITT 1 + u8 inputcharacteristics; + +#define SMS_GPIO_OUTPUTSLEWRATE_FAST 0 +#define SMS_GPIO_OUTPUTSLEWRATE_SLOW 1 + u8 outputslewrate; + +#define SMS_GPIO_OUTPUTDRIVING_4mA 0 +#define SMS_GPIO_OUTPUTDRIVING_8mA 1 +#define SMS_GPIO_OUTPUTDRIVING_12mA 2 +#define SMS_GPIO_OUTPUTDRIVING_16mA 3 + u8 outputdriving; +}; + +struct smscore_gpio_config { +#define SMS_GPIO_DIRECTION_INPUT 0 +#define SMS_GPIO_DIRECTION_OUTPUT 1 + u8 Direction; + +#define SMS_GPIO_PULL_UP_DOWN_NONE 0 +#define SMS_GPIO_PULL_UP_DOWN_PULLDOWN 1 +#define SMS_GPIO_PULL_UP_DOWN_PULLUP 2 +#define SMS_GPIO_PULL_UP_DOWN_KEEPER 3 + u8 PullUpDown; + +#define SMS_GPIO_INPUT_CHARACTERISTICS_NORMAL 0 +#define SMS_GPIO_INPUT_CHARACTERISTICS_SCHMITT 1 + u8 InputCharacteristics; + +#define SMS_GPIO_OUTPUT_SLEW_RATE_SLOW 1 /* 10xx */ +#define SMS_GPIO_OUTPUT_SLEW_RATE_FAST 0 /* 10xx */ + + +#define SMS_GPIO_OUTPUT_SLEW_RATE_0_45_V_NS 0 /* 11xx */ +#define SMS_GPIO_OUTPUT_SLEW_RATE_0_9_V_NS 1 /* 11xx */ +#define SMS_GPIO_OUTPUT_SLEW_RATE_1_7_V_NS 2 /* 11xx */ +#define SMS_GPIO_OUTPUT_SLEW_RATE_3_3_V_NS 3 /* 11xx */ + u8 OutputSlewRate; + +#define SMS_GPIO_OUTPUT_DRIVING_S_4mA 0 /* 10xx */ +#define SMS_GPIO_OUTPUT_DRIVING_S_8mA 1 /* 10xx */ +#define SMS_GPIO_OUTPUT_DRIVING_S_12mA 2 /* 10xx */ +#define SMS_GPIO_OUTPUT_DRIVING_S_16mA 3 /* 10xx */ + +#define SMS_GPIO_OUTPUT_DRIVING_1_5mA 0 /* 11xx */ +#define SMS_GPIO_OUTPUT_DRIVING_2_8mA 1 /* 11xx */ +#define SMS_GPIO_OUTPUT_DRIVING_4mA 2 /* 11xx */ +#define SMS_GPIO_OUTPUT_DRIVING_7mA 3 /* 11xx */ +#define SMS_GPIO_OUTPUT_DRIVING_10mA 4 /* 11xx */ +#define SMS_GPIO_OUTPUT_DRIVING_11mA 5 /* 11xx */ +#define SMS_GPIO_OUTPUT_DRIVING_14mA 6 /* 11xx */ +#define SMS_GPIO_OUTPUT_DRIVING_16mA 7 /* 11xx */ + u8 OutputDriving; +}; + +extern void smscore_registry_setmode(char *devpath, int mode); +extern int smscore_registry_getmode(char *devpath); + +extern int smscore_register_hotplug(hotplug_t hotplug); +extern void smscore_unregister_hotplug(hotplug_t hotplug); + +extern int smscore_register_device(struct smsdevice_params_t *params, + struct smscore_device_t **coredev); +extern void smscore_unregister_device(struct smscore_device_t *coredev); + +extern int smscore_start_device(struct smscore_device_t *coredev); +extern int smscore_load_firmware(struct smscore_device_t *coredev, + char *filename, + loadfirmware_t loadfirmware_handler); + +extern int smscore_set_device_mode(struct smscore_device_t *coredev, int mode); +extern int smscore_get_device_mode(struct smscore_device_t *coredev); + +extern int smscore_register_client(struct smscore_device_t *coredev, + struct smsclient_params_t *params, + struct smscore_client_t **client); +extern void smscore_unregister_client(struct smscore_client_t *client); + +extern int smsclient_sendrequest(struct smscore_client_t *client, + void *buffer, size_t size); +extern void smscore_onresponse(struct smscore_device_t *coredev, + struct smscore_buffer_t *cb); + +extern int smscore_get_common_buffer_size(struct smscore_device_t *coredev); +extern int smscore_map_common_buffer(struct smscore_device_t *coredev, + struct vm_area_struct *vma); +extern int smscore_get_fw_filename(struct smscore_device_t *coredev, + int mode, char *filename); +extern int smscore_send_fw_file(struct smscore_device_t *coredev, + u8 *ufwbuf, int size); + +extern +struct smscore_buffer_t *smscore_getbuffer(struct smscore_device_t *coredev); +extern void smscore_putbuffer(struct smscore_device_t *coredev, + struct smscore_buffer_t *cb); + +/* old GPIO management */ +int smscore_configure_gpio(struct smscore_device_t *coredev, u32 pin, + struct smscore_config_gpio *pinconfig); +int smscore_set_gpio(struct smscore_device_t *coredev, u32 pin, int level); + +/* new GPIO management */ +extern int smscore_gpio_configure(struct smscore_device_t *coredev, u8 PinNum, + struct smscore_gpio_config *pGpioConfig); +extern int smscore_gpio_set_level(struct smscore_device_t *coredev, u8 PinNum, + u8 NewLevel); +extern int smscore_gpio_get_level(struct smscore_device_t *coredev, u8 PinNum, + u8 *level); + +void smscore_set_board_id(struct smscore_device_t *core, int id); +int smscore_get_board_id(struct smscore_device_t *core); + +int smscore_led_state(struct smscore_device_t *core, int led); + + +/* ------------------------------------------------------------------------ */ + +#define DBG_INFO 1 +#define DBG_ADV 2 + +#define sms_printk(kern, fmt, arg...) \ + printk(kern "%s: " fmt "\n", __func__, ##arg) + +#define dprintk(kern, lvl, fmt, arg...) do {\ + if (sms_dbg & lvl) \ + sms_printk(kern, fmt, ##arg); } while (0) + +#define sms_log(fmt, arg...) sms_printk(KERN_INFO, fmt, ##arg) +#define sms_err(fmt, arg...) \ + sms_printk(KERN_ERR, "line: %d: " fmt, __LINE__, ##arg) +#define sms_warn(fmt, arg...) sms_printk(KERN_WARNING, fmt, ##arg) +#define sms_info(fmt, arg...) \ + dprintk(KERN_INFO, DBG_INFO, fmt, ##arg) +#define sms_debug(fmt, arg...) \ + dprintk(KERN_DEBUG, DBG_ADV, fmt, ##arg) + + +#endif /* __SMS_CORE_API_H__ */ diff --git a/drivers/media/usb/siano/smsdvb.c b/drivers/media/usb/siano/smsdvb.c new file mode 100644 index 000000000000..aa77e54a8fae --- /dev/null +++ b/drivers/media/usb/siano/smsdvb.c @@ -0,0 +1,1078 @@ +/**************************************************************** + +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 <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> + +#include "dmxdev.h" +#include "dvbdev.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" + +#include "smscoreapi.h" +#include "smsendian.h" +#include "sms-cards.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +struct smsdvb_client_t { + struct list_head entry; + + struct smscore_device_t *coredev; + struct smscore_client_t *smsclient; + + struct dvb_adapter adapter; + struct dvb_demux demux; + struct dmxdev dmxdev; + struct dvb_frontend frontend; + + fe_status_t fe_status; + + struct completion tune_done; + + struct SMSHOSTLIB_STATISTICS_DVB_S sms_stat_dvb; + int event_fe_state; + int event_unc_state; +}; + +static struct list_head g_smsdvb_clients; +static struct mutex g_smsdvb_clientslock; + +static int sms_dbg; +module_param_named(debug, sms_dbg, int, 0644); +MODULE_PARM_DESC(debug, "set debug level (info=1, adv=2 (or-able))"); + +/* Events that may come from DVB v3 adapter */ +static void sms_board_dvb3_event(struct smsdvb_client_t *client, + enum SMS_DVB3_EVENTS event) { + + struct smscore_device_t *coredev = client->coredev; + switch (event) { + case DVB3_EVENT_INIT: + sms_debug("DVB3_EVENT_INIT"); + sms_board_event(coredev, BOARD_EVENT_BIND); + break; + case DVB3_EVENT_SLEEP: + sms_debug("DVB3_EVENT_SLEEP"); + sms_board_event(coredev, BOARD_EVENT_POWER_SUSPEND); + break; + case DVB3_EVENT_HOTPLUG: + sms_debug("DVB3_EVENT_HOTPLUG"); + sms_board_event(coredev, BOARD_EVENT_POWER_INIT); + break; + case DVB3_EVENT_FE_LOCK: + if (client->event_fe_state != DVB3_EVENT_FE_LOCK) { + client->event_fe_state = DVB3_EVENT_FE_LOCK; + sms_debug("DVB3_EVENT_FE_LOCK"); + sms_board_event(coredev, BOARD_EVENT_FE_LOCK); + } + break; + case DVB3_EVENT_FE_UNLOCK: + if (client->event_fe_state != DVB3_EVENT_FE_UNLOCK) { + client->event_fe_state = DVB3_EVENT_FE_UNLOCK; + sms_debug("DVB3_EVENT_FE_UNLOCK"); + sms_board_event(coredev, BOARD_EVENT_FE_UNLOCK); + } + break; + case DVB3_EVENT_UNC_OK: + if (client->event_unc_state != DVB3_EVENT_UNC_OK) { + client->event_unc_state = DVB3_EVENT_UNC_OK; + sms_debug("DVB3_EVENT_UNC_OK"); + sms_board_event(coredev, BOARD_EVENT_MULTIPLEX_OK); + } + break; + case DVB3_EVENT_UNC_ERR: + if (client->event_unc_state != DVB3_EVENT_UNC_ERR) { + client->event_unc_state = DVB3_EVENT_UNC_ERR; + sms_debug("DVB3_EVENT_UNC_ERR"); + sms_board_event(coredev, BOARD_EVENT_MULTIPLEX_ERRORS); + } + break; + + default: + sms_err("Unknown dvb3 api event"); + break; + } +} + + +static void smsdvb_update_dvb_stats(struct RECEPTION_STATISTICS_S *pReceptionData, + struct SMSHOSTLIB_STATISTICS_ST *p) +{ + if (sms_dbg & 2) { + printk(KERN_DEBUG "Reserved = %d", p->Reserved); + printk(KERN_DEBUG "IsRfLocked = %d", p->IsRfLocked); + printk(KERN_DEBUG "IsDemodLocked = %d", p->IsDemodLocked); + printk(KERN_DEBUG "IsExternalLNAOn = %d", p->IsExternalLNAOn); + printk(KERN_DEBUG "SNR = %d", p->SNR); + printk(KERN_DEBUG "BER = %d", p->BER); + printk(KERN_DEBUG "FIB_CRC = %d", p->FIB_CRC); + printk(KERN_DEBUG "TS_PER = %d", p->TS_PER); + printk(KERN_DEBUG "MFER = %d", p->MFER); + printk(KERN_DEBUG "RSSI = %d", p->RSSI); + printk(KERN_DEBUG "InBandPwr = %d", p->InBandPwr); + printk(KERN_DEBUG "CarrierOffset = %d", p->CarrierOffset); + printk(KERN_DEBUG "Frequency = %d", p->Frequency); + printk(KERN_DEBUG "Bandwidth = %d", p->Bandwidth); + printk(KERN_DEBUG "TransmissionMode = %d", p->TransmissionMode); + printk(KERN_DEBUG "ModemState = %d", p->ModemState); + printk(KERN_DEBUG "GuardInterval = %d", p->GuardInterval); + printk(KERN_DEBUG "CodeRate = %d", p->CodeRate); + printk(KERN_DEBUG "LPCodeRate = %d", p->LPCodeRate); + printk(KERN_DEBUG "Hierarchy = %d", p->Hierarchy); + printk(KERN_DEBUG "Constellation = %d", p->Constellation); + printk(KERN_DEBUG "BurstSize = %d", p->BurstSize); + printk(KERN_DEBUG "BurstDuration = %d", p->BurstDuration); + printk(KERN_DEBUG "BurstCycleTime = %d", p->BurstCycleTime); + printk(KERN_DEBUG "CalculatedBurstCycleTime = %d", p->CalculatedBurstCycleTime); + printk(KERN_DEBUG "NumOfRows = %d", p->NumOfRows); + printk(KERN_DEBUG "NumOfPaddCols = %d", p->NumOfPaddCols); + printk(KERN_DEBUG "NumOfPunctCols = %d", p->NumOfPunctCols); + printk(KERN_DEBUG "ErrorTSPackets = %d", p->ErrorTSPackets); + printk(KERN_DEBUG "TotalTSPackets = %d", p->TotalTSPackets); + printk(KERN_DEBUG "NumOfValidMpeTlbs = %d", p->NumOfValidMpeTlbs); + printk(KERN_DEBUG "NumOfInvalidMpeTlbs = %d", p->NumOfInvalidMpeTlbs); + printk(KERN_DEBUG "NumOfCorrectedMpeTlbs = %d", p->NumOfCorrectedMpeTlbs); + printk(KERN_DEBUG "BERErrorCount = %d", p->BERErrorCount); + printk(KERN_DEBUG "BERBitCount = %d", p->BERBitCount); + printk(KERN_DEBUG "SmsToHostTxErrors = %d", p->SmsToHostTxErrors); + printk(KERN_DEBUG "PreBER = %d", p->PreBER); + printk(KERN_DEBUG "CellId = %d", p->CellId); + printk(KERN_DEBUG "DvbhSrvIndHP = %d", p->DvbhSrvIndHP); + printk(KERN_DEBUG "DvbhSrvIndLP = %d", p->DvbhSrvIndLP); + printk(KERN_DEBUG "NumMPEReceived = %d", p->NumMPEReceived); + } + + pReceptionData->IsDemodLocked = p->IsDemodLocked; + + pReceptionData->SNR = p->SNR; + pReceptionData->BER = p->BER; + pReceptionData->BERErrorCount = p->BERErrorCount; + pReceptionData->InBandPwr = p->InBandPwr; + pReceptionData->ErrorTSPackets = p->ErrorTSPackets; +}; + + +static void smsdvb_update_isdbt_stats(struct RECEPTION_STATISTICS_S *pReceptionData, + struct SMSHOSTLIB_STATISTICS_ISDBT_ST *p) +{ + int i; + + if (sms_dbg & 2) { + printk(KERN_DEBUG "IsRfLocked = %d", p->IsRfLocked); + printk(KERN_DEBUG "IsDemodLocked = %d", p->IsDemodLocked); + printk(KERN_DEBUG "IsExternalLNAOn = %d", p->IsExternalLNAOn); + printk(KERN_DEBUG "SNR = %d", p->SNR); + printk(KERN_DEBUG "RSSI = %d", p->RSSI); + printk(KERN_DEBUG "InBandPwr = %d", p->InBandPwr); + printk(KERN_DEBUG "CarrierOffset = %d", p->CarrierOffset); + printk(KERN_DEBUG "Frequency = %d", p->Frequency); + printk(KERN_DEBUG "Bandwidth = %d", p->Bandwidth); + printk(KERN_DEBUG "TransmissionMode = %d", p->TransmissionMode); + printk(KERN_DEBUG "ModemState = %d", p->ModemState); + printk(KERN_DEBUG "GuardInterval = %d", p->GuardInterval); + printk(KERN_DEBUG "SystemType = %d", p->SystemType); + printk(KERN_DEBUG "PartialReception = %d", p->PartialReception); + printk(KERN_DEBUG "NumOfLayers = %d", p->NumOfLayers); + printk(KERN_DEBUG "SmsToHostTxErrors = %d", p->SmsToHostTxErrors); + + for (i = 0; i < 3; i++) { + printk(KERN_DEBUG "%d: CodeRate = %d", i, p->LayerInfo[i].CodeRate); + printk(KERN_DEBUG "%d: Constellation = %d", i, p->LayerInfo[i].Constellation); + printk(KERN_DEBUG "%d: BER = %d", i, p->LayerInfo[i].BER); + printk(KERN_DEBUG "%d: BERErrorCount = %d", i, p->LayerInfo[i].BERErrorCount); + printk(KERN_DEBUG "%d: BERBitCount = %d", i, p->LayerInfo[i].BERBitCount); + printk(KERN_DEBUG "%d: PreBER = %d", i, p->LayerInfo[i].PreBER); + printk(KERN_DEBUG "%d: TS_PER = %d", i, p->LayerInfo[i].TS_PER); + printk(KERN_DEBUG "%d: ErrorTSPackets = %d", i, p->LayerInfo[i].ErrorTSPackets); + printk(KERN_DEBUG "%d: TotalTSPackets = %d", i, p->LayerInfo[i].TotalTSPackets); + printk(KERN_DEBUG "%d: TILdepthI = %d", i, p->LayerInfo[i].TILdepthI); + printk(KERN_DEBUG "%d: NumberOfSegments = %d", i, p->LayerInfo[i].NumberOfSegments); + printk(KERN_DEBUG "%d: TMCCErrors = %d", i, p->LayerInfo[i].TMCCErrors); + } + } + + pReceptionData->IsDemodLocked = p->IsDemodLocked; + + pReceptionData->SNR = p->SNR; + pReceptionData->InBandPwr = p->InBandPwr; + + pReceptionData->ErrorTSPackets = 0; + pReceptionData->BER = 0; + pReceptionData->BERErrorCount = 0; + for (i = 0; i < 3; i++) { + pReceptionData->BER += p->LayerInfo[i].BER; + pReceptionData->BERErrorCount += p->LayerInfo[i].BERErrorCount; + pReceptionData->ErrorTSPackets += p->LayerInfo[i].ErrorTSPackets; + } +} + +static int smsdvb_onresponse(void *context, struct smscore_buffer_t *cb) +{ + struct smsdvb_client_t *client = (struct smsdvb_client_t *) context; + struct SmsMsgHdr_ST *phdr = (struct SmsMsgHdr_ST *) (((u8 *) cb->p) + + cb->offset); + u32 *pMsgData = (u32 *) phdr + 1; + /*u32 MsgDataLen = phdr->msgLength - sizeof(struct SmsMsgHdr_ST);*/ + bool is_status_update = false; + + smsendian_handle_rx_message((struct SmsMsgData_ST *) phdr); + + switch (phdr->msgType) { + case MSG_SMS_DVBT_BDA_DATA: + dvb_dmx_swfilter(&client->demux, (u8 *)(phdr + 1), + cb->size - sizeof(struct SmsMsgHdr_ST)); + break; + + case MSG_SMS_RF_TUNE_RES: + case MSG_SMS_ISDBT_TUNE_RES: + complete(&client->tune_done); + break; + + case MSG_SMS_SIGNAL_DETECTED_IND: + sms_info("MSG_SMS_SIGNAL_DETECTED_IND"); + client->sms_stat_dvb.TransmissionData.IsDemodLocked = true; + is_status_update = true; + break; + + case MSG_SMS_NO_SIGNAL_IND: + sms_info("MSG_SMS_NO_SIGNAL_IND"); + client->sms_stat_dvb.TransmissionData.IsDemodLocked = false; + is_status_update = true; + break; + + case MSG_SMS_TRANSMISSION_IND: { + sms_info("MSG_SMS_TRANSMISSION_IND"); + + pMsgData++; + memcpy(&client->sms_stat_dvb.TransmissionData, pMsgData, + sizeof(struct TRANSMISSION_STATISTICS_S)); + + /* Mo need to correct guard interval + * (as opposed to old statistics message). + */ + CORRECT_STAT_BANDWIDTH(client->sms_stat_dvb.TransmissionData); + CORRECT_STAT_TRANSMISSON_MODE( + client->sms_stat_dvb.TransmissionData); + is_status_update = true; + break; + } + case MSG_SMS_HO_PER_SLICES_IND: { + struct RECEPTION_STATISTICS_S *pReceptionData = + &client->sms_stat_dvb.ReceptionData; + struct SRVM_SIGNAL_STATUS_S SignalStatusData; + + /*sms_info("MSG_SMS_HO_PER_SLICES_IND");*/ + pMsgData++; + SignalStatusData.result = pMsgData[0]; + SignalStatusData.snr = pMsgData[1]; + SignalStatusData.inBandPower = (s32) pMsgData[2]; + SignalStatusData.tsPackets = pMsgData[3]; + SignalStatusData.etsPackets = pMsgData[4]; + SignalStatusData.constellation = pMsgData[5]; + SignalStatusData.hpCode = pMsgData[6]; + SignalStatusData.tpsSrvIndLP = pMsgData[7] & 0x03; + SignalStatusData.tpsSrvIndHP = pMsgData[8] & 0x03; + SignalStatusData.cellId = pMsgData[9] & 0xFFFF; + SignalStatusData.reason = pMsgData[10]; + SignalStatusData.requestId = pMsgData[11]; + pReceptionData->IsRfLocked = pMsgData[16]; + pReceptionData->IsDemodLocked = pMsgData[17]; + pReceptionData->ModemState = pMsgData[12]; + pReceptionData->SNR = pMsgData[1]; + pReceptionData->BER = pMsgData[13]; + pReceptionData->RSSI = pMsgData[14]; + CORRECT_STAT_RSSI(client->sms_stat_dvb.ReceptionData); + + pReceptionData->InBandPwr = (s32) pMsgData[2]; + pReceptionData->CarrierOffset = (s32) pMsgData[15]; + pReceptionData->TotalTSPackets = pMsgData[3]; + pReceptionData->ErrorTSPackets = pMsgData[4]; + + /* TS PER */ + if ((SignalStatusData.tsPackets + SignalStatusData.etsPackets) + > 0) { + pReceptionData->TS_PER = (SignalStatusData.etsPackets + * 100) / (SignalStatusData.tsPackets + + SignalStatusData.etsPackets); + } else { + pReceptionData->TS_PER = 0; + } + + pReceptionData->BERBitCount = pMsgData[18]; + pReceptionData->BERErrorCount = pMsgData[19]; + + pReceptionData->MRC_SNR = pMsgData[20]; + pReceptionData->MRC_InBandPwr = pMsgData[21]; + pReceptionData->MRC_RSSI = pMsgData[22]; + + is_status_update = true; + break; + } + case MSG_SMS_GET_STATISTICS_RES: { + union { + struct SMSHOSTLIB_STATISTICS_ISDBT_ST isdbt; + struct SmsMsgStatisticsInfo_ST dvb; + } *p = (void *) (phdr + 1); + struct RECEPTION_STATISTICS_S *pReceptionData = + &client->sms_stat_dvb.ReceptionData; + + sms_info("MSG_SMS_GET_STATISTICS_RES"); + + is_status_update = true; + + switch (smscore_get_device_mode(client->coredev)) { + case DEVICE_MODE_ISDBT: + case DEVICE_MODE_ISDBT_BDA: + smsdvb_update_isdbt_stats(pReceptionData, &p->isdbt); + break; + default: + smsdvb_update_dvb_stats(pReceptionData, &p->dvb.Stat); + } + if (!pReceptionData->IsDemodLocked) { + pReceptionData->SNR = 0; + pReceptionData->BER = 0; + pReceptionData->BERErrorCount = 0; + pReceptionData->InBandPwr = 0; + pReceptionData->ErrorTSPackets = 0; + } + + complete(&client->tune_done); + break; + } + default: + sms_info("Unhandled message %d", phdr->msgType); + + } + smscore_putbuffer(client->coredev, cb); + + if (is_status_update) { + if (client->sms_stat_dvb.ReceptionData.IsDemodLocked) { + client->fe_status = FE_HAS_SIGNAL | FE_HAS_CARRIER + | FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + sms_board_dvb3_event(client, DVB3_EVENT_FE_LOCK); + if (client->sms_stat_dvb.ReceptionData.ErrorTSPackets + == 0) + sms_board_dvb3_event(client, DVB3_EVENT_UNC_OK); + else + sms_board_dvb3_event(client, + DVB3_EVENT_UNC_ERR); + + } else { + if (client->sms_stat_dvb.ReceptionData.IsRfLocked) + client->fe_status = FE_HAS_SIGNAL | FE_HAS_CARRIER; + else + client->fe_status = 0; + sms_board_dvb3_event(client, DVB3_EVENT_FE_UNLOCK); + } + } + + return 0; +} + +static void smsdvb_unregister_client(struct smsdvb_client_t *client) +{ + /* must be called under clientslock */ + + list_del(&client->entry); + + smscore_unregister_client(client->smsclient); + dvb_unregister_frontend(&client->frontend); + dvb_dmxdev_release(&client->dmxdev); + dvb_dmx_release(&client->demux); + dvb_unregister_adapter(&client->adapter); + kfree(client); +} + +static void smsdvb_onremove(void *context) +{ + kmutex_lock(&g_smsdvb_clientslock); + + smsdvb_unregister_client((struct smsdvb_client_t *) context); + + kmutex_unlock(&g_smsdvb_clientslock); +} + +static int smsdvb_start_feed(struct dvb_demux_feed *feed) +{ + struct smsdvb_client_t *client = + container_of(feed->demux, struct smsdvb_client_t, demux); + struct SmsMsgData_ST PidMsg; + + sms_debug("add pid %d(%x)", + feed->pid, feed->pid); + + PidMsg.xMsgHeader.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; + PidMsg.xMsgHeader.msgDstId = HIF_TASK; + PidMsg.xMsgHeader.msgFlags = 0; + PidMsg.xMsgHeader.msgType = MSG_SMS_ADD_PID_FILTER_REQ; + PidMsg.xMsgHeader.msgLength = sizeof(PidMsg); + PidMsg.msgData[0] = feed->pid; + + smsendian_handle_tx_message((struct SmsMsgHdr_ST *)&PidMsg); + return smsclient_sendrequest(client->smsclient, + &PidMsg, sizeof(PidMsg)); +} + +static int smsdvb_stop_feed(struct dvb_demux_feed *feed) +{ + struct smsdvb_client_t *client = + container_of(feed->demux, struct smsdvb_client_t, demux); + struct SmsMsgData_ST PidMsg; + + sms_debug("remove pid %d(%x)", + feed->pid, feed->pid); + + PidMsg.xMsgHeader.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; + PidMsg.xMsgHeader.msgDstId = HIF_TASK; + PidMsg.xMsgHeader.msgFlags = 0; + PidMsg.xMsgHeader.msgType = MSG_SMS_REMOVE_PID_FILTER_REQ; + PidMsg.xMsgHeader.msgLength = sizeof(PidMsg); + PidMsg.msgData[0] = feed->pid; + + smsendian_handle_tx_message((struct SmsMsgHdr_ST *)&PidMsg); + return smsclient_sendrequest(client->smsclient, + &PidMsg, sizeof(PidMsg)); +} + +static int smsdvb_sendrequest_and_wait(struct smsdvb_client_t *client, + void *buffer, size_t size, + struct completion *completion) +{ + int rc; + + smsendian_handle_tx_message((struct SmsMsgHdr_ST *)buffer); + rc = smsclient_sendrequest(client->smsclient, buffer, size); + if (rc < 0) + return rc; + + return wait_for_completion_timeout(completion, + msecs_to_jiffies(2000)) ? + 0 : -ETIME; +} + +static int smsdvb_send_statistics_request(struct smsdvb_client_t *client) +{ + int rc; + struct SmsMsgHdr_ST Msg = { MSG_SMS_GET_STATISTICS_REQ, + DVBT_BDA_CONTROL_MSG_ID, + HIF_TASK, + sizeof(struct SmsMsgHdr_ST), 0 }; + + rc = smsdvb_sendrequest_and_wait(client, &Msg, sizeof(Msg), + &client->tune_done); + + return rc; +} + +static inline int led_feedback(struct smsdvb_client_t *client) +{ + if (client->fe_status & FE_HAS_LOCK) + return sms_board_led_feedback(client->coredev, + (client->sms_stat_dvb.ReceptionData.BER + == 0) ? SMS_LED_HI : SMS_LED_LO); + else + return sms_board_led_feedback(client->coredev, SMS_LED_OFF); +} + +static int smsdvb_read_status(struct dvb_frontend *fe, fe_status_t *stat) +{ + int rc; + struct smsdvb_client_t *client; + client = container_of(fe, struct smsdvb_client_t, frontend); + + rc = smsdvb_send_statistics_request(client); + + *stat = client->fe_status; + + led_feedback(client); + + return rc; +} + +static int smsdvb_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + int rc; + struct smsdvb_client_t *client; + client = container_of(fe, struct smsdvb_client_t, frontend); + + rc = smsdvb_send_statistics_request(client); + + *ber = client->sms_stat_dvb.ReceptionData.BER; + + led_feedback(client); + + return rc; +} + +static int smsdvb_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + int rc; + + struct smsdvb_client_t *client; + client = container_of(fe, struct smsdvb_client_t, frontend); + + rc = smsdvb_send_statistics_request(client); + + if (client->sms_stat_dvb.ReceptionData.InBandPwr < -95) + *strength = 0; + else if (client->sms_stat_dvb.ReceptionData.InBandPwr > -29) + *strength = 100; + else + *strength = + (client->sms_stat_dvb.ReceptionData.InBandPwr + + 95) * 3 / 2; + + led_feedback(client); + + return rc; +} + +static int smsdvb_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + int rc; + struct smsdvb_client_t *client; + client = container_of(fe, struct smsdvb_client_t, frontend); + + rc = smsdvb_send_statistics_request(client); + + *snr = client->sms_stat_dvb.ReceptionData.SNR; + + led_feedback(client); + + return rc; +} + +static int smsdvb_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + int rc; + struct smsdvb_client_t *client; + client = container_of(fe, struct smsdvb_client_t, frontend); + + rc = smsdvb_send_statistics_request(client); + + *ucblocks = client->sms_stat_dvb.ReceptionData.ErrorTSPackets; + + led_feedback(client); + + return rc; +} + +static int smsdvb_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *tune) +{ + sms_debug(""); + + tune->min_delay_ms = 400; + tune->step_size = 250000; + tune->max_drift = 0; + return 0; +} + +static int smsdvb_dvbt_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct smsdvb_client_t *client = + container_of(fe, struct smsdvb_client_t, frontend); + + struct { + struct SmsMsgHdr_ST Msg; + u32 Data[3]; + } Msg; + + int ret; + + client->fe_status = FE_HAS_SIGNAL; + client->event_fe_state = -1; + client->event_unc_state = -1; + fe->dtv_property_cache.delivery_system = SYS_DVBT; + + Msg.Msg.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; + Msg.Msg.msgDstId = HIF_TASK; + Msg.Msg.msgFlags = 0; + Msg.Msg.msgType = MSG_SMS_RF_TUNE_REQ; + Msg.Msg.msgLength = sizeof(Msg); + Msg.Data[0] = c->frequency; + Msg.Data[2] = 12000000; + + sms_info("%s: freq %d band %d", __func__, c->frequency, + c->bandwidth_hz); + + switch (c->bandwidth_hz / 1000000) { + case 8: + Msg.Data[1] = BW_8_MHZ; + break; + case 7: + Msg.Data[1] = BW_7_MHZ; + break; + case 6: + Msg.Data[1] = BW_6_MHZ; + break; + case 0: + return -EOPNOTSUPP; + default: + return -EINVAL; + } + /* Disable LNA, if any. An error is returned if no LNA is present */ + ret = sms_board_lna_control(client->coredev, 0); + if (ret == 0) { + fe_status_t status; + + /* tune with LNA off at first */ + ret = smsdvb_sendrequest_and_wait(client, &Msg, sizeof(Msg), + &client->tune_done); + + smsdvb_read_status(fe, &status); + + if (status & FE_HAS_LOCK) + return ret; + + /* previous tune didn't lock - enable LNA and tune again */ + sms_board_lna_control(client->coredev, 1); + } + + return smsdvb_sendrequest_and_wait(client, &Msg, sizeof(Msg), + &client->tune_done); +} + +static int smsdvb_isdbt_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct smsdvb_client_t *client = + container_of(fe, struct smsdvb_client_t, frontend); + + struct { + struct SmsMsgHdr_ST Msg; + u32 Data[4]; + } Msg; + + fe->dtv_property_cache.delivery_system = SYS_ISDBT; + + Msg.Msg.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; + Msg.Msg.msgDstId = HIF_TASK; + Msg.Msg.msgFlags = 0; + Msg.Msg.msgType = MSG_SMS_ISDBT_TUNE_REQ; + Msg.Msg.msgLength = sizeof(Msg); + + if (c->isdbt_sb_segment_idx == -1) + c->isdbt_sb_segment_idx = 0; + + switch (c->isdbt_sb_segment_count) { + case 3: + Msg.Data[1] = BW_ISDBT_3SEG; + break; + case 1: + Msg.Data[1] = BW_ISDBT_1SEG; + break; + case 0: /* AUTO */ + switch (c->bandwidth_hz / 1000000) { + case 8: + case 7: + c->isdbt_sb_segment_count = 3; + Msg.Data[1] = BW_ISDBT_3SEG; + break; + case 6: + c->isdbt_sb_segment_count = 1; + Msg.Data[1] = BW_ISDBT_1SEG; + break; + default: /* Assumes 6 MHZ bw */ + c->isdbt_sb_segment_count = 1; + c->bandwidth_hz = 6000; + Msg.Data[1] = BW_ISDBT_1SEG; + break; + } + break; + default: + sms_info("Segment count %d not supported", c->isdbt_sb_segment_count); + return -EINVAL; + } + + Msg.Data[0] = c->frequency; + Msg.Data[2] = 12000000; + Msg.Data[3] = c->isdbt_sb_segment_idx; + + sms_info("%s: freq %d segwidth %d segindex %d\n", __func__, + c->frequency, c->isdbt_sb_segment_count, + c->isdbt_sb_segment_idx); + + return smsdvb_sendrequest_and_wait(client, &Msg, sizeof(Msg), + &client->tune_done); +} + +static int smsdvb_set_frontend(struct dvb_frontend *fe) +{ + struct smsdvb_client_t *client = + container_of(fe, struct smsdvb_client_t, frontend); + struct smscore_device_t *coredev = client->coredev; + + switch (smscore_get_device_mode(coredev)) { + case DEVICE_MODE_DVBT: + case DEVICE_MODE_DVBT_BDA: + return smsdvb_dvbt_set_frontend(fe); + case DEVICE_MODE_ISDBT: + case DEVICE_MODE_ISDBT_BDA: + return smsdvb_isdbt_set_frontend(fe); + default: + return -EINVAL; + } +} + +static int smsdvb_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *fep = &fe->dtv_property_cache; + struct smsdvb_client_t *client = + container_of(fe, struct smsdvb_client_t, frontend); + struct smscore_device_t *coredev = client->coredev; + struct TRANSMISSION_STATISTICS_S *td = + &client->sms_stat_dvb.TransmissionData; + + switch (smscore_get_device_mode(coredev)) { + case DEVICE_MODE_DVBT: + case DEVICE_MODE_DVBT_BDA: + fep->frequency = td->Frequency; + + switch (td->Bandwidth) { + case 6: + fep->bandwidth_hz = 6000000; + break; + case 7: + fep->bandwidth_hz = 7000000; + break; + case 8: + fep->bandwidth_hz = 8000000; + break; + } + + switch (td->TransmissionMode) { + case 2: + fep->transmission_mode = TRANSMISSION_MODE_2K; + break; + case 8: + fep->transmission_mode = TRANSMISSION_MODE_8K; + } + + switch (td->GuardInterval) { + case 0: + fep->guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + fep->guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + fep->guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + fep->guard_interval = GUARD_INTERVAL_1_4; + break; + } + + switch (td->CodeRate) { + case 0: + fep->code_rate_HP = FEC_1_2; + break; + case 1: + fep->code_rate_HP = FEC_2_3; + break; + case 2: + fep->code_rate_HP = FEC_3_4; + break; + case 3: + fep->code_rate_HP = FEC_5_6; + break; + case 4: + fep->code_rate_HP = FEC_7_8; + break; + } + + switch (td->LPCodeRate) { + case 0: + fep->code_rate_LP = FEC_1_2; + break; + case 1: + fep->code_rate_LP = FEC_2_3; + break; + case 2: + fep->code_rate_LP = FEC_3_4; + break; + case 3: + fep->code_rate_LP = FEC_5_6; + break; + case 4: + fep->code_rate_LP = FEC_7_8; + break; + } + + switch (td->Constellation) { + case 0: + fep->modulation = QPSK; + break; + case 1: + fep->modulation = QAM_16; + break; + case 2: + fep->modulation = QAM_64; + break; + } + + switch (td->Hierarchy) { + case 0: + fep->hierarchy = HIERARCHY_NONE; + break; + case 1: + fep->hierarchy = HIERARCHY_1; + break; + case 2: + fep->hierarchy = HIERARCHY_2; + break; + case 3: + fep->hierarchy = HIERARCHY_4; + break; + } + + fep->inversion = INVERSION_AUTO; + break; + case DEVICE_MODE_ISDBT: + case DEVICE_MODE_ISDBT_BDA: + fep->frequency = td->Frequency; + fep->bandwidth_hz = 6000000; + /* todo: retrive the other parameters */ + break; + default: + return -EINVAL; + } + + return 0; +} + +static int smsdvb_init(struct dvb_frontend *fe) +{ + struct smsdvb_client_t *client = + container_of(fe, struct smsdvb_client_t, frontend); + + sms_board_power(client->coredev, 1); + + sms_board_dvb3_event(client, DVB3_EVENT_INIT); + return 0; +} + +static int smsdvb_sleep(struct dvb_frontend *fe) +{ + struct smsdvb_client_t *client = + container_of(fe, struct smsdvb_client_t, frontend); + + sms_board_led_feedback(client->coredev, SMS_LED_OFF); + sms_board_power(client->coredev, 0); + + sms_board_dvb3_event(client, DVB3_EVENT_SLEEP); + + return 0; +} + +static void smsdvb_release(struct dvb_frontend *fe) +{ + /* do nothing */ +} + +static struct dvb_frontend_ops smsdvb_fe_ops = { + .info = { + .name = "Siano Mobile Digital MDTV Receiver", + .frequency_min = 44250000, + .frequency_max = 867250000, + .frequency_stepsize = 250000, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | + FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_RECOVER | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = smsdvb_release, + + .set_frontend = smsdvb_set_frontend, + .get_frontend = smsdvb_get_frontend, + .get_tune_settings = smsdvb_get_tune_settings, + + .read_status = smsdvb_read_status, + .read_ber = smsdvb_read_ber, + .read_signal_strength = smsdvb_read_signal_strength, + .read_snr = smsdvb_read_snr, + .read_ucblocks = smsdvb_read_ucblocks, + + .init = smsdvb_init, + .sleep = smsdvb_sleep, +}; + +static int smsdvb_hotplug(struct smscore_device_t *coredev, + struct device *device, int arrival) +{ + struct smsclient_params_t params; + struct smsdvb_client_t *client; + int rc; + + /* device removal handled by onremove callback */ + if (!arrival) + return 0; + client = kzalloc(sizeof(struct smsdvb_client_t), GFP_KERNEL); + if (!client) { + sms_err("kmalloc() failed"); + return -ENOMEM; + } + + /* register dvb adapter */ + rc = dvb_register_adapter(&client->adapter, + sms_get_board( + smscore_get_board_id(coredev))->name, + THIS_MODULE, device, adapter_nr); + if (rc < 0) { + sms_err("dvb_register_adapter() failed %d", rc); + goto adapter_error; + } + + /* init dvb demux */ + client->demux.dmx.capabilities = DMX_TS_FILTERING; + client->demux.filternum = 32; /* todo: nova ??? */ + client->demux.feednum = 32; + client->demux.start_feed = smsdvb_start_feed; + client->demux.stop_feed = smsdvb_stop_feed; + + rc = dvb_dmx_init(&client->demux); + if (rc < 0) { + sms_err("dvb_dmx_init failed %d", rc); + goto dvbdmx_error; + } + + /* init dmxdev */ + client->dmxdev.filternum = 32; + client->dmxdev.demux = &client->demux.dmx; + client->dmxdev.capabilities = 0; + + rc = dvb_dmxdev_init(&client->dmxdev, &client->adapter); + if (rc < 0) { + sms_err("dvb_dmxdev_init failed %d", rc); + goto dmxdev_error; + } + + /* init and register frontend */ + memcpy(&client->frontend.ops, &smsdvb_fe_ops, + sizeof(struct dvb_frontend_ops)); + + switch (smscore_get_device_mode(coredev)) { + case DEVICE_MODE_DVBT: + case DEVICE_MODE_DVBT_BDA: + client->frontend.ops.delsys[0] = SYS_DVBT; + break; + case DEVICE_MODE_ISDBT: + case DEVICE_MODE_ISDBT_BDA: + client->frontend.ops.delsys[0] = SYS_ISDBT; + break; + } + + rc = dvb_register_frontend(&client->adapter, &client->frontend); + if (rc < 0) { + sms_err("frontend registration failed %d", rc); + goto frontend_error; + } + + params.initial_id = 1; + params.data_type = MSG_SMS_DVBT_BDA_DATA; + params.onresponse_handler = smsdvb_onresponse; + params.onremove_handler = smsdvb_onremove; + params.context = client; + + rc = smscore_register_client(coredev, ¶ms, &client->smsclient); + if (rc < 0) { + sms_err("smscore_register_client() failed %d", rc); + goto client_error; + } + + client->coredev = coredev; + + init_completion(&client->tune_done); + + kmutex_lock(&g_smsdvb_clientslock); + + list_add(&client->entry, &g_smsdvb_clients); + + kmutex_unlock(&g_smsdvb_clientslock); + + client->event_fe_state = -1; + client->event_unc_state = -1; + sms_board_dvb3_event(client, DVB3_EVENT_HOTPLUG); + + sms_info("success"); + sms_board_setup(coredev); + + return 0; + +client_error: + dvb_unregister_frontend(&client->frontend); + +frontend_error: + dvb_dmxdev_release(&client->dmxdev); + +dmxdev_error: + dvb_dmx_release(&client->demux); + +dvbdmx_error: + dvb_unregister_adapter(&client->adapter); + +adapter_error: + kfree(client); + return rc; +} + +static int __init smsdvb_module_init(void) +{ + int rc; + + INIT_LIST_HEAD(&g_smsdvb_clients); + kmutex_init(&g_smsdvb_clientslock); + + rc = smscore_register_hotplug(smsdvb_hotplug); + + sms_debug(""); + + return rc; +} + +static void __exit smsdvb_module_exit(void) +{ + smscore_unregister_hotplug(smsdvb_hotplug); + + kmutex_lock(&g_smsdvb_clientslock); + + while (!list_empty(&g_smsdvb_clients)) + smsdvb_unregister_client( + (struct smsdvb_client_t *) g_smsdvb_clients.next); + + kmutex_unlock(&g_smsdvb_clientslock); +} + +module_init(smsdvb_module_init); +module_exit(smsdvb_module_exit); + +MODULE_DESCRIPTION("SMS DVB subsystem adaptation module"); +MODULE_AUTHOR("Siano Mobile Silicon, Inc. (uris@siano-ms.com)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/siano/smsendian.c b/drivers/media/usb/siano/smsendian.c new file mode 100644 index 000000000000..e2657c2f0109 --- /dev/null +++ b/drivers/media/usb/siano/smsendian.c @@ -0,0 +1,103 @@ +/**************************************************************** + + Siano Mobile Silicon, Inc. + MDTV receiver kernel modules. + Copyright (C) 2006-2009, 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 <linux/export.h> +#include <asm/byteorder.h> + +#include "smsendian.h" +#include "smscoreapi.h" + +void smsendian_handle_tx_message(void *buffer) +{ +#ifdef __BIG_ENDIAN + struct SmsMsgData_ST *msg = (struct SmsMsgData_ST *)buffer; + int i; + int msgWords; + + switch (msg->xMsgHeader.msgType) { + case MSG_SMS_DATA_DOWNLOAD_REQ: + { + msg->msgData[0] = le32_to_cpu(msg->msgData[0]); + break; + } + + default: + msgWords = (msg->xMsgHeader.msgLength - + sizeof(struct SmsMsgHdr_ST))/4; + + for (i = 0; i < msgWords; i++) + msg->msgData[i] = le32_to_cpu(msg->msgData[i]); + + break; + } +#endif /* __BIG_ENDIAN */ +} +EXPORT_SYMBOL_GPL(smsendian_handle_tx_message); + +void smsendian_handle_rx_message(void *buffer) +{ +#ifdef __BIG_ENDIAN + struct SmsMsgData_ST *msg = (struct SmsMsgData_ST *)buffer; + int i; + int msgWords; + + switch (msg->xMsgHeader.msgType) { + case MSG_SMS_GET_VERSION_EX_RES: + { + struct SmsVersionRes_ST *ver = + (struct SmsVersionRes_ST *) msg; + ver->ChipModel = le16_to_cpu(ver->ChipModel); + break; + } + + case MSG_SMS_DVBT_BDA_DATA: + case MSG_SMS_DAB_CHANNEL: + case MSG_SMS_DATA_MSG: + { + break; + } + + default: + { + msgWords = (msg->xMsgHeader.msgLength - + sizeof(struct SmsMsgHdr_ST))/4; + + for (i = 0; i < msgWords; i++) + msg->msgData[i] = le32_to_cpu(msg->msgData[i]); + + break; + } + } +#endif /* __BIG_ENDIAN */ +} +EXPORT_SYMBOL_GPL(smsendian_handle_rx_message); + +void smsendian_handle_message_header(void *msg) +{ +#ifdef __BIG_ENDIAN + struct SmsMsgHdr_ST *phdr = (struct SmsMsgHdr_ST *)msg; + + phdr->msgType = le16_to_cpu(phdr->msgType); + phdr->msgLength = le16_to_cpu(phdr->msgLength); + phdr->msgFlags = le16_to_cpu(phdr->msgFlags); +#endif /* __BIG_ENDIAN */ +} +EXPORT_SYMBOL_GPL(smsendian_handle_message_header); diff --git a/drivers/media/usb/siano/smsendian.h b/drivers/media/usb/siano/smsendian.h new file mode 100644 index 000000000000..1624d6fd367b --- /dev/null +++ b/drivers/media/usb/siano/smsendian.h @@ -0,0 +1,32 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. +Copyright (C) 2006-2009, 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_ENDIAN_H__ +#define __SMS_ENDIAN_H__ + +#include <asm/byteorder.h> + +extern void smsendian_handle_tx_message(void *buffer); +extern void smsendian_handle_rx_message(void *buffer); +extern void smsendian_handle_message_header(void *msg); + +#endif /* __SMS_ENDIAN_H__ */ + diff --git a/drivers/media/usb/siano/smsir.c b/drivers/media/usb/siano/smsir.c new file mode 100644 index 000000000000..37bc5c4b8ad8 --- /dev/null +++ b/drivers/media/usb/siano/smsir.c @@ -0,0 +1,114 @@ +/**************************************************************** + + Siano Mobile Silicon, Inc. + MDTV receiver kernel modules. + Copyright (C) 2006-2009, Uri Shkolnik + + Copyright (c) 2010 - Mauro Carvalho Chehab + - Ported the driver to use rc-core + - IR raw event decoding is now done at rc-core + - Code almost re-written + + 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 <linux/types.h> +#include <linux/input.h> + +#include "smscoreapi.h" +#include "smsir.h" +#include "sms-cards.h" + +#define MODULE_NAME "smsmdtv" + +void sms_ir_event(struct smscore_device_t *coredev, const char *buf, int len) +{ + int i; + const s32 *samples = (const void *)buf; + + for (i = 0; i < len >> 2; i++) { + DEFINE_IR_RAW_EVENT(ev); + + ev.duration = abs(samples[i]) * 1000; /* Convert to ns */ + ev.pulse = (samples[i] > 0) ? false : true; + + ir_raw_event_store(coredev->ir.dev, &ev); + } + ir_raw_event_handle(coredev->ir.dev); +} + +int sms_ir_init(struct smscore_device_t *coredev) +{ + int err; + int board_id = smscore_get_board_id(coredev); + struct rc_dev *dev; + + sms_log("Allocating rc device"); + dev = rc_allocate_device(); + if (!dev) { + sms_err("Not enough memory"); + return -ENOMEM; + } + + coredev->ir.controller = 0; /* Todo: vega/nova SPI number */ + coredev->ir.timeout = IR_DEFAULT_TIMEOUT; + sms_log("IR port %d, timeout %d ms", + coredev->ir.controller, coredev->ir.timeout); + + snprintf(coredev->ir.name, sizeof(coredev->ir.name), + "SMS IR (%s)", sms_get_board(board_id)->name); + + strlcpy(coredev->ir.phys, coredev->devpath, sizeof(coredev->ir.phys)); + strlcat(coredev->ir.phys, "/ir0", sizeof(coredev->ir.phys)); + + dev->input_name = coredev->ir.name; + dev->input_phys = coredev->ir.phys; + dev->dev.parent = coredev->device; + +#if 0 + /* TODO: properly initialize the parameters bellow */ + dev->input_id.bustype = BUS_USB; + dev->input_id.version = 1; + dev->input_id.vendor = le16_to_cpu(dev->udev->descriptor.idVendor); + dev->input_id.product = le16_to_cpu(dev->udev->descriptor.idProduct); +#endif + + dev->priv = coredev; + dev->driver_type = RC_DRIVER_IR_RAW; + dev->allowed_protos = RC_TYPE_ALL; + dev->map_name = sms_get_board(board_id)->rc_codes; + dev->driver_name = MODULE_NAME; + + sms_log("Input device (IR) %s is set for key events", dev->input_name); + + err = rc_register_device(dev); + if (err < 0) { + sms_err("Failed to register device"); + rc_free_device(dev); + return err; + } + + coredev->ir.dev = dev; + return 0; +} + +void sms_ir_exit(struct smscore_device_t *coredev) +{ + if (coredev->ir.dev) + rc_unregister_device(coredev->ir.dev); + + sms_log(""); +} diff --git a/drivers/media/usb/siano/smsir.h b/drivers/media/usb/siano/smsir.h new file mode 100644 index 000000000000..ae92b3a8587e --- /dev/null +++ b/drivers/media/usb/siano/smsir.h @@ -0,0 +1,55 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. +Copyright (C) 2006-2009, Uri Shkolnik + + Copyright (c) 2010 - Mauro Carvalho Chehab + - Ported the driver to use rc-core + - IR raw event decoding is now done at rc-core + - Code almost re-written + +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_IR_H__ +#define __SMS_IR_H__ + +#include <linux/input.h> +#include <media/rc-core.h> + +#define IR_DEFAULT_TIMEOUT 100 + +struct smscore_device_t; + +struct ir_t { + struct rc_dev *dev; + char name[40]; + char phys[32]; + + char *rc_codes; + u64 protocol; + + u32 timeout; + u32 controller; +}; + +int sms_ir_init(struct smscore_device_t *coredev); +void sms_ir_exit(struct smscore_device_t *coredev); +void sms_ir_event(struct smscore_device_t *coredev, + const char *buf, int len); + +#endif /* __SMS_IR_H__ */ + diff --git a/drivers/media/usb/siano/smssdio.c b/drivers/media/usb/siano/smssdio.c new file mode 100644 index 000000000000..d6f3f100699a --- /dev/null +++ b/drivers/media/usb/siano/smssdio.c @@ -0,0 +1,365 @@ +/* + * smssdio.c - Siano 1xxx SDIO interface driver + * + * Copyright 2008 Pierre Ossman + * + * Based on code by Siano Mobile Silicon, Inc., + * 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 hardware is a bit odd in that all transfers should be done + * to/from the SMSSDIO_DATA register, yet the "increase address" bit + * always needs to be set. + * + * Also, buffers from the card are always aligned to 128 byte + * boundaries. + */ + +/* + * General cleanup notes: + * + * - only typedefs should be name *_t + * + * - use ERR_PTR and friends for smscore_register_device() + * + * - smscore_getbuffer should zero fields + * + * Fix stop command + */ + +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <linux/delay.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> +#include <linux/module.h> + +#include "smscoreapi.h" +#include "sms-cards.h" + +/* Registers */ + +#define SMSSDIO_DATA 0x00 +#define SMSSDIO_INT 0x04 +#define SMSSDIO_BLOCK_SIZE 128 + +static const struct sdio_device_id smssdio_ids[] __devinitconst = { + {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_STELLAR), + .driver_data = SMS1XXX_BOARD_SIANO_STELLAR}, + {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_NOVA_A0), + .driver_data = SMS1XXX_BOARD_SIANO_NOVA_A}, + {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_NOVA_B0), + .driver_data = SMS1XXX_BOARD_SIANO_NOVA_B}, + {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_VEGA_A0), + .driver_data = SMS1XXX_BOARD_SIANO_VEGA}, + {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_VENICE), + .driver_data = SMS1XXX_BOARD_SIANO_VEGA}, + { /* end: all zeroes */ }, +}; + +MODULE_DEVICE_TABLE(sdio, smssdio_ids); + +struct smssdio_device { + struct sdio_func *func; + + struct smscore_device_t *coredev; + + struct smscore_buffer_t *split_cb; +}; + +/*******************************************************************/ +/* Siano core callbacks */ +/*******************************************************************/ + +static int smssdio_sendrequest(void *context, void *buffer, size_t size) +{ + int ret = 0; + struct smssdio_device *smsdev; + + smsdev = context; + + sdio_claim_host(smsdev->func); + + while (size >= smsdev->func->cur_blksize) { + ret = sdio_memcpy_toio(smsdev->func, SMSSDIO_DATA, + buffer, smsdev->func->cur_blksize); + if (ret) + goto out; + + buffer += smsdev->func->cur_blksize; + size -= smsdev->func->cur_blksize; + } + + if (size) { + ret = sdio_memcpy_toio(smsdev->func, SMSSDIO_DATA, + buffer, size); + } + +out: + sdio_release_host(smsdev->func); + + return ret; +} + +/*******************************************************************/ +/* SDIO callbacks */ +/*******************************************************************/ + +static void smssdio_interrupt(struct sdio_func *func) +{ + int ret; + + struct smssdio_device *smsdev; + struct smscore_buffer_t *cb; + struct SmsMsgHdr_ST *hdr; + size_t size; + + smsdev = sdio_get_drvdata(func); + + /* + * The interrupt register has no defined meaning. It is just + * a way of turning of the level triggered interrupt. + */ + (void)sdio_readb(func, SMSSDIO_INT, &ret); + if (ret) { + sms_err("Unable to read interrupt register!\n"); + return; + } + + if (smsdev->split_cb == NULL) { + cb = smscore_getbuffer(smsdev->coredev); + if (!cb) { + sms_err("Unable to allocate data buffer!\n"); + return; + } + + ret = sdio_memcpy_fromio(smsdev->func, + cb->p, + SMSSDIO_DATA, + SMSSDIO_BLOCK_SIZE); + if (ret) { + sms_err("Error %d reading initial block!\n", ret); + return; + } + + hdr = cb->p; + + if (hdr->msgFlags & MSG_HDR_FLAG_SPLIT_MSG) { + smsdev->split_cb = cb; + return; + } + + if (hdr->msgLength > smsdev->func->cur_blksize) + size = hdr->msgLength - smsdev->func->cur_blksize; + else + size = 0; + } else { + cb = smsdev->split_cb; + hdr = cb->p; + + size = hdr->msgLength - sizeof(struct SmsMsgHdr_ST); + + smsdev->split_cb = NULL; + } + + if (size) { + void *buffer; + + buffer = cb->p + (hdr->msgLength - size); + size = ALIGN(size, SMSSDIO_BLOCK_SIZE); + + BUG_ON(smsdev->func->cur_blksize != SMSSDIO_BLOCK_SIZE); + + /* + * First attempt to transfer all of it in one go... + */ + ret = sdio_memcpy_fromio(smsdev->func, + buffer, + SMSSDIO_DATA, + size); + if (ret && ret != -EINVAL) { + smscore_putbuffer(smsdev->coredev, cb); + sms_err("Error %d reading data from card!\n", ret); + return; + } + + /* + * ..then fall back to one block at a time if that is + * not possible... + * + * (we have to do this manually because of the + * problem with the "increase address" bit) + */ + if (ret == -EINVAL) { + while (size) { + ret = sdio_memcpy_fromio(smsdev->func, + buffer, SMSSDIO_DATA, + smsdev->func->cur_blksize); + if (ret) { + smscore_putbuffer(smsdev->coredev, cb); + sms_err("Error %d reading " + "data from card!\n", ret); + return; + } + + buffer += smsdev->func->cur_blksize; + if (size > smsdev->func->cur_blksize) + size -= smsdev->func->cur_blksize; + else + size = 0; + } + } + } + + cb->size = hdr->msgLength; + cb->offset = 0; + + smscore_onresponse(smsdev->coredev, cb); +} + +static int __devinit smssdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + int ret; + + int board_id; + struct smssdio_device *smsdev; + struct smsdevice_params_t params; + + board_id = id->driver_data; + + smsdev = kzalloc(sizeof(struct smssdio_device), GFP_KERNEL); + if (!smsdev) + return -ENOMEM; + + smsdev->func = func; + + memset(¶ms, 0, sizeof(struct smsdevice_params_t)); + + params.device = &func->dev; + params.buffer_size = 0x5000; /* ?? */ + params.num_buffers = 22; /* ?? */ + params.context = smsdev; + + snprintf(params.devpath, sizeof(params.devpath), + "sdio\\%s", sdio_func_id(func)); + + params.sendrequest_handler = smssdio_sendrequest; + + params.device_type = sms_get_board(board_id)->type; + + if (params.device_type != SMS_STELLAR) + params.flags |= SMS_DEVICE_FAMILY2; + else { + /* + * FIXME: Stellar needs special handling... + */ + ret = -ENODEV; + goto free; + } + + ret = smscore_register_device(¶ms, &smsdev->coredev); + if (ret < 0) + goto free; + + smscore_set_board_id(smsdev->coredev, board_id); + + sdio_claim_host(func); + + ret = sdio_enable_func(func); + if (ret) + goto release; + + ret = sdio_set_block_size(func, SMSSDIO_BLOCK_SIZE); + if (ret) + goto disable; + + ret = sdio_claim_irq(func, smssdio_interrupt); + if (ret) + goto disable; + + sdio_set_drvdata(func, smsdev); + + sdio_release_host(func); + + ret = smscore_start_device(smsdev->coredev); + if (ret < 0) + goto reclaim; + + return 0; + +reclaim: + sdio_claim_host(func); + sdio_release_irq(func); +disable: + sdio_disable_func(func); +release: + sdio_release_host(func); + smscore_unregister_device(smsdev->coredev); +free: + kfree(smsdev); + + return ret; +} + +static void smssdio_remove(struct sdio_func *func) +{ + struct smssdio_device *smsdev; + + smsdev = sdio_get_drvdata(func); + + /* FIXME: racy! */ + if (smsdev->split_cb) + smscore_putbuffer(smsdev->coredev, smsdev->split_cb); + + smscore_unregister_device(smsdev->coredev); + + sdio_claim_host(func); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); + + kfree(smsdev); +} + +static struct sdio_driver smssdio_driver = { + .name = "smssdio", + .id_table = smssdio_ids, + .probe = smssdio_probe, + .remove = smssdio_remove, +}; + +/*******************************************************************/ +/* Module functions */ +/*******************************************************************/ + +static int __init smssdio_module_init(void) +{ + int ret = 0; + + printk(KERN_INFO "smssdio: Siano SMS1xxx SDIO driver\n"); + printk(KERN_INFO "smssdio: Copyright Pierre Ossman\n"); + + ret = sdio_register_driver(&smssdio_driver); + + return ret; +} + +static void __exit smssdio_module_exit(void) +{ + sdio_unregister_driver(&smssdio_driver); +} + +module_init(smssdio_module_init); +module_exit(smssdio_module_exit); + +MODULE_DESCRIPTION("Siano SMS1xxx SDIO driver"); +MODULE_AUTHOR("Pierre Ossman"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/siano/smsusb.c b/drivers/media/usb/siano/smsusb.c new file mode 100644 index 000000000000..664e460f247b --- /dev/null +++ b/drivers/media/usb/siano/smsusb.c @@ -0,0 +1,568 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. +Copyright (C) 2005-2009, Uri Shkolnik, Anatoly Greenblat + +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 <linux/kernel.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/firmware.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include "smscoreapi.h" +#include "sms-cards.h" +#include "smsendian.h" + +static int sms_dbg; +module_param_named(debug, sms_dbg, int, 0644); +MODULE_PARM_DESC(debug, "set debug level (info=1, adv=2 (or-able))"); + +#define USB1_BUFFER_SIZE 0x1000 +#define USB2_BUFFER_SIZE 0x4000 + +#define MAX_BUFFERS 50 +#define MAX_URBS 10 + +struct smsusb_device_t; + +struct smsusb_urb_t { + struct smscore_buffer_t *cb; + struct smsusb_device_t *dev; + + struct urb urb; +}; + +struct smsusb_device_t { + struct usb_device *udev; + struct smscore_device_t *coredev; + + struct smsusb_urb_t surbs[MAX_URBS]; + + int response_alignment; + int buffer_size; +}; + +static int smsusb_submit_urb(struct smsusb_device_t *dev, + struct smsusb_urb_t *surb); + +static void smsusb_onresponse(struct urb *urb) +{ + struct smsusb_urb_t *surb = (struct smsusb_urb_t *) urb->context; + struct smsusb_device_t *dev = surb->dev; + + if (urb->status == -ESHUTDOWN) { + sms_err("error, urb status %d (-ESHUTDOWN), %d bytes", + urb->status, urb->actual_length); + return; + } + + if ((urb->actual_length > 0) && (urb->status == 0)) { + struct SmsMsgHdr_ST *phdr = (struct SmsMsgHdr_ST *)surb->cb->p; + + smsendian_handle_message_header(phdr); + if (urb->actual_length >= phdr->msgLength) { + surb->cb->size = phdr->msgLength; + + if (dev->response_alignment && + (phdr->msgFlags & MSG_HDR_FLAG_SPLIT_MSG)) { + + surb->cb->offset = + dev->response_alignment + + ((phdr->msgFlags >> 8) & 3); + + /* sanity check */ + if (((int) phdr->msgLength + + surb->cb->offset) > urb->actual_length) { + sms_err("invalid response " + "msglen %d offset %d " + "size %d", + phdr->msgLength, + surb->cb->offset, + urb->actual_length); + goto exit_and_resubmit; + } + + /* move buffer pointer and + * copy header to its new location */ + memcpy((char *) phdr + surb->cb->offset, + phdr, sizeof(struct SmsMsgHdr_ST)); + } else + surb->cb->offset = 0; + + smscore_onresponse(dev->coredev, surb->cb); + surb->cb = NULL; + } else { + sms_err("invalid response " + "msglen %d actual %d", + phdr->msgLength, urb->actual_length); + } + } else + sms_err("error, urb status %d, %d bytes", + urb->status, urb->actual_length); + + +exit_and_resubmit: + smsusb_submit_urb(dev, surb); +} + +static int smsusb_submit_urb(struct smsusb_device_t *dev, + struct smsusb_urb_t *surb) +{ + if (!surb->cb) { + surb->cb = smscore_getbuffer(dev->coredev); + if (!surb->cb) { + sms_err("smscore_getbuffer(...) returned NULL"); + return -ENOMEM; + } + } + + usb_fill_bulk_urb( + &surb->urb, + dev->udev, + usb_rcvbulkpipe(dev->udev, 0x81), + surb->cb->p, + dev->buffer_size, + smsusb_onresponse, + surb + ); + surb->urb.transfer_dma = surb->cb->phys; + surb->urb.transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + return usb_submit_urb(&surb->urb, GFP_ATOMIC); +} + +static void smsusb_stop_streaming(struct smsusb_device_t *dev) +{ + int i; + + for (i = 0; i < MAX_URBS; i++) { + usb_kill_urb(&dev->surbs[i].urb); + + if (dev->surbs[i].cb) { + smscore_putbuffer(dev->coredev, dev->surbs[i].cb); + dev->surbs[i].cb = NULL; + } + } +} + +static int smsusb_start_streaming(struct smsusb_device_t *dev) +{ + int i, rc; + + for (i = 0; i < MAX_URBS; i++) { + rc = smsusb_submit_urb(dev, &dev->surbs[i]); + if (rc < 0) { + sms_err("smsusb_submit_urb(...) failed"); + smsusb_stop_streaming(dev); + break; + } + } + + return rc; +} + +static int smsusb_sendrequest(void *context, void *buffer, size_t size) +{ + struct smsusb_device_t *dev = (struct smsusb_device_t *) context; + int dummy; + + smsendian_handle_message_header((struct SmsMsgHdr_ST *)buffer); + return usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, 2), + buffer, size, &dummy, 1000); +} + +static char *smsusb1_fw_lkup[] = { + "dvbt_stellar_usb.inp", + "dvbh_stellar_usb.inp", + "tdmb_stellar_usb.inp", + "none", + "dvbt_bda_stellar_usb.inp", +}; + +static inline char *sms_get_fw_name(int mode, int board_id) +{ + char **fw = sms_get_board(board_id)->fw; + return (fw && fw[mode]) ? fw[mode] : smsusb1_fw_lkup[mode]; +} + +static int smsusb1_load_firmware(struct usb_device *udev, int id, int board_id) +{ + const struct firmware *fw; + u8 *fw_buffer; + int rc, dummy; + char *fw_filename; + + if (id < DEVICE_MODE_DVBT || id > DEVICE_MODE_DVBT_BDA) { + sms_err("invalid firmware id specified %d", id); + return -EINVAL; + } + + fw_filename = sms_get_fw_name(id, board_id); + + rc = request_firmware(&fw, fw_filename, &udev->dev); + if (rc < 0) { + sms_warn("failed to open \"%s\" mode %d, " + "trying again with default firmware", fw_filename, id); + + fw_filename = smsusb1_fw_lkup[id]; + rc = request_firmware(&fw, fw_filename, &udev->dev); + if (rc < 0) { + sms_warn("failed to open \"%s\" mode %d", + fw_filename, id); + + return rc; + } + } + + fw_buffer = kmalloc(fw->size, GFP_KERNEL); + if (fw_buffer) { + memcpy(fw_buffer, fw->data, fw->size); + + rc = usb_bulk_msg(udev, usb_sndbulkpipe(udev, 2), + fw_buffer, fw->size, &dummy, 1000); + + sms_info("sent %zd(%d) bytes, rc %d", fw->size, dummy, rc); + + kfree(fw_buffer); + } else { + sms_err("failed to allocate firmware buffer"); + rc = -ENOMEM; + } + sms_info("read FW %s, size=%zd", fw_filename, fw->size); + + release_firmware(fw); + + return rc; +} + +static void smsusb1_detectmode(void *context, int *mode) +{ + char *product_string = + ((struct smsusb_device_t *) context)->udev->product; + + *mode = DEVICE_MODE_NONE; + + if (!product_string) { + product_string = "none"; + sms_err("product string not found"); + } else if (strstr(product_string, "DVBH")) + *mode = 1; + else if (strstr(product_string, "BDA")) + *mode = 4; + else if (strstr(product_string, "DVBT")) + *mode = 0; + else if (strstr(product_string, "TDMB")) + *mode = 2; + + sms_info("%d \"%s\"", *mode, product_string); +} + +static int smsusb1_setmode(void *context, int mode) +{ + struct SmsMsgHdr_ST Msg = { MSG_SW_RELOAD_REQ, 0, HIF_TASK, + sizeof(struct SmsMsgHdr_ST), 0 }; + + if (mode < DEVICE_MODE_DVBT || mode > DEVICE_MODE_DVBT_BDA) { + sms_err("invalid firmware id specified %d", mode); + return -EINVAL; + } + + return smsusb_sendrequest(context, &Msg, sizeof(Msg)); +} + +static void smsusb_term_device(struct usb_interface *intf) +{ + struct smsusb_device_t *dev = usb_get_intfdata(intf); + + if (dev) { + smsusb_stop_streaming(dev); + + /* unregister from smscore */ + if (dev->coredev) + smscore_unregister_device(dev->coredev); + + sms_info("device %p destroyed", dev); + kfree(dev); + } + + usb_set_intfdata(intf, NULL); +} + +static int smsusb_init_device(struct usb_interface *intf, int board_id) +{ + struct smsdevice_params_t params; + struct smsusb_device_t *dev; + int i, rc; + + /* create device object */ + dev = kzalloc(sizeof(struct smsusb_device_t), GFP_KERNEL); + if (!dev) { + sms_err("kzalloc(sizeof(struct smsusb_device_t) failed"); + return -ENOMEM; + } + + memset(¶ms, 0, sizeof(params)); + usb_set_intfdata(intf, dev); + dev->udev = interface_to_usbdev(intf); + + params.device_type = sms_get_board(board_id)->type; + + switch (params.device_type) { + case SMS_STELLAR: + dev->buffer_size = USB1_BUFFER_SIZE; + + params.setmode_handler = smsusb1_setmode; + params.detectmode_handler = smsusb1_detectmode; + break; + default: + sms_err("Unspecified sms device type!"); + /* fall-thru */ + case SMS_NOVA_A0: + case SMS_NOVA_B0: + case SMS_VEGA: + dev->buffer_size = USB2_BUFFER_SIZE; + dev->response_alignment = + le16_to_cpu(dev->udev->ep_in[1]->desc.wMaxPacketSize) - + sizeof(struct SmsMsgHdr_ST); + + params.flags |= SMS_DEVICE_FAMILY2; + break; + } + + params.device = &dev->udev->dev; + params.buffer_size = dev->buffer_size; + params.num_buffers = MAX_BUFFERS; + params.sendrequest_handler = smsusb_sendrequest; + params.context = dev; + usb_make_path(dev->udev, params.devpath, sizeof(params.devpath)); + + /* register in smscore */ + rc = smscore_register_device(¶ms, &dev->coredev); + if (rc < 0) { + sms_err("smscore_register_device(...) failed, rc %d", rc); + smsusb_term_device(intf); + return rc; + } + + smscore_set_board_id(dev->coredev, board_id); + + /* initialize urbs */ + for (i = 0; i < MAX_URBS; i++) { + dev->surbs[i].dev = dev; + usb_init_urb(&dev->surbs[i].urb); + } + + sms_info("smsusb_start_streaming(...)."); + rc = smsusb_start_streaming(dev); + if (rc < 0) { + sms_err("smsusb_start_streaming(...) failed"); + smsusb_term_device(intf); + return rc; + } + + rc = smscore_start_device(dev->coredev); + if (rc < 0) { + sms_err("smscore_start_device(...) failed"); + smsusb_term_device(intf); + return rc; + } + + sms_info("device %p created", dev); + + return rc; +} + +static int __devinit smsusb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + char devpath[32]; + int i, rc; + + rc = usb_clear_halt(udev, usb_rcvbulkpipe(udev, 0x81)); + rc = usb_clear_halt(udev, usb_rcvbulkpipe(udev, 0x02)); + + if (intf->num_altsetting > 0) { + rc = usb_set_interface( + udev, intf->cur_altsetting->desc.bInterfaceNumber, 0); + if (rc < 0) { + sms_err("usb_set_interface failed, rc %d", rc); + return rc; + } + } + + sms_info("smsusb_probe %d", + intf->cur_altsetting->desc.bInterfaceNumber); + for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) + sms_info("endpoint %d %02x %02x %d", i, + intf->cur_altsetting->endpoint[i].desc.bEndpointAddress, + intf->cur_altsetting->endpoint[i].desc.bmAttributes, + intf->cur_altsetting->endpoint[i].desc.wMaxPacketSize); + + if ((udev->actconfig->desc.bNumInterfaces == 2) && + (intf->cur_altsetting->desc.bInterfaceNumber == 0)) { + sms_err("rom interface 0 is not used"); + return -ENODEV; + } + + if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { + snprintf(devpath, sizeof(devpath), "usb\\%d-%s", + udev->bus->busnum, udev->devpath); + sms_info("stellar device was found."); + return smsusb1_load_firmware( + udev, smscore_registry_getmode(devpath), + id->driver_info); + } + + rc = smsusb_init_device(intf, id->driver_info); + sms_info("rc %d", rc); + sms_board_load_modules(id->driver_info); + return rc; +} + +static void smsusb_disconnect(struct usb_interface *intf) +{ + smsusb_term_device(intf); +} + +static int smsusb_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct smsusb_device_t *dev = usb_get_intfdata(intf); + printk(KERN_INFO "%s: Entering status %d.\n", __func__, msg.event); + smsusb_stop_streaming(dev); + return 0; +} + +static int smsusb_resume(struct usb_interface *intf) +{ + int rc, i; + struct smsusb_device_t *dev = usb_get_intfdata(intf); + struct usb_device *udev = interface_to_usbdev(intf); + + printk(KERN_INFO "%s: Entering.\n", __func__); + usb_clear_halt(udev, usb_rcvbulkpipe(udev, 0x81)); + usb_clear_halt(udev, usb_rcvbulkpipe(udev, 0x02)); + + for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) + printk(KERN_INFO "endpoint %d %02x %02x %d\n", i, + intf->cur_altsetting->endpoint[i].desc.bEndpointAddress, + intf->cur_altsetting->endpoint[i].desc.bmAttributes, + intf->cur_altsetting->endpoint[i].desc.wMaxPacketSize); + + if (intf->num_altsetting > 0) { + rc = usb_set_interface(udev, + intf->cur_altsetting->desc. + bInterfaceNumber, 0); + if (rc < 0) { + printk(KERN_INFO "%s usb_set_interface failed, " + "rc %d\n", __func__, rc); + return rc; + } + } + + smsusb_start_streaming(dev); + return 0; +} + +static const struct usb_device_id smsusb_id_table[] __devinitconst = { + { USB_DEVICE(0x187f, 0x0010), + .driver_info = SMS1XXX_BOARD_SIANO_STELLAR }, + { USB_DEVICE(0x187f, 0x0100), + .driver_info = SMS1XXX_BOARD_SIANO_STELLAR }, + { USB_DEVICE(0x187f, 0x0200), + .driver_info = SMS1XXX_BOARD_SIANO_NOVA_A }, + { USB_DEVICE(0x187f, 0x0201), + .driver_info = SMS1XXX_BOARD_SIANO_NOVA_B }, + { USB_DEVICE(0x187f, 0x0300), + .driver_info = SMS1XXX_BOARD_SIANO_VEGA }, + { USB_DEVICE(0x2040, 0x1700), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_CATAMOUNT }, + { USB_DEVICE(0x2040, 0x1800), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_OKEMO_A }, + { USB_DEVICE(0x2040, 0x1801), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_OKEMO_B }, + { USB_DEVICE(0x2040, 0x2000), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD }, + { USB_DEVICE(0x2040, 0x2009), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2 }, + { USB_DEVICE(0x2040, 0x200a), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD }, + { USB_DEVICE(0x2040, 0x2010), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD }, + { USB_DEVICE(0x2040, 0x2011), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD }, + { USB_DEVICE(0x2040, 0x2019), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD }, + { USB_DEVICE(0x2040, 0x5500), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0x5510), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0x5520), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0x5530), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0x5580), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0x5590), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x187f, 0x0202), + .driver_info = SMS1XXX_BOARD_SIANO_NICE }, + { USB_DEVICE(0x187f, 0x0301), + .driver_info = SMS1XXX_BOARD_SIANO_VENICE }, + { USB_DEVICE(0x2040, 0xb900), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0xb910), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0xb980), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0xb990), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0xc000), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0xc010), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0xc080), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0xc090), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0xc0a0), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { USB_DEVICE(0x2040, 0xf5a0), + .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM }, + { } /* Terminating entry */ + }; + +MODULE_DEVICE_TABLE(usb, smsusb_id_table); + +static struct usb_driver smsusb_driver = { + .name = "smsusb", + .probe = smsusb_probe, + .disconnect = smsusb_disconnect, + .id_table = smsusb_id_table, + + .suspend = smsusb_suspend, + .resume = smsusb_resume, +}; + +module_usb_driver(smsusb_driver); + +MODULE_DESCRIPTION("Driver for the Siano SMS1xxx USB dongle"); +MODULE_AUTHOR("Siano Mobile Silicon, INC. (uris@siano-ms.com)"); +MODULE_LICENSE("GPL"); |