diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index e3ce0ad0d9fa3c..ca12cf394ca94c 100755 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -81,13 +81,21 @@ config MEDIA_RC_SUPPORT config MEDIA_CONTROLLER bool "Media Controller API" - depends on MEDIA_CAMERA_SUPPORT + depends on MEDIA_CAMERA_SUPPORT || MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT ---help--- Enable the media controller API used to query media devices internal topology and configure it dynamically. This API is mostly used by camera interfaces in embedded platforms. +config MEDIA_CONTROLLER_DVB + bool "Enable Media controller for DVB" + depends on MEDIA_CONTROLLER + ---help--- + Enable the media controller API support for DVB. + + This is currently experimental. + # # Video4Linux support # Only enables if one of the V4L2 types (ATV, webcam, radio) is selected @@ -96,7 +104,7 @@ config MEDIA_CONTROLLER config VIDEO_DEV tristate depends on MEDIA_SUPPORT - depends on MEDIA_CAMERA_SUPPORT || MEDIA_ANALOG_TV_SUPPORT || MEDIA_RADIO_SUPPORT + depends on MEDIA_CAMERA_SUPPORT || MEDIA_ANALOG_TV_SUPPORT || MEDIA_RADIO_SUPPORT || MEDIA_SDR_SUPPORT default y config VIDEO_V4L2_SUBDEV_API @@ -172,7 +180,10 @@ comment "Media ancillary drivers (tuners, sensors, i2c, frontends)" config MEDIA_SUBDRV_AUTOSELECT bool "Autoselect ancillary drivers (tuners, sensors, i2c, frontends)" - depends on MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT || MEDIA_CAMERA_SUPPORT + depends on MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT || MEDIA_CAMERA_SUPPORT || MEDIA_SDR_SUPPORT + depends on HAS_IOMEM + select I2C + select I2C_MUX default y help By default, a media driver auto-selects all possible ancillary diff --git a/drivers/media/dvb-core/dmxdev.c b/drivers/media/dvb-core/dmxdev.c index 0b4616b8719594..d0e3f9d85f34c7 100644 --- a/drivers/media/dvb-core/dmxdev.c +++ b/drivers/media/dvb-core/dmxdev.c @@ -206,8 +206,6 @@ static int dvb_dvr_release(struct inode *inode, struct file *file) /* TODO */ dvbdev->users--; if (dvbdev->users == 1 && dmxdev->exit == 1) { - fops_put(file->f_op); - file->f_op = NULL; mutex_unlock(&dmxdev->mutex); wake_up(&dvbdev->wait_queue); } else @@ -1089,8 +1087,8 @@ static unsigned int dvb_demux_poll(struct file *file, poll_table *wait) struct dmxdev_filter *dmxdevfilter = file->private_data; unsigned int mask = 0; - if (!dmxdevfilter) - return -EINVAL; + if ((!dmxdevfilter) || dmxdevfilter->dev->exit) + return POLLERR; poll_wait(file, &dmxdevfilter->buffer.queue, wait); @@ -1120,8 +1118,6 @@ static int dvb_demux_release(struct inode *inode, struct file *file) mutex_lock(&dmxdev->mutex); dmxdev->dvbdev->users--; if(dmxdev->dvbdev->users==1 && dmxdev->exit==1) { - fops_put(file->f_op); - file->f_op = NULL; mutex_unlock(&dmxdev->mutex); wake_up(&dmxdev->dvbdev->wait_queue); } else @@ -1140,10 +1136,13 @@ static const struct file_operations dvb_demux_fops = { .llseek = default_llseek, }; -static struct dvb_device dvbdev_demux = { +static const struct dvb_device dvbdev_demux = { .priv = NULL, .users = 1, .writers = 1, +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + .name = "dvb-demux", +#endif .fops = &dvb_demux_fops }; @@ -1185,6 +1184,9 @@ static unsigned int dvb_dvr_poll(struct file *file, poll_table *wait) dprintk("function : %s\n", __func__); + if (dmxdev->exit) + return POLLERR; + poll_wait(file, &dmxdev->dvr_buffer.queue, wait); if ((file->f_flags & O_ACCMODE) == O_RDONLY) { @@ -1210,13 +1212,15 @@ static const struct file_operations dvb_dvr_fops = { .llseek = default_llseek, }; -static struct dvb_device dvbdev_dvr = { +static const struct dvb_device dvbdev_dvr = { .priv = NULL, .readers = 1, .users = 1, +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + .name = "dvb-dvr", +#endif .fops = &dvb_dvr_fops }; - int dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *dvb_adapter) { int i; diff --git a/drivers/media/dvb-core/dvb-usb-ids.h b/drivers/media/dvb-core/dvb-usb-ids.h index 335a8f4695b469..80ab8d0ff6e09a 100644 --- a/drivers/media/dvb-core/dvb-usb-ids.h +++ b/drivers/media/dvb-core/dvb-usb-ids.h @@ -144,6 +144,7 @@ #define USB_PID_ITETECH_IT9135 0x9135 #define USB_PID_ITETECH_IT9135_9005 0x9005 #define USB_PID_ITETECH_IT9135_9006 0x9006 +#define USB_PID_ITETECH_IT9303 0x9306 #define USB_PID_KWORLD_399U 0xe399 #define USB_PID_KWORLD_399U_2 0xe400 #define USB_PID_KWORLD_395U 0xe396 @@ -239,10 +240,13 @@ #define USB_PID_AVERMEDIA_A835B_4835 0x4835 #define USB_PID_AVERMEDIA_1867 0x1867 #define USB_PID_AVERMEDIA_A867 0xa867 +#define USB_PID_AVERMEDIA_H335 0x0335 #define USB_PID_AVERMEDIA_TWINSTAR 0x0825 #define USB_PID_TECHNOTREND_CONNECT_S2400 0x3006 #define USB_PID_TECHNOTREND_CONNECT_S2400_8KEEPROM 0x3009 #define USB_PID_TECHNOTREND_CONNECT_CT3650 0x300d +#define USB_PID_TECHNOTREND_CONNECT_CT2_4650_CI 0x3012 +#define USB_PID_TECHNOTREND_TVSTICK_CT2_4400 0x3014 #define USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY 0x005a #define USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY_2 0x0081 #define USB_PID_TERRATEC_CINERGY_HT_USB_XE 0x0058 @@ -256,6 +260,7 @@ #define USB_PID_TERRATEC_T5 0x10a1 #define USB_PID_NOXON_DAB_STICK 0x00b3 #define USB_PID_NOXON_DAB_STICK_REV2 0x00e0 +#define USB_PID_NOXON_DAB_STICK_REV3 0x00b4 #define USB_PID_PINNACLE_EXPRESSCARD_320CX 0x022e #define USB_PID_PINNACLE_PCTV2000E 0x022c #define USB_PID_PINNACLE_PCTV_DVB_T_FLASH 0x0228 @@ -277,6 +282,8 @@ #define USB_PID_PCTV_400E 0x020f #define USB_PID_PCTV_450E 0x0222 #define USB_PID_PCTV_452E 0x021f +#define USB_PID_PCTV_78E 0x025a +#define USB_PID_PCTV_79E 0x0262 #define USB_PID_REALTEK_RTL2831U 0x2831 #define USB_PID_REALTEK_RTL2832U 0x2832 #define USB_PID_TECHNOTREND_CONNECT_S2_3600 0x3007 @@ -317,6 +324,7 @@ #define USB_PID_WINFAST_DTV_DONGLE_H 0x60f6 #define USB_PID_WINFAST_DTV_DONGLE_STK7700P_2 0x6f01 #define USB_PID_WINFAST_DTV_DONGLE_GOLD 0x6029 +#define USB_PID_WINFAST_DTV_DONGLE_MINID 0x6f0f #define USB_PID_GENPIX_8PSK_REV_1_COLD 0x0200 #define USB_PID_GENPIX_8PSK_REV_1_WARM 0x0201 #define USB_PID_GENPIX_8PSK_REV_2 0x0202 @@ -348,16 +356,20 @@ #define USB_PID_MSI_DIGI_VOX_MINI_III 0x8807 #define USB_PID_SONY_PLAYTV 0x0003 #define USB_PID_MYGICA_D689 0xd811 +#define USB_PID_MYGICA_T230 0xc688 #define USB_PID_ELGATO_EYETV_DIVERSITY 0x0011 #define USB_PID_ELGATO_EYETV_DTT 0x0021 #define USB_PID_ELGATO_EYETV_DTT_2 0x003f #define USB_PID_ELGATO_EYETV_DTT_Dlx 0x0020 #define USB_PID_ELGATO_EYETV_SAT 0x002a +#define USB_PID_ELGATO_EYETV_SAT_V2 0x0025 #define USB_PID_DVB_T_USB_STICK_HIGH_SPEED_COLD 0x5000 #define USB_PID_DVB_T_USB_STICK_HIGH_SPEED_WARM 0x5001 #define USB_PID_FRIIO_WHITE 0x0001 #define USB_PID_TVWAY_PLUS 0x0002 #define USB_PID_SVEON_STV20 0xe39d +#define USB_PID_SVEON_STV20_RTL2832U 0xd39d +#define USB_PID_SVEON_STV21 0xd3b0 #define USB_PID_SVEON_STV22 0xe401 #define USB_PID_SVEON_STV22_IT9137 0xe411 #define USB_PID_AZUREWAVE_AZ6027 0x3275 @@ -365,6 +377,12 @@ #define USB_PID_TERRATEC_DVBS2CI_V2 0x10ac #define USB_PID_TECHNISAT_USB2_HDCI_V1 0x0001 #define USB_PID_TECHNISAT_USB2_HDCI_V2 0x0002 +#define USB_PID_TECHNISAT_USB2_CABLESTAR_HDCI 0x0003 #define USB_PID_TECHNISAT_AIRSTAR_TELESTICK_2 0x0004 #define USB_PID_TECHNISAT_USB2_DVB_S2 0x0500 +#define USB_PID_CPYTO_REDI_PC50A 0xa803 +#define USB_PID_CTVDIGDUAL_V2 0xe410 +#define USB_PID_PCTV_2002E 0x025c +#define USB_PID_PCTV_2002E_SE 0x025d +#define USB_PID_SVEON_STV27 0xd3af #endif diff --git a/drivers/media/dvb-core/dvb_ca_en50221.c b/drivers/media/dvb-core/dvb_ca_en50221.c index 0aac3096728ee1..55a217f0ad0e67 100644 --- a/drivers/media/dvb-core/dvb_ca_en50221.c +++ b/drivers/media/dvb-core/dvb_ca_en50221.c @@ -1638,15 +1638,17 @@ static const struct file_operations dvb_ca_fops = { .llseek = noop_llseek, }; -static struct dvb_device dvbdev_ca = { +static const struct dvb_device dvbdev_ca = { .priv = NULL, .users = 1, .readers = 1, .writers = 1, +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + .name = "dvb-ca-en50221", +#endif .fops = &dvb_ca_fops, }; - /* ******************************************************************************** */ /* Initialisation/shutdown functions */ diff --git a/drivers/media/dvb-core/dvb_demux.c b/drivers/media/dvb-core/dvb_demux.c index 3485655fa08297..6c7ff0cdcd32dd 100644 --- a/drivers/media/dvb-core/dvb_demux.c +++ b/drivers/media/dvb-core/dvb_demux.c @@ -435,7 +435,7 @@ static void dvb_dmx_swfilter_packet(struct dvb_demux *demux, const u8 *buf) dprintk_tscheck("TEI detected. " "PID=0x%x data1=0x%x\n", pid, buf[1]); - /* data in this packet cant be trusted - drop it unless + /* data in this packet can't be trusted - drop it unless * module option dvb_demux_feed_err_pkts is set */ if (!dvb_demux_feed_err_pkts) return; @@ -476,7 +476,9 @@ static void dvb_dmx_swfilter_packet(struct dvb_demux *demux, const u8 *buf) void dvb_dmx_swfilter_packets(struct dvb_demux *demux, const u8 *buf, size_t count) { - spin_lock(&demux->lock); + unsigned long flags; + + spin_lock_irqsave(&demux->lock, flags); while (count--) { if (buf[0] == 0x47) @@ -484,7 +486,7 @@ void dvb_dmx_swfilter_packets(struct dvb_demux *demux, const u8 *buf, buf += 188; } - spin_unlock(&demux->lock); + spin_unlock_irqrestore(&demux->lock, flags); } EXPORT_SYMBOL(dvb_dmx_swfilter_packets); @@ -519,8 +521,9 @@ static inline void _dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, { int p = 0, i, j; const u8 *q; + unsigned long flags; - spin_lock(&demux->lock); + spin_lock_irqsave(&demux->lock, flags); if (demux->tsbufp) { /* tsbuf[0] is now 0x47. */ i = demux->tsbufp; @@ -564,7 +567,7 @@ static inline void _dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, } bailout: - spin_unlock(&demux->lock); + spin_unlock_irqrestore(&demux->lock, flags); } void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count) @@ -581,11 +584,13 @@ EXPORT_SYMBOL(dvb_dmx_swfilter_204); void dvb_dmx_swfilter_raw(struct dvb_demux *demux, const u8 *buf, size_t count) { - spin_lock(&demux->lock); + unsigned long flags; + + spin_lock_irqsave(&demux->lock, flags); demux->feed->cb.ts(buf, count, NULL, 0, &demux->feed->feed.ts, DMX_OK); - spin_unlock(&demux->lock); + spin_unlock_irqrestore(&demux->lock, flags); } EXPORT_SYMBOL(dvb_dmx_swfilter_raw); @@ -1027,8 +1032,13 @@ static int dmx_section_feed_release_filter(struct dmx_section_feed *feed, return -EINVAL; } - if (feed->is_filtering) + if (feed->is_filtering) { + /* release dvbdmx->mutex as far as it is + acquired by stop_filtering() itself */ + mutex_unlock(&dvbdmx->mutex); feed->stop_filtering(feed); + mutex_lock(&dvbdmx->mutex); + } spin_lock_irq(&dvbdmx->lock); f = dvbdmxfeed->filter; diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core/dvb_frontend.c index 1f925e85697493..882ca417f328a3 100644 --- a/drivers/media/dvb-core/dvb_frontend.c +++ b/drivers/media/dvb-core/dvb_frontend.c @@ -96,10 +96,6 @@ MODULE_PARM_DESC(dvb_mfe_wait_time, "Wait up to seconds on open( * FESTATE_LOSTLOCK. When the lock has been lost, and we're searching it again. */ -#define DVB_FE_NO_EXIT 0 -#define DVB_FE_NORMAL_EXIT 1 -#define DVB_FE_DEVICE_REMOVED 2 - static DEFINE_MUTEX(frontend_mutex); struct dvb_frontend_private { @@ -113,7 +109,6 @@ struct dvb_frontend_private { wait_queue_head_t wait_queue; struct task_struct *thread; unsigned long release_jiffies; - unsigned int exit; unsigned int wakeup; fe_status_t status; unsigned long tune_mode_flags; @@ -136,6 +131,11 @@ struct dvb_frontend_private { int quality; unsigned int check_wrapped; enum dvbfe_search algo_status; + +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + struct media_pipeline pipe; + struct media_entity *pipe_start_entity; +#endif }; static void dvb_frontend_wakeup(struct dvb_frontend *fe); @@ -565,7 +565,7 @@ static int dvb_frontend_is_exiting(struct dvb_frontend *fe) { struct dvb_frontend_private *fepriv = fe->frontend_priv; - if (fepriv->exit != DVB_FE_NO_EXIT) + if (fe->exit != DVB_FE_NO_EXIT) return 1; if (fepriv->dvbdev->writers == 1) @@ -595,12 +595,106 @@ static void dvb_frontend_wakeup(struct dvb_frontend *fe) wake_up_interruptible(&fepriv->wait_queue); } +/** + * dvb_enable_media_tuner() - tries to enable the DVB tuner + * + * @fe: struct dvb_frontend pointer + * + * This function ensures that just one media tuner is enabled for a given + * frontend. It has two different behaviors: + * - For trivial devices with just one tuner: + * it just enables the existing tuner->fe link + * - For devices with more than one tuner: + * It is up to the driver to implement the logic that will enable one tuner + * and disable the other ones. However, if more than one tuner is enabled for + * the same frontend, it will print an error message and return -EINVAL. + * + * At return, it will return the error code returned by media_entity_setup_link, + * or 0 if everything is OK, if no tuner is linked to the frontend or if the + * mdev is NULL. + */ +#ifdef CONFIG_MEDIA_CONTROLLER_DVB +static int dvb_enable_media_tuner(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_adapter *adapter = fe->dvb; + struct media_device *mdev = adapter->mdev; + struct media_entity *entity, *source; + struct media_link *link, *found_link = NULL; + int i, ret, n_links = 0, active_links = 0; + + fepriv->pipe_start_entity = NULL; + + if (!mdev) + return 0; + + entity = fepriv->dvbdev->entity; + fepriv->pipe_start_entity = entity; + + for (i = 0; i < entity->num_links; i++) { + link = &entity->links[i]; + if (link->sink->entity == entity) { + found_link = link; + n_links++; + if (link->flags & MEDIA_LNK_FL_ENABLED) + active_links++; + } + } + + if (!n_links || active_links == 1 || !found_link) + return 0; + + /* + * If a frontend has more than one tuner linked, it is up to the driver + * to select with one will be the active one, as the frontend core can't + * guess. If the driver doesn't do that, it is a bug. + */ + if (n_links > 1 && active_links != 1) { + dev_err(fe->dvb->device, + "WARNING: there are %d active links among %d tuners. This is a driver's bug!\n", + active_links, n_links); + return -EINVAL; + } + + source = found_link->source->entity; + fepriv->pipe_start_entity = source; + for (i = 0; i < source->num_links; i++) { + struct media_entity *sink; + int flags = 0; + + link = &source->links[i]; + sink = link->sink->entity; + + if (sink == entity) + flags = MEDIA_LNK_FL_ENABLED; + + ret = media_entity_setup_link(link, flags); + if (ret) { + dev_err(fe->dvb->device, + "Couldn't change link %s->%s to %s. Error %d\n", + source->name, sink->name, + flags ? "enabled" : "disabled", + ret); + return ret; + } else + dev_dbg(fe->dvb->device, + "link %s->%s was %s\n", + source->name, sink->name, + flags ? "ENABLED" : "disabled"); + } + return 0; +} +#endif + static int dvb_frontend_thread(void *data) { struct dvb_frontend *fe = data; struct dvb_frontend_private *fepriv = fe->frontend_priv; fe_status_t s; enum dvbfe_algo algo; +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + int ret; +#endif bool re_tune = false; bool semheld = false; @@ -614,6 +708,20 @@ static int dvb_frontend_thread(void *data) fepriv->wakeup = 0; fepriv->reinitialise = 0; +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + ret = dvb_enable_media_tuner(fe); + if (ret) { + /* FIXME: return an error if it fails */ + dev_info(fe->dvb->device, + "proceeding with FE task\n"); + } else if (fepriv->pipe_start_entity) { + ret = media_entity_pipeline_start(fepriv->pipe_start_entity, + &fepriv->pipe); + if (ret) + return ret; + } +#endif + dvb_frontend_init(fe); set_freezable(); @@ -629,7 +737,7 @@ static int dvb_frontend_thread(void *data) /* got signal or quitting */ if (!down_interruptible(&fepriv->sem)) semheld = true; - fepriv->exit = DVB_FE_NORMAL_EXIT; + fe->exit = DVB_FE_NORMAL_EXIT; break; } @@ -723,6 +831,12 @@ static int dvb_frontend_thread(void *data) } } +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + if (fepriv->pipe_start_entity) + media_entity_pipeline_stop(fepriv->pipe_start_entity); + fepriv->pipe_start_entity = NULL; +#endif + if (dvb_powerdown_on_sleep) { if (fe->ops.set_voltage) fe->ops.set_voltage(fe, SEC_VOLTAGE_OFF); @@ -739,9 +853,9 @@ static int dvb_frontend_thread(void *data) fepriv->thread = NULL; if (kthread_should_stop()) - fepriv->exit = DVB_FE_DEVICE_REMOVED; + fe->exit = DVB_FE_DEVICE_REMOVED; else - fepriv->exit = DVB_FE_NO_EXIT; + fe->exit = DVB_FE_NO_EXIT; mb(); if (semheld) @@ -756,7 +870,8 @@ static void dvb_frontend_stop(struct dvb_frontend *fe) dev_dbg(fe->dvb->device, "%s:\n", __func__); - fepriv->exit = DVB_FE_NORMAL_EXIT; + if (fe->exit != DVB_FE_DEVICE_REMOVED) + fe->exit = DVB_FE_NORMAL_EXIT; mb(); if (!fepriv->thread) @@ -826,7 +941,7 @@ static int dvb_frontend_start(struct dvb_frontend *fe) dev_dbg(fe->dvb->device, "%s:\n", __func__); if (fepriv->thread) { - if (fepriv->exit == DVB_FE_NO_EXIT) + if (fe->exit == DVB_FE_NO_EXIT) return 0; else dvb_frontend_stop (fe); @@ -838,7 +953,7 @@ static int dvb_frontend_start(struct dvb_frontend *fe) return -EINTR; fepriv->state = FESTATE_IDLE; - fepriv->exit = DVB_FE_NO_EXIT; + fe->exit = DVB_FE_NO_EXIT; fepriv->thread = NULL; mb(); @@ -966,6 +1081,11 @@ static int dvb_frontend_clear_cache(struct dvb_frontend *fe) case SYS_ATSC: c->modulation = VSB_8; break; + case SYS_ISDBS: + c->symbol_rate = 28860000; + c->rolloff = ROLLOFF_35; + c->bandwidth_hz = c->symbol_rate / 100 * 135; + break; default: c->modulation = QAM_AUTO; break; @@ -1279,7 +1399,7 @@ static int dtv_property_process_get(struct dvb_frontend *fe, switch(tvp->cmd) { case DTV_ENUM_DELSYS: ncaps = 0; - while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + while (ncaps < MAX_DELSYS && fe->ops.delsys[ncaps]) { tvp->u.buffer.data[ncaps] = fe->ops.delsys[ncaps]; ncaps++; } @@ -1596,7 +1716,7 @@ static int dvbv5_set_delivery_system(struct dvb_frontend *fe, * supported */ ncaps = 0; - while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + while (ncaps < MAX_DELSYS && fe->ops.delsys[ncaps]) { if (fe->ops.delsys[ncaps] == desired_system) { c->delivery_system = desired_system; dev_dbg(fe->dvb->device, @@ -1628,7 +1748,7 @@ static int dvbv5_set_delivery_system(struct dvb_frontend *fe, * of the desired system */ ncaps = 0; - while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + while (ncaps < MAX_DELSYS && fe->ops.delsys[ncaps]) { if (dvbv3_type(fe->ops.delsys[ncaps]) == type) delsys = fe->ops.delsys[ncaps]; ncaps++; @@ -1703,7 +1823,7 @@ static int dvbv3_set_delivery_system(struct dvb_frontend *fe) * DVBv3 standard */ ncaps = 0; - while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + while (ncaps < MAX_DELSYS && fe->ops.delsys[ncaps]) { if (dvbv3_type(fe->ops.delsys[ncaps]) != DVBV3_UNKNOWN) { delsys = fe->ops.delsys[ncaps]; break; @@ -1882,6 +2002,8 @@ static int dtv_property_process_set(struct dvb_frontend *fe, c->lna = tvp->u.data; if (fe->ops.set_lna) r = fe->ops.set_lna(fe); + if (r < 0) + c->lna = LNA_AUTO; break; default: @@ -1904,7 +2026,7 @@ static int dvb_frontend_ioctl(struct file *file, if (down_interruptible(&fepriv->sem)) return -ERESTARTSYS; - if (fepriv->exit != DVB_FE_NO_EXIT) { + if (fe->exit != DVB_FE_NO_EXIT) { up(&fepriv->sem); return -ENODEV; } @@ -1936,15 +2058,13 @@ static int dvb_frontend_ioctl_properties(struct file *file, struct dtv_frontend_properties *c = &fe->dtv_property_cache; int err = 0; - struct dtv_properties *tvps = NULL; + struct dtv_properties *tvps = parg; struct dtv_property *tvp = NULL; int i; dev_dbg(fe->dvb->device, "%s:\n", __func__); - if(cmd == FE_SET_PROPERTY) { - tvps = (struct dtv_properties __user *)parg; - + if (cmd == FE_SET_PROPERTY) { dev_dbg(fe->dvb->device, "%s: properties.num = %d\n", __func__, tvps->num); dev_dbg(fe->dvb->device, "%s: properties.props = %p\n", __func__, tvps->props); @@ -1959,7 +2079,8 @@ static int dvb_frontend_ioctl_properties(struct file *file, goto out; } - if (copy_from_user(tvp, tvps->props, tvps->num * sizeof(struct dtv_property))) { + if (copy_from_user(tvp, (void __user *)tvps->props, + tvps->num * sizeof(struct dtv_property))) { err = -EFAULT; goto out; } @@ -1974,10 +2095,7 @@ static int dvb_frontend_ioctl_properties(struct file *file, if (c->state == DTV_TUNE) dev_dbg(fe->dvb->device, "%s: Property cache is full, tuning\n", __func__); - } else - if(cmd == FE_GET_PROPERTY) { - tvps = (struct dtv_properties __user *)parg; - + } else if (cmd == FE_GET_PROPERTY) { dev_dbg(fe->dvb->device, "%s: properties.num = %d\n", __func__, tvps->num); dev_dbg(fe->dvb->device, "%s: properties.props = %p\n", __func__, tvps->props); @@ -1992,7 +2110,8 @@ static int dvb_frontend_ioctl_properties(struct file *file, goto out; } - if (copy_from_user(tvp, tvps->props, tvps->num * sizeof(struct dtv_property))) { + if (copy_from_user(tvp, (void __user *)tvps->props, + tvps->num * sizeof(struct dtv_property))) { err = -EFAULT; goto out; } @@ -2014,7 +2133,8 @@ static int dvb_frontend_ioctl_properties(struct file *file, (tvp + i)->result = err; } - if (copy_to_user(tvps->props, tvp, tvps->num * sizeof(struct dtv_property))) { + if (copy_to_user((void __user *)tvps->props, tvp, + tvps->num * sizeof(struct dtv_property))) { err = -EFAULT; goto out; } @@ -2074,6 +2194,24 @@ static int dtv_set_frontend(struct dvb_frontend *fe) case SYS_DVBC_ANNEX_C: rolloff = 113; break; + case SYS_DVBS: + case SYS_TURBO: + case SYS_ISDBS: + rolloff = 135; + break; + case SYS_DVBS2: + switch (c->rolloff) { + case ROLLOFF_20: + rolloff = 120; + break; + case ROLLOFF_25: + rolloff = 125; + break; + default: + case ROLLOFF_35: + rolloff = 135; + } + break; default: break; } @@ -2422,7 +2560,7 @@ static int dvb_frontend_open(struct inode *inode, struct file *file) int ret; dev_dbg(fe->dvb->device, "%s:\n", __func__); - if (fepriv->exit == DVB_FE_DEVICE_REMOVED) + if (fe->exit == DVB_FE_DEVICE_REMOVED) return -ENODEV; if (adapter->mfe_shared) { @@ -2527,7 +2665,7 @@ static int dvb_frontend_release(struct inode *inode, struct file *file) if (dvbdev->users == -1) { wake_up(&fepriv->wait_queue); - if (fepriv->exit != DVB_FE_NO_EXIT) + if (fe->exit != DVB_FE_NO_EXIT) wake_up(&dvbdev->wait_queue); if (fe->ops.ts_bus_ctrl) fe->ops.ts_bus_ctrl(fe, 0); @@ -2552,7 +2690,9 @@ int dvb_frontend_suspend(struct dvb_frontend *fe) dev_dbg(fe->dvb->device, "%s: adap=%d fe=%d\n", __func__, fe->dvb->num, fe->id); - if (fe->ops.tuner_ops.sleep) + if (fe->ops.tuner_ops.suspend) + ret = fe->ops.tuner_ops.suspend(fe); + else if (fe->ops.tuner_ops.sleep) ret = fe->ops.tuner_ops.sleep(fe); if (fe->ops.sleep) @@ -2570,12 +2710,16 @@ int dvb_frontend_resume(struct dvb_frontend *fe) dev_dbg(fe->dvb->device, "%s: adap=%d fe=%d\n", __func__, fe->dvb->num, fe->id); + fe->exit = DVB_FE_DEVICE_RESUME; if (fe->ops.init) ret = fe->ops.init(fe); - if (fe->ops.tuner_ops.init) + if (fe->ops.tuner_ops.resume) + ret = fe->ops.tuner_ops.resume(fe); + else if (fe->ops.tuner_ops.init) ret = fe->ops.tuner_ops.init(fe); + fe->exit = DVB_FE_NO_EXIT; fepriv->state = FESTATE_RETUNE; dvb_frontend_wakeup(fe); @@ -2587,11 +2731,14 @@ int dvb_register_frontend(struct dvb_adapter* dvb, struct dvb_frontend* fe) { struct dvb_frontend_private *fepriv; - static const struct dvb_device dvbdev_template = { + const struct dvb_device dvbdev_template = { .users = ~0, .writers = 1, .readers = (~0)-1, .fops = &dvb_frontend_fops, +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + .name = fe->ops.info.name, +#endif .kernel_ioctl = dvb_frontend_ioctl }; @@ -2664,20 +2811,20 @@ void dvb_frontend_detach(struct dvb_frontend* fe) if (fe->ops.release_sec) { fe->ops.release_sec(fe); - symbol_put_addr(fe->ops.release_sec); + dvb_detach(fe->ops.release_sec); } if (fe->ops.tuner_ops.release) { fe->ops.tuner_ops.release(fe); - symbol_put_addr(fe->ops.tuner_ops.release); + dvb_detach(fe->ops.tuner_ops.release); } if (fe->ops.analog_ops.release) { fe->ops.analog_ops.release(fe); - symbol_put_addr(fe->ops.analog_ops.release); + dvb_detach(fe->ops.analog_ops.release); } ptr = (void*)fe->ops.release; if (ptr) { fe->ops.release(fe); - symbol_put_addr(ptr); + dvb_detach(ptr); } } #else diff --git a/drivers/media/dvb-core/dvb_frontend.h b/drivers/media/dvb-core/dvb_frontend.h index 371b6caf486cad..816269e5f7068b 100644 --- a/drivers/media/dvb-core/dvb_frontend.h +++ b/drivers/media/dvb-core/dvb_frontend.h @@ -201,6 +201,8 @@ struct dvb_tuner_ops { int (*release)(struct dvb_frontend *fe); int (*init)(struct dvb_frontend *fe); int (*sleep)(struct dvb_frontend *fe); + int (*suspend)(struct dvb_frontend *fe); + int (*resume)(struct dvb_frontend *fe); /** This is for simple PLLs - set all parameters in one go. */ int (*set_params)(struct dvb_frontend *fe); @@ -405,6 +407,11 @@ struct dtv_frontend_properties { struct dtv_fe_stats block_count; }; +#define DVB_FE_NO_EXIT 0 +#define DVB_FE_NORMAL_EXIT 1 +#define DVB_FE_DEVICE_REMOVED 2 +#define DVB_FE_DEVICE_RESUME 3 + struct dvb_frontend { struct dvb_frontend_ops ops; struct dvb_adapter *dvb; @@ -418,6 +425,7 @@ struct dvb_frontend { #define DVB_FRONTEND_COMPONENT_DEMOD 1 int (*callback)(void *adapter_priv, int component, int cmd, int arg); int id; + unsigned int exit; }; extern int dvb_register_frontend(struct dvb_adapter *dvb, diff --git a/drivers/media/dvb-core/dvb_net.c b/drivers/media/dvb-core/dvb_net.c index f91c80c0e9ec08..e7e46c857fec8a 100644 --- a/drivers/media/dvb-core/dvb_net.c +++ b/drivers/media/dvb-core/dvb_net.c @@ -68,13 +68,6 @@ #include "dvb_demux.h" #include "dvb_net.h" -static int dvb_net_debug; -module_param(dvb_net_debug, int, 0444); -MODULE_PARM_DESC(dvb_net_debug, "enable debug messages"); - -#define dprintk(x...) do { if (dvb_net_debug) printk(x); } while (0) - - static inline __u32 iov_crc32( __u32 c, struct kvec *iov, unsigned int cnt ) { unsigned int j; @@ -90,36 +83,9 @@ static inline __u32 iov_crc32( __u32 c, struct kvec *iov, unsigned int cnt ) #ifdef ULE_DEBUG -#define MAC_ADDR_PRINTFMT "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" -#define MAX_ADDR_PRINTFMT_ARGS(macap) (macap)[0],(macap)[1],(macap)[2],(macap)[3],(macap)[4],(macap)[5] - -#define isprint(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) - -static void hexdump( const unsigned char *buf, unsigned short len ) +static void hexdump(const unsigned char *buf, unsigned short len) { - char str[80], octet[10]; - int ofs, i, l; - - for (ofs = 0; ofs < len; ofs += 16) { - sprintf( str, "%03d: ", ofs ); - - for (i = 0; i < 16; i++) { - if ((i + ofs) < len) - sprintf( octet, "%02x ", buf[ofs + i] ); - else - strcpy( octet, " " ); - - strcat( str, octet ); - } - strcat( str, " " ); - l = strlen( str ); - - for (i = 0; (i < 16) && ((i + ofs) < len); i++) - str[l++] = isprint( buf[ofs + i] ) ? buf[ofs + i] : '.'; - - str[l] = '\0'; - printk( KERN_WARNING "%s\n", str ); - } + print_hex_dump_debug("", DUMP_PREFIX_OFFSET, 16, 1, buf, len, true); } #endif @@ -179,7 +145,7 @@ static __be16 dvb_net_eth_type_trans(struct sk_buff *skb, eth = eth_hdr(skb); if (*eth->h_dest & 1) { - if(memcmp(eth->h_dest,dev->broadcast, ETH_ALEN)==0) + if(ether_addr_equal(eth->h_dest,dev->broadcast)) skb->pkt_type=PACKET_BROADCAST; else skb->pkt_type=PACKET_MULTICAST; @@ -315,9 +281,9 @@ static int handle_ule_extensions( struct dvb_net_priv *p ) return l; /* Stop extension header processing and discard SNDU. */ total_ext_len += l; #ifdef ULE_DEBUG - dprintk("handle_ule_extensions: ule_next_hdr=%p, ule_sndu_type=%i, " - "l=%i, total_ext_len=%i\n", p->ule_next_hdr, - (int) p->ule_sndu_type, l, total_ext_len); + pr_debug("ule_next_hdr=%p, ule_sndu_type=%i, l=%i, total_ext_len=%i\n", + p->ule_next_hdr, (int)p->ule_sndu_type, + l, total_ext_len); #endif } while (p->ule_sndu_type < ETH_P_802_3_MIN); @@ -379,7 +345,9 @@ static void dvb_net_ule( struct net_device *dev, const u8 *buf, size_t buf_len ) /* Check TS error conditions: sync_byte, transport_error_indicator, scrambling_control . */ if ((ts[0] != TS_SYNC) || (ts[1] & TS_TEI) || ((ts[3] & TS_SC) != 0)) { printk(KERN_WARNING "%lu: Invalid TS cell: SYNC %#x, TEI %u, SC %#x.\n", - priv->ts_count, ts[0], ts[1] & TS_TEI >> 7, ts[3] & 0xC0 >> 6); + priv->ts_count, ts[0], + (ts[1] & TS_TEI) >> 7, + (ts[3] & TS_SC) >> 6); /* Drop partly decoded SNDU, reset state, resync on PUSI. */ if (priv->ule_skb) { @@ -674,11 +642,13 @@ static void dvb_net_ule( struct net_device *dev, const u8 *buf, size_t buf_len ) if (priv->rx_mode != RX_MODE_PROMISC) { if (priv->ule_skb->data[0] & 0x01) { /* multicast or broadcast */ - if (memcmp(priv->ule_skb->data, bc_addr, ETH_ALEN)) { + if (!ether_addr_equal(priv->ule_skb->data, bc_addr)) { /* multicast */ if (priv->rx_mode == RX_MODE_MULTI) { int i; - for(i = 0; i < priv->multi_num && memcmp(priv->ule_skb->data, priv->multi_macs[i], ETH_ALEN); i++) + for(i = 0; i < priv->multi_num && + !ether_addr_equal(priv->ule_skb->data, + priv->multi_macs[i]); i++) ; if (i == priv->multi_num) drop = 1; @@ -688,7 +658,7 @@ static void dvb_net_ule( struct net_device *dev, const u8 *buf, size_t buf_len ) } /* else: broadcast */ } - else if (memcmp(priv->ule_skb->data, dev->dev_addr, ETH_ALEN)) + else if (!ether_addr_equal(priv->ule_skb->data, dev->dev_addr)) drop = 1; /* else: destination address matches the MAC address of our receiver device */ } @@ -696,8 +666,8 @@ static void dvb_net_ule( struct net_device *dev, const u8 *buf, size_t buf_len ) if (drop) { #ifdef ULE_DEBUG - dprintk("Dropping SNDU: MAC destination address does not match: dest addr: "MAC_ADDR_PRINTFMT", dev addr: "MAC_ADDR_PRINTFMT"\n", - MAX_ADDR_PRINTFMT_ARGS(priv->ule_skb->data), MAX_ADDR_PRINTFMT_ARGS(dev->dev_addr)); + netdev_dbg(dev, "Dropping SNDU: MAC destination address does not match: dest addr: %pM, dev addr: %pM\n", + priv->ule_skb->data, dev->dev_addr); #endif dev_kfree_skb(priv->ule_skb); goto sndu_done; @@ -960,8 +930,7 @@ static int dvb_net_filter_sec_set(struct net_device *dev, (*secfilter)->filter_mask[10] = mac_mask[1]; (*secfilter)->filter_mask[11]=mac_mask[0]; - dprintk("%s: filter mac=%pM\n", dev->name, mac); - dprintk("%s: filter mask=%pM\n", dev->name, mac_mask); + netdev_dbg(dev, "filter mac=%pM mask=%pM\n", mac, mac_mask); return 0; } @@ -973,7 +942,7 @@ static int dvb_net_feed_start(struct net_device *dev) struct dmx_demux *demux = priv->demux; unsigned char *mac = (unsigned char *) dev->dev_addr; - dprintk("%s: rx_mode %i\n", __func__, priv->rx_mode); + netdev_dbg(dev, "rx_mode %i\n", priv->rx_mode); mutex_lock(&priv->mutex); if (priv->tsfeed || priv->secfeed || priv->secfilter || priv->multi_secfilter[0]) printk("%s: BUG %d\n", __func__, __LINE__); @@ -983,7 +952,7 @@ static int dvb_net_feed_start(struct net_device *dev) priv->tsfeed = NULL; if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) { - dprintk("%s: alloc secfeed\n", __func__); + netdev_dbg(dev, "alloc secfeed\n"); ret=demux->allocate_section_feed(demux, &priv->secfeed, dvb_net_sec_callback); if (ret<0) { @@ -1001,38 +970,38 @@ static int dvb_net_feed_start(struct net_device *dev) } if (priv->rx_mode != RX_MODE_PROMISC) { - dprintk("%s: set secfilter\n", __func__); + netdev_dbg(dev, "set secfilter\n"); dvb_net_filter_sec_set(dev, &priv->secfilter, mac, mask_normal); } switch (priv->rx_mode) { case RX_MODE_MULTI: for (i = 0; i < priv->multi_num; i++) { - dprintk("%s: set multi_secfilter[%d]\n", __func__, i); + netdev_dbg(dev, "set multi_secfilter[%d]\n", i); dvb_net_filter_sec_set(dev, &priv->multi_secfilter[i], priv->multi_macs[i], mask_normal); } break; case RX_MODE_ALL_MULTI: priv->multi_num=1; - dprintk("%s: set multi_secfilter[0]\n", __func__); + netdev_dbg(dev, "set multi_secfilter[0]\n"); dvb_net_filter_sec_set(dev, &priv->multi_secfilter[0], mac_allmulti, mask_allmulti); break; case RX_MODE_PROMISC: priv->multi_num=0; - dprintk("%s: set secfilter\n", __func__); + netdev_dbg(dev, "set secfilter\n"); dvb_net_filter_sec_set(dev, &priv->secfilter, mac, mask_promisc); break; } - dprintk("%s: start filtering\n", __func__); + netdev_dbg(dev, "start filtering\n"); priv->secfeed->start_filtering(priv->secfeed); } else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) { struct timespec timeout = { 0, 10000000 }; // 10 msec /* we have payloads encapsulated in TS */ - dprintk("%s: alloc tsfeed\n", __func__); + netdev_dbg(dev, "alloc tsfeed\n"); ret = demux->allocate_ts_feed(demux, &priv->tsfeed, dvb_net_ts_callback); if (ret < 0) { printk("%s: could not allocate ts feed\n", dev->name); @@ -1056,7 +1025,7 @@ static int dvb_net_feed_start(struct net_device *dev) goto error; } - dprintk("%s: start filtering\n", __func__); + netdev_dbg(dev, "start filtering\n"); priv->tsfeed->start_filtering(priv->tsfeed); } else ret = -EINVAL; @@ -1071,17 +1040,16 @@ static int dvb_net_feed_stop(struct net_device *dev) struct dvb_net_priv *priv = netdev_priv(dev); int i, ret = 0; - dprintk("%s\n", __func__); mutex_lock(&priv->mutex); if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) { if (priv->secfeed) { if (priv->secfeed->is_filtering) { - dprintk("%s: stop secfeed\n", __func__); + netdev_dbg(dev, "stop secfeed\n"); priv->secfeed->stop_filtering(priv->secfeed); } if (priv->secfilter) { - dprintk("%s: release secfilter\n", __func__); + netdev_dbg(dev, "release secfilter\n"); priv->secfeed->release_filter(priv->secfeed, priv->secfilter); priv->secfilter=NULL; @@ -1089,8 +1057,8 @@ static int dvb_net_feed_stop(struct net_device *dev) for (i=0; imulti_num; i++) { if (priv->multi_secfilter[i]) { - dprintk("%s: release multi_filter[%d]\n", - __func__, i); + netdev_dbg(dev, "release multi_filter[%d]\n", + i); priv->secfeed->release_filter(priv->secfeed, priv->multi_secfilter[i]); priv->multi_secfilter[i] = NULL; @@ -1104,7 +1072,7 @@ static int dvb_net_feed_stop(struct net_device *dev) } else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) { if (priv->tsfeed) { if (priv->tsfeed->is_filtering) { - dprintk("%s: stop tsfeed\n", __func__); + netdev_dbg(dev, "stop tsfeed\n"); priv->tsfeed->stop_filtering(priv->tsfeed); } priv->demux->release_ts_feed(priv->demux, priv->tsfeed); @@ -1144,16 +1112,16 @@ static void wq_set_multicast_list (struct work_struct *work) netif_addr_lock_bh(dev); if (dev->flags & IFF_PROMISC) { - dprintk("%s: promiscuous mode\n", dev->name); + netdev_dbg(dev, "promiscuous mode\n"); priv->rx_mode = RX_MODE_PROMISC; } else if ((dev->flags & IFF_ALLMULTI)) { - dprintk("%s: allmulti mode\n", dev->name); + netdev_dbg(dev, "allmulti mode\n"); priv->rx_mode = RX_MODE_ALL_MULTI; } else if (!netdev_mc_empty(dev)) { struct netdev_hw_addr *ha; - dprintk("%s: set_mc_list, %d entries\n", - dev->name, netdev_mc_count(dev)); + netdev_dbg(dev, "set_mc_list, %d entries\n", + netdev_mc_count(dev)); priv->rx_mode = RX_MODE_MULTI; priv->multi_num = 0; @@ -1493,14 +1461,16 @@ static const struct file_operations dvb_net_fops = { .llseek = noop_llseek, }; -static struct dvb_device dvbdev_net = { +static const struct dvb_device dvbdev_net = { .priv = NULL, .users = 1, .writers = 1, +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + .name = "dvb-net", +#endif .fops = &dvb_net_fops, }; - void dvb_net_release (struct dvb_net *dvbnet) { int i; diff --git a/drivers/media/dvb-core/dvb_ringbuffer.c b/drivers/media/dvb-core/dvb_ringbuffer.c index a5712cd7c65ff0..1100e98a7b1d36 100644 --- a/drivers/media/dvb-core/dvb_ringbuffer.c +++ b/drivers/media/dvb-core/dvb_ringbuffer.c @@ -166,6 +166,31 @@ ssize_t dvb_ringbuffer_write(struct dvb_ringbuffer *rbuf, const u8 *buf, size_t return len; } +ssize_t dvb_ringbuffer_write_user(struct dvb_ringbuffer *rbuf, + const u8 __user *buf, size_t len) +{ + int status; + size_t todo = len; + size_t split; + + split = (rbuf->pwrite + len > rbuf->size) ? rbuf->size - rbuf->pwrite : 0; + + if (split > 0) { + status = copy_from_user(rbuf->data+rbuf->pwrite, buf, split); + if (status) + return len - todo; + buf += split; + todo -= split; + rbuf->pwrite = 0; + } + status = copy_from_user(rbuf->data+rbuf->pwrite, buf, todo); + if (status) + return len - todo; + rbuf->pwrite = (rbuf->pwrite + todo) % rbuf->size; + + return len; +} + ssize_t dvb_ringbuffer_pkt_write(struct dvb_ringbuffer *rbuf, u8* buf, size_t len) { int status; @@ -297,3 +322,4 @@ EXPORT_SYMBOL(dvb_ringbuffer_flush_spinlock_wakeup); EXPORT_SYMBOL(dvb_ringbuffer_read_user); EXPORT_SYMBOL(dvb_ringbuffer_read); EXPORT_SYMBOL(dvb_ringbuffer_write); +EXPORT_SYMBOL(dvb_ringbuffer_write_user); diff --git a/drivers/media/dvb-core/dvb_ringbuffer.h b/drivers/media/dvb-core/dvb_ringbuffer.h index 41f04dae69b618..9e1e11b7c39cb2 100644 --- a/drivers/media/dvb-core/dvb_ringbuffer.h +++ b/drivers/media/dvb-core/dvb_ringbuffer.h @@ -133,6 +133,8 @@ extern void dvb_ringbuffer_read(struct dvb_ringbuffer *rbuf, */ extern ssize_t dvb_ringbuffer_write(struct dvb_ringbuffer *rbuf, const u8 *buf, size_t len); +extern ssize_t dvb_ringbuffer_write_user(struct dvb_ringbuffer *rbuf, + const u8 __user *buf, size_t len); /** diff --git a/drivers/media/dvb-core/dvbdev.c b/drivers/media/dvb-core/dvbdev.c index 401ef64f92c66a..13bb57f0457f7f 100644 --- a/drivers/media/dvb-core/dvbdev.c +++ b/drivers/media/dvb-core/dvbdev.c @@ -74,22 +74,15 @@ static int dvb_device_open(struct inode *inode, struct file *file) if (dvbdev && dvbdev->fops) { int err = 0; - const struct file_operations *old_fops; + const struct file_operations *new_fops; - file->private_data = dvbdev; - old_fops = file->f_op; - file->f_op = fops_get(dvbdev->fops); - if (file->f_op == NULL) { - file->f_op = old_fops; + new_fops = fops_get(dvbdev->fops); + if (!new_fops) goto fail; - } - if(file->f_op->open) + file->private_data = dvbdev; + replace_fops(file, new_fops); + if (file->f_op->open) err = file->f_op->open(inode,file); - if (err) { - fops_put(file->f_op); - file->f_op = fops_get(old_fops); - } - fops_put(old_fops); up_read(&minor_rwsem); mutex_unlock(&dvbdev_mutex); return err; @@ -187,6 +180,93 @@ static int dvbdev_get_free_id (struct dvb_adapter *adap, int type) return -ENFILE; } +static void dvb_register_media_device(struct dvb_device *dvbdev, + int type, int minor) +{ +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + int ret = 0, npads; + + if (!dvbdev->adapter->mdev) + return; + + dvbdev->entity = kzalloc(sizeof(*dvbdev->entity), GFP_KERNEL); + if (!dvbdev->entity) + return; + + dvbdev->entity->info.dev.major = DVB_MAJOR; + dvbdev->entity->info.dev.minor = minor; + dvbdev->entity->name = dvbdev->name; + + switch (type) { + case DVB_DEVICE_CA: + case DVB_DEVICE_DEMUX: + case DVB_DEVICE_FRONTEND: + npads = 2; + break; + case DVB_DEVICE_NET: + npads = 0; + break; + default: + npads = 1; + } + + if (npads) { + dvbdev->pads = kcalloc(npads, sizeof(*dvbdev->pads), + GFP_KERNEL); + if (!dvbdev->pads) { + kfree(dvbdev->entity); + return; + } + } + + switch (type) { + case DVB_DEVICE_FRONTEND: + dvbdev->entity->type = MEDIA_ENT_T_DEVNODE_DVB_FE; + dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK; + dvbdev->pads[1].flags = MEDIA_PAD_FL_SOURCE; + break; + case DVB_DEVICE_DEMUX: + dvbdev->entity->type = MEDIA_ENT_T_DEVNODE_DVB_DEMUX; + dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK; + dvbdev->pads[1].flags = MEDIA_PAD_FL_SOURCE; + break; + case DVB_DEVICE_DVR: + dvbdev->entity->type = MEDIA_ENT_T_DEVNODE_DVB_DVR; + dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK; + break; + case DVB_DEVICE_CA: + dvbdev->entity->type = MEDIA_ENT_T_DEVNODE_DVB_CA; + dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK; + dvbdev->pads[1].flags = MEDIA_PAD_FL_SOURCE; + break; + case DVB_DEVICE_NET: + dvbdev->entity->type = MEDIA_ENT_T_DEVNODE_DVB_NET; + break; + default: + kfree(dvbdev->entity); + dvbdev->entity = NULL; + return; + } + + if (npads) + ret = media_entity_init(dvbdev->entity, npads, dvbdev->pads, 0); + if (!ret) + ret = media_device_register_entity(dvbdev->adapter->mdev, + dvbdev->entity); + if (ret < 0) { + printk(KERN_ERR + "%s: media_device_register_entity failed for %s\n", + __func__, dvbdev->entity->name); + kfree(dvbdev->pads); + kfree(dvbdev->entity); + dvbdev->entity = NULL; + return; + } + + printk(KERN_DEBUG "%s: media device '%s' registered.\n", + __func__, dvbdev->entity->name); +#endif +} int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev, const struct dvb_device *template, void *priv, int type) @@ -265,10 +345,11 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev, __func__, adap->num, dnames[type], id, PTR_ERR(clsdev)); return PTR_ERR(clsdev); } - dprintk(KERN_DEBUG "DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n", adap->num, dnames[type], id, minor, minor); + dvb_register_media_device(dvbdev, type, minor); + return 0; } EXPORT_SYMBOL(dvb_register_device); @@ -285,12 +366,66 @@ void dvb_unregister_device(struct dvb_device *dvbdev) device_destroy(dvb_class, MKDEV(DVB_MAJOR, dvbdev->minor)); +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + if (dvbdev->entity) { + media_device_unregister_entity(dvbdev->entity); + kfree(dvbdev->entity); + kfree(dvbdev->pads); + } +#endif + list_del (&dvbdev->list_head); kfree (dvbdev->fops); kfree (dvbdev); } EXPORT_SYMBOL(dvb_unregister_device); + +#ifdef CONFIG_MEDIA_CONTROLLER_DVB +void dvb_create_media_graph(struct dvb_adapter *adap) +{ + struct media_device *mdev = adap->mdev; + struct media_entity *entity, *tuner = NULL, *fe = NULL; + struct media_entity *demux = NULL, *dvr = NULL, *ca = NULL; + + if (!mdev) + return; + + media_device_for_each_entity(entity, mdev) { + switch (entity->type) { + case MEDIA_ENT_T_V4L2_SUBDEV_TUNER: + tuner = entity; + break; + case MEDIA_ENT_T_DEVNODE_DVB_FE: + fe = entity; + break; + case MEDIA_ENT_T_DEVNODE_DVB_DEMUX: + demux = entity; + break; + case MEDIA_ENT_T_DEVNODE_DVB_DVR: + dvr = entity; + break; + case MEDIA_ENT_T_DEVNODE_DVB_CA: + ca = entity; + break; + } + } + + if (tuner && fe) + media_entity_create_link(tuner, 0, fe, 0, 0); + + if (fe && demux) + media_entity_create_link(fe, 1, demux, 0, MEDIA_LNK_FL_ENABLED); + + if (demux && dvr) + media_entity_create_link(demux, 1, dvr, 0, MEDIA_LNK_FL_ENABLED); + + if (demux && ca) + media_entity_create_link(demux, 1, ca, 0, MEDIA_LNK_FL_ENABLED); +} +EXPORT_SYMBOL_GPL(dvb_create_media_graph); +#endif + static int dvbdev_check_free_adapter_num(int num) { struct list_head *entry; diff --git a/drivers/media/dvb-core/dvbdev.h b/drivers/media/dvb-core/dvbdev.h index 93a9470d3f0c7c..5ec34c31cca837 100644 --- a/drivers/media/dvb-core/dvbdev.h +++ b/drivers/media/dvb-core/dvbdev.h @@ -27,6 +27,7 @@ #include #include #include +#include #define DVB_MAJOR 212 @@ -71,6 +72,10 @@ struct dvb_adapter { int mfe_shared; /* indicates mutually exclusive frontends */ struct dvb_device *mfe_dvbdev; /* frontend device in use */ struct mutex mfe_lock; /* access lock for thread creation */ + +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + struct media_device *mdev; +#endif }; @@ -92,6 +97,15 @@ struct dvb_device { /* don't really need those !? -- FIXME: use video_usercopy */ int (*kernel_ioctl)(struct file *file, unsigned int cmd, void *arg); + /* Needed for media controller register/unregister */ +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + const char *name; + + /* Allocated and filled inside dvbdev.c */ + struct media_entity *entity; + struct media_pad *pads; +#endif + void *priv; }; @@ -109,6 +123,19 @@ extern int dvb_register_device (struct dvb_adapter *adap, extern void dvb_unregister_device (struct dvb_device *dvbdev); +#ifdef CONFIG_MEDIA_CONTROLLER_DVB +void dvb_create_media_graph(struct dvb_adapter *adap); +static inline void dvb_register_media_controller(struct dvb_adapter *adap, + struct media_device *mdev) +{ + adap->mdev = mdev; +} + +#else +static inline void dvb_create_media_graph(struct dvb_adapter *adap) {} +#define dvb_register_media_controller(a, b) {} +#endif + extern int dvb_generic_open (struct inode *inode, struct file *file); extern int dvb_generic_release (struct inode *inode, struct file *file); extern long dvb_generic_ioctl (struct file *file, @@ -136,11 +163,22 @@ extern int dvb_usercopy(struct file *file, unsigned int cmd, unsigned long arg, __r; \ }) +#define dvb_detach(FUNC) symbol_put_addr(FUNC) + #else #define dvb_attach(FUNCTION, ARGS...) ({ \ FUNCTION(ARGS); \ }) +#define dvb_detach(FUNC) {} + #endif +#define replace_fops(f, fops) \ + do { \ + struct file *__file = (f); \ + fops_put(__file->f_op); \ + BUG_ON(!(__file->f_op = (fops))); \ + } while(0) + #endif /* #ifndef _DVBDEV_H_ */ diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig index 0e2ec6f73b0525..c6fde8eace09f8 100644 --- a/drivers/media/dvb-frontends/Kconfig +++ b/drivers/media/dvb-frontends/Kconfig @@ -35,6 +35,13 @@ config DVB_STV6110x help A Silicon tuner that supports DVB-S and DVB-S2 modes +config DVB_M88DS3103 + tristate "Montage M88DS3103" + depends on DVB_CORE && I2C && I2C_MUX + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + comment "Multistandard (cable + terrestrial) frontends" depends on DVB_CORE @@ -432,6 +439,27 @@ config DVB_RTL2832 help Say Y when you want to support this frontend. +config DVB_RTL2832_SDR + tristate "Realtek RTL2832 SDR" + depends on DVB_CORE && I2C && I2C_MUX && VIDEO_V4L2 && MEDIA_SDR_SUPPORT && USB + select DVB_RTL2832 + select VIDEOBUF2_VMALLOC + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this SDR module. + +config DVB_SI2168 + tristate "Silicon Labs Si2168" + depends on DVB_CORE && I2C && I2C_MUX + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + +config DVB_AS102_FE + tristate + depends on DVB_CORE + default DVB_AS102 + comment "DVB-C (cable) frontends" depends on DVB_CORE @@ -679,6 +707,13 @@ config DVB_A8293 depends on DVB_CORE && I2C default m if !MEDIA_SUBDRV_AUTOSELECT +config DVB_SP2 + tristate "CIMaX SP2" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + CIMaX SP2/SP2HF Common Interface module. + config DVB_LGS8GL5 tristate "Silicon Legend LGS-8GL5 demodulator (OFDM)" depends on DVB_CORE && I2C diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile index cebc0faffab590..c0471ad3589195 100644 --- a/drivers/media/dvb-frontends/Makefile +++ b/drivers/media/dvb-frontends/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_DVB_DUMMY_FE) += dvb_dummy_fe.o obj-$(CONFIG_DVB_AF9013) += af9013.o obj-$(CONFIG_DVB_CX24116) += cx24116.o obj-$(CONFIG_DVB_SI21XX) += si21xx.o +obj-$(CONFIG_DVB_SI2168) += si2168.o obj-$(CONFIG_DVB_STV0288) += stv0288.o obj-$(CONFIG_DVB_STB6000) += stb6000.o obj-$(CONFIG_DVB_S921) += s921.o @@ -84,6 +85,7 @@ obj-$(CONFIG_DVB_STV6110) += stv6110.o obj-$(CONFIG_DVB_STV0900) += stv0900.o obj-$(CONFIG_DVB_STV090x) += stv090x.o obj-$(CONFIG_DVB_STV6110x) += stv6110x.o +obj-$(CONFIG_DVB_M88DS3103) += m88ds3103.o obj-$(CONFIG_DVB_ISL6423) += isl6423.o obj-$(CONFIG_DVB_EC100) += ec100.o obj-$(CONFIG_DVB_HD29L2) += hd29l2.o @@ -98,6 +100,7 @@ obj-$(CONFIG_DVB_DRXK) += drxk.o obj-$(CONFIG_DVB_TDA18271C2DD) += tda18271c2dd.o obj-$(CONFIG_DVB_IT913X_FE) += it913x-fe.o obj-$(CONFIG_DVB_A8293) += a8293.o +obj-$(CONFIG_DVB_SP2) += sp2.o obj-$(CONFIG_DVB_TDA10071) += tda10071.o obj-$(CONFIG_DVB_RTL2830) += rtl2830.o obj-$(CONFIG_DVB_RTL2832) += rtl2832.o diff --git a/drivers/media/dvb-frontends/m88ds3103.c b/drivers/media/dvb-frontends/m88ds3103.c new file mode 100644 index 00000000000000..ba4ee0b4883440 --- /dev/null +++ b/drivers/media/dvb-frontends/m88ds3103.c @@ -0,0 +1,1506 @@ +/* + * Montage M88DS3103/M88RS6000 demodulator driver + * + * Copyright (C) 2013 Antti Palosaari + * + * 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. + */ + +#include "m88ds3103_priv.h" + +static struct dvb_frontend_ops m88ds3103_ops; + +/* write multiple registers */ +static int m88ds3103_wr_regs(struct m88ds3103_priv *priv, + u8 reg, const u8 *val, int len) +{ +#define MAX_WR_LEN 32 +#define MAX_WR_XFER_LEN (MAX_WR_LEN + 1) + int ret; + u8 buf[MAX_WR_XFER_LEN]; + struct i2c_msg msg[1] = { + { + .addr = priv->cfg->i2c_addr, + .flags = 0, + .len = 1 + len, + .buf = buf, + } + }; + + if (WARN_ON(len > MAX_WR_LEN)) + return -EINVAL; + + buf[0] = reg; + memcpy(&buf[1], val, len); + + mutex_lock(&priv->i2c_mutex); + ret = i2c_transfer(priv->i2c, msg, 1); + mutex_unlock(&priv->i2c_mutex); + if (ret == 1) { + ret = 0; + } else { + dev_warn(&priv->i2c->dev, + "%s: i2c wr failed=%d reg=%02x len=%d\n", + KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + + return ret; +} + +/* read multiple registers */ +static int m88ds3103_rd_regs(struct m88ds3103_priv *priv, + u8 reg, u8 *val, int len) +{ +#define MAX_RD_LEN 3 +#define MAX_RD_XFER_LEN (MAX_RD_LEN) + int ret; + u8 buf[MAX_RD_XFER_LEN]; + struct i2c_msg msg[2] = { + { + .addr = priv->cfg->i2c_addr, + .flags = 0, + .len = 1, + .buf = ®, + }, { + .addr = priv->cfg->i2c_addr, + .flags = I2C_M_RD, + .len = len, + .buf = buf, + } + }; + + if (WARN_ON(len > MAX_RD_LEN)) + return -EINVAL; + + mutex_lock(&priv->i2c_mutex); + ret = i2c_transfer(priv->i2c, msg, 2); + mutex_unlock(&priv->i2c_mutex); + if (ret == 2) { + memcpy(val, buf, len); + ret = 0; + } else { + dev_warn(&priv->i2c->dev, + "%s: i2c rd failed=%d reg=%02x len=%d\n", + KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + + return ret; +} + +/* write single register */ +static int m88ds3103_wr_reg(struct m88ds3103_priv *priv, u8 reg, u8 val) +{ + return m88ds3103_wr_regs(priv, reg, &val, 1); +} + +/* read single register */ +static int m88ds3103_rd_reg(struct m88ds3103_priv *priv, u8 reg, u8 *val) +{ + return m88ds3103_rd_regs(priv, reg, val, 1); +} + +/* write single register with mask */ +static int m88ds3103_wr_reg_mask(struct m88ds3103_priv *priv, + u8 reg, u8 val, u8 mask) +{ + int ret; + u8 u8tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = m88ds3103_rd_regs(priv, reg, &u8tmp, 1); + if (ret) + return ret; + + val &= mask; + u8tmp &= ~mask; + val |= u8tmp; + } + + return m88ds3103_wr_regs(priv, reg, &val, 1); +} + +/* read single register with mask */ +static int m88ds3103_rd_reg_mask(struct m88ds3103_priv *priv, + u8 reg, u8 *val, u8 mask) +{ + int ret, i; + u8 u8tmp; + + ret = m88ds3103_rd_regs(priv, reg, &u8tmp, 1); + if (ret) + return ret; + + u8tmp &= mask; + + /* find position of the first bit */ + for (i = 0; i < 8; i++) { + if ((mask >> i) & 0x01) + break; + } + *val = u8tmp >> i; + + return 0; +} + +/* write reg val table using reg addr auto increment */ +static int m88ds3103_wr_reg_val_tab(struct m88ds3103_priv *priv, + const struct m88ds3103_reg_val *tab, int tab_len) +{ + int ret, i, j; + u8 buf[83]; + + dev_dbg(&priv->i2c->dev, "%s: tab_len=%d\n", __func__, tab_len); + + if (tab_len > 86) { + ret = -EINVAL; + goto err; + } + + for (i = 0, j = 0; i < tab_len; i++, j++) { + buf[j] = tab[i].val; + + if (i == tab_len - 1 || tab[i].reg != tab[i + 1].reg - 1 || + !((j + 1) % (priv->cfg->i2c_wr_max - 1))) { + ret = m88ds3103_wr_regs(priv, tab[i].reg - j, buf, j + 1); + if (ret) + goto err; + + j = -1; + } + } + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + u8 u8tmp; + + *status = 0; + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + switch (c->delivery_system) { + case SYS_DVBS: + ret = m88ds3103_rd_reg_mask(priv, 0xd1, &u8tmp, 0x07); + if (ret) + goto err; + + if (u8tmp == 0x07) + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | + FE_HAS_LOCK; + break; + case SYS_DVBS2: + ret = m88ds3103_rd_reg_mask(priv, 0x0d, &u8tmp, 0x8f); + if (ret) + goto err; + + if (u8tmp == 0x8f) + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | + FE_HAS_LOCK; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", + __func__); + ret = -EINVAL; + goto err; + } + + priv->fe_status = *status; + + dev_dbg(&priv->i2c->dev, "%s: lock=%02x status=%02x\n", + __func__, u8tmp, *status); + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_set_frontend(struct dvb_frontend *fe) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, len; + const struct m88ds3103_reg_val *init; + u8 u8tmp, u8tmp1 = 0, u8tmp2 = 0; /* silence compiler warning */ + u8 buf[3]; + u16 u16tmp, divide_ratio = 0; + u32 tuner_frequency, target_mclk; + s32 s32tmp; + + dev_dbg(&priv->i2c->dev, + "%s: delivery_system=%d modulation=%d frequency=%d symbol_rate=%d inversion=%d pilot=%d rolloff=%d\n", + __func__, c->delivery_system, + c->modulation, c->frequency, c->symbol_rate, + c->inversion, c->pilot, c->rolloff); + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + /* reset */ + ret = m88ds3103_wr_reg(priv, 0x07, 0x80); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x07, 0x00); + if (ret) + goto err; + + /* Disable demod clock path */ + if (priv->chip_id == M88RS6000_CHIP_ID) { + ret = m88ds3103_wr_reg(priv, 0x06, 0xe0); + if (ret) + goto err; + } + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) { + ret = fe->ops.tuner_ops.set_params(fe); + if (ret) + goto err; + } + + if (fe->ops.tuner_ops.get_frequency) { + ret = fe->ops.tuner_ops.get_frequency(fe, &tuner_frequency); + if (ret) + goto err; + } else { + /* + * Use nominal target frequency as tuner driver does not provide + * actual frequency used. Carrier offset calculation is not + * valid. + */ + tuner_frequency = c->frequency; + } + + /* select M88RS6000 demod main mclk and ts mclk from tuner die. */ + if (priv->chip_id == M88RS6000_CHIP_ID) { + if (c->symbol_rate > 45010000) + priv->mclk_khz = 110250; + else + priv->mclk_khz = 96000; + + if (c->delivery_system == SYS_DVBS) + target_mclk = 96000; + else + target_mclk = 144000; + + /* Enable demod clock path */ + ret = m88ds3103_wr_reg(priv, 0x06, 0x00); + if (ret) + goto err; + usleep_range(10000, 20000); + } else { + /* set M88DS3103 mclk and ts mclk. */ + priv->mclk_khz = 96000; + + switch (priv->cfg->ts_mode) { + case M88DS3103_TS_SERIAL: + case M88DS3103_TS_SERIAL_D7: + target_mclk = priv->cfg->ts_clk; + break; + case M88DS3103_TS_PARALLEL: + case M88DS3103_TS_CI: + if (c->delivery_system == SYS_DVBS) + target_mclk = 96000; + else { + if (c->symbol_rate < 18000000) + target_mclk = 96000; + else if (c->symbol_rate < 28000000) + target_mclk = 144000; + else + target_mclk = 192000; + } + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid ts_mode\n", + __func__); + ret = -EINVAL; + goto err; + } + + switch (target_mclk) { + case 96000: + u8tmp1 = 0x02; /* 0b10 */ + u8tmp2 = 0x01; /* 0b01 */ + break; + case 144000: + u8tmp1 = 0x00; /* 0b00 */ + u8tmp2 = 0x01; /* 0b01 */ + break; + case 192000: + u8tmp1 = 0x03; /* 0b11 */ + u8tmp2 = 0x00; /* 0b00 */ + break; + } + ret = m88ds3103_wr_reg_mask(priv, 0x22, u8tmp1 << 6, 0xc0); + if (ret) + goto err; + ret = m88ds3103_wr_reg_mask(priv, 0x24, u8tmp2 << 6, 0xc0); + if (ret) + goto err; + } + + ret = m88ds3103_wr_reg(priv, 0xb2, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x00, 0x01); + if (ret) + goto err; + + switch (c->delivery_system) { + case SYS_DVBS: + if (priv->chip_id == M88RS6000_CHIP_ID) { + len = ARRAY_SIZE(m88rs6000_dvbs_init_reg_vals); + init = m88rs6000_dvbs_init_reg_vals; + } else { + len = ARRAY_SIZE(m88ds3103_dvbs_init_reg_vals); + init = m88ds3103_dvbs_init_reg_vals; + } + break; + case SYS_DVBS2: + if (priv->chip_id == M88RS6000_CHIP_ID) { + len = ARRAY_SIZE(m88rs6000_dvbs2_init_reg_vals); + init = m88rs6000_dvbs2_init_reg_vals; + } else { + len = ARRAY_SIZE(m88ds3103_dvbs2_init_reg_vals); + init = m88ds3103_dvbs2_init_reg_vals; + } + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", + __func__); + ret = -EINVAL; + goto err; + } + + /* program init table */ + if (c->delivery_system != priv->delivery_system) { + ret = m88ds3103_wr_reg_val_tab(priv, init, len); + if (ret) + goto err; + } + + if (priv->chip_id == M88RS6000_CHIP_ID) { + if ((c->delivery_system == SYS_DVBS2) + && ((c->symbol_rate / 1000) <= 5000)) { + ret = m88ds3103_wr_reg(priv, 0xc0, 0x04); + if (ret) + goto err; + buf[0] = 0x09; + buf[1] = 0x22; + buf[2] = 0x88; + ret = m88ds3103_wr_regs(priv, 0x8a, buf, 3); + if (ret) + goto err; + } + ret = m88ds3103_wr_reg_mask(priv, 0x9d, 0x08, 0x08); + if (ret) + goto err; + ret = m88ds3103_wr_reg(priv, 0xf1, 0x01); + if (ret) + goto err; + ret = m88ds3103_wr_reg_mask(priv, 0x30, 0x80, 0x80); + if (ret) + goto err; + } + + switch (priv->cfg->ts_mode) { + case M88DS3103_TS_SERIAL: + u8tmp1 = 0x00; + u8tmp = 0x06; + break; + case M88DS3103_TS_SERIAL_D7: + u8tmp1 = 0x20; + u8tmp = 0x06; + break; + case M88DS3103_TS_PARALLEL: + u8tmp = 0x02; + break; + case M88DS3103_TS_CI: + u8tmp = 0x03; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid ts_mode\n", __func__); + ret = -EINVAL; + goto err; + } + + if (priv->cfg->ts_clk_pol) + u8tmp |= 0x40; + + /* TS mode */ + ret = m88ds3103_wr_reg(priv, 0xfd, u8tmp); + if (ret) + goto err; + + switch (priv->cfg->ts_mode) { + case M88DS3103_TS_SERIAL: + case M88DS3103_TS_SERIAL_D7: + ret = m88ds3103_wr_reg_mask(priv, 0x29, u8tmp1, 0x20); + if (ret) + goto err; + u8tmp1 = 0; + u8tmp2 = 0; + break; + default: + if (priv->cfg->ts_clk) { + divide_ratio = DIV_ROUND_UP(target_mclk, priv->cfg->ts_clk); + u8tmp1 = divide_ratio / 2; + u8tmp2 = DIV_ROUND_UP(divide_ratio, 2); + } + } + + dev_dbg(&priv->i2c->dev, + "%s: target_mclk=%d ts_clk=%d divide_ratio=%d\n", + __func__, target_mclk, priv->cfg->ts_clk, divide_ratio); + + u8tmp1--; + u8tmp2--; + /* u8tmp1[5:2] => fe[3:0], u8tmp1[1:0] => ea[7:6] */ + u8tmp1 &= 0x3f; + /* u8tmp2[5:0] => ea[5:0] */ + u8tmp2 &= 0x3f; + + ret = m88ds3103_rd_reg(priv, 0xfe, &u8tmp); + if (ret) + goto err; + + u8tmp = ((u8tmp & 0xf0) << 0) | u8tmp1 >> 2; + ret = m88ds3103_wr_reg(priv, 0xfe, u8tmp); + if (ret) + goto err; + + u8tmp = ((u8tmp1 & 0x03) << 6) | u8tmp2 >> 0; + ret = m88ds3103_wr_reg(priv, 0xea, u8tmp); + if (ret) + goto err; + + if (c->symbol_rate <= 3000000) + u8tmp = 0x20; + else if (c->symbol_rate <= 10000000) + u8tmp = 0x10; + else + u8tmp = 0x06; + + ret = m88ds3103_wr_reg(priv, 0xc3, 0x08); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xc8, u8tmp); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xc4, 0x08); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xc7, 0x00); + if (ret) + goto err; + + u16tmp = DIV_ROUND_CLOSEST((c->symbol_rate / 1000) << 15, priv->mclk_khz / 2); + buf[0] = (u16tmp >> 0) & 0xff; + buf[1] = (u16tmp >> 8) & 0xff; + ret = m88ds3103_wr_regs(priv, 0x61, buf, 2); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x4d, priv->cfg->spec_inv << 1, 0x02); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x30, priv->cfg->agc_inv << 4, 0x10); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x33, priv->cfg->agc); + if (ret) + goto err; + + dev_dbg(&priv->i2c->dev, "%s: carrier offset=%d\n", __func__, + (tuner_frequency - c->frequency)); + + s32tmp = 0x10000 * (tuner_frequency - c->frequency); + s32tmp = DIV_ROUND_CLOSEST(s32tmp, priv->mclk_khz); + if (s32tmp < 0) + s32tmp += 0x10000; + + buf[0] = (s32tmp >> 0) & 0xff; + buf[1] = (s32tmp >> 8) & 0xff; + ret = m88ds3103_wr_regs(priv, 0x5e, buf, 2); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x00, 0x00); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xb2, 0x00); + if (ret) + goto err; + + priv->delivery_system = c->delivery_system; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_init(struct dvb_frontend *fe) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret, len, remaining; + const struct firmware *fw = NULL; + u8 *fw_file; + u8 u8tmp; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + + /* set cold state by default */ + priv->warm = false; + + /* wake up device from sleep */ + ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x01, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x00, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x00, 0x10); + if (ret) + goto err; + + /* firmware status */ + ret = m88ds3103_rd_reg(priv, 0xb9, &u8tmp); + if (ret) + goto err; + + dev_dbg(&priv->i2c->dev, "%s: firmware=%02x\n", __func__, u8tmp); + + if (u8tmp) + goto skip_fw_download; + + /* global reset, global diseqc reset, golbal fec reset */ + ret = m88ds3103_wr_reg(priv, 0x07, 0xe0); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x07, 0x00); + if (ret) + goto err; + + /* cold state - try to download firmware */ + dev_info(&priv->i2c->dev, "%s: found a '%s' in cold state\n", + KBUILD_MODNAME, m88ds3103_ops.info.name); + + if (priv->chip_id == M88RS6000_CHIP_ID) + fw_file = M88RS6000_FIRMWARE; + else + fw_file = M88DS3103_FIRMWARE; + /* request the firmware, this will block and timeout */ + ret = request_firmware(&fw, fw_file, priv->i2c->dev.parent); + if (ret) { + dev_err(&priv->i2c->dev, "%s: firmare file '%s' not found\n", + KBUILD_MODNAME, fw_file); + goto err; + } + + dev_info(&priv->i2c->dev, "%s: downloading firmware from file '%s'\n", + KBUILD_MODNAME, fw_file); + + ret = m88ds3103_wr_reg(priv, 0xb2, 0x01); + if (ret) + goto error_fw_release; + + for (remaining = fw->size; remaining > 0; + remaining -= (priv->cfg->i2c_wr_max - 1)) { + len = remaining; + if (len > (priv->cfg->i2c_wr_max - 1)) + len = (priv->cfg->i2c_wr_max - 1); + + ret = m88ds3103_wr_regs(priv, 0xb0, + &fw->data[fw->size - remaining], len); + if (ret) { + dev_err(&priv->i2c->dev, + "%s: firmware download failed=%d\n", + KBUILD_MODNAME, ret); + goto error_fw_release; + } + } + + ret = m88ds3103_wr_reg(priv, 0xb2, 0x00); + if (ret) + goto error_fw_release; + + release_firmware(fw); + fw = NULL; + + ret = m88ds3103_rd_reg(priv, 0xb9, &u8tmp); + if (ret) + goto err; + + if (!u8tmp) { + dev_info(&priv->i2c->dev, "%s: firmware did not run\n", + KBUILD_MODNAME); + ret = -EFAULT; + goto err; + } + + dev_info(&priv->i2c->dev, "%s: found a '%s' in warm state\n", + KBUILD_MODNAME, m88ds3103_ops.info.name); + dev_info(&priv->i2c->dev, "%s: firmware version %X.%X\n", + KBUILD_MODNAME, (u8tmp >> 4) & 0xf, (u8tmp >> 0 & 0xf)); + +skip_fw_download: + /* warm state */ + priv->warm = true; + + return 0; + +error_fw_release: + release_firmware(fw); +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_sleep(struct dvb_frontend *fe) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret; + u8 u8tmp; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + + priv->delivery_system = SYS_UNDEFINED; + + /* TS Hi-Z */ + if (priv->chip_id == M88RS6000_CHIP_ID) + u8tmp = 0x29; + else + u8tmp = 0x27; + ret = m88ds3103_wr_reg_mask(priv, u8tmp, 0x00, 0x01); + if (ret) + goto err; + + /* sleep */ + ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x00, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x01, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x10, 0x10); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_get_frontend(struct dvb_frontend *fe) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + u8 buf[3]; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + + if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { + ret = -EAGAIN; + goto err; + } + + switch (c->delivery_system) { + case SYS_DVBS: + ret = m88ds3103_rd_reg(priv, 0xe0, &buf[0]); + if (ret) + goto err; + + ret = m88ds3103_rd_reg(priv, 0xe6, &buf[1]); + if (ret) + goto err; + + switch ((buf[0] >> 2) & 0x01) { + case 0: + c->inversion = INVERSION_OFF; + break; + case 1: + c->inversion = INVERSION_ON; + break; + } + + switch ((buf[1] >> 5) & 0x07) { + case 0: + c->fec_inner = FEC_7_8; + break; + case 1: + c->fec_inner = FEC_5_6; + break; + case 2: + c->fec_inner = FEC_3_4; + break; + case 3: + c->fec_inner = FEC_2_3; + break; + case 4: + c->fec_inner = FEC_1_2; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fec_inner\n", + __func__); + } + + c->modulation = QPSK; + + break; + case SYS_DVBS2: + ret = m88ds3103_rd_reg(priv, 0x7e, &buf[0]); + if (ret) + goto err; + + ret = m88ds3103_rd_reg(priv, 0x89, &buf[1]); + if (ret) + goto err; + + ret = m88ds3103_rd_reg(priv, 0xf2, &buf[2]); + if (ret) + goto err; + + switch ((buf[0] >> 0) & 0x0f) { + case 2: + c->fec_inner = FEC_2_5; + break; + case 3: + c->fec_inner = FEC_1_2; + break; + case 4: + c->fec_inner = FEC_3_5; + break; + case 5: + c->fec_inner = FEC_2_3; + break; + case 6: + c->fec_inner = FEC_3_4; + break; + case 7: + c->fec_inner = FEC_4_5; + break; + case 8: + c->fec_inner = FEC_5_6; + break; + case 9: + c->fec_inner = FEC_8_9; + break; + case 10: + c->fec_inner = FEC_9_10; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fec_inner\n", + __func__); + } + + switch ((buf[0] >> 5) & 0x01) { + case 0: + c->pilot = PILOT_OFF; + break; + case 1: + c->pilot = PILOT_ON; + break; + } + + switch ((buf[0] >> 6) & 0x07) { + case 0: + c->modulation = QPSK; + break; + case 1: + c->modulation = PSK_8; + break; + case 2: + c->modulation = APSK_16; + break; + case 3: + c->modulation = APSK_32; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid modulation\n", + __func__); + } + + switch ((buf[1] >> 7) & 0x01) { + case 0: + c->inversion = INVERSION_OFF; + break; + case 1: + c->inversion = INVERSION_ON; + break; + } + + switch ((buf[2] >> 0) & 0x03) { + case 0: + c->rolloff = ROLLOFF_35; + break; + case 1: + c->rolloff = ROLLOFF_25; + break; + case 2: + c->rolloff = ROLLOFF_20; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid rolloff\n", + __func__); + } + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", + __func__); + ret = -EINVAL; + goto err; + } + + ret = m88ds3103_rd_regs(priv, 0x6d, buf, 2); + if (ret) + goto err; + + c->symbol_rate = 1ull * ((buf[1] << 8) | (buf[0] << 0)) * + priv->mclk_khz * 1000 / 0x10000; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i, tmp; + u8 buf[3]; + u16 noise, signal; + u32 noise_tot, signal_tot; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + /* reports SNR in resolution of 0.1 dB */ + + /* more iterations for more accurate estimation */ + #define M88DS3103_SNR_ITERATIONS 3 + + switch (c->delivery_system) { + case SYS_DVBS: + tmp = 0; + + for (i = 0; i < M88DS3103_SNR_ITERATIONS; i++) { + ret = m88ds3103_rd_reg(priv, 0xff, &buf[0]); + if (ret) + goto err; + + tmp += buf[0]; + } + + /* use of one register limits max value to 15 dB */ + /* SNR(X) dB = 10 * ln(X) / ln(10) dB */ + tmp = DIV_ROUND_CLOSEST(tmp, 8 * M88DS3103_SNR_ITERATIONS); + if (tmp) + *snr = div_u64((u64) 100 * intlog2(tmp), intlog2(10)); + else + *snr = 0; + break; + case SYS_DVBS2: + noise_tot = 0; + signal_tot = 0; + + for (i = 0; i < M88DS3103_SNR_ITERATIONS; i++) { + ret = m88ds3103_rd_regs(priv, 0x8c, buf, 3); + if (ret) + goto err; + + noise = buf[1] << 6; /* [13:6] */ + noise |= buf[0] & 0x3f; /* [5:0] */ + noise >>= 2; + signal = buf[2] * buf[2]; + signal >>= 1; + + noise_tot += noise; + signal_tot += signal; + } + + noise = noise_tot / M88DS3103_SNR_ITERATIONS; + signal = signal_tot / M88DS3103_SNR_ITERATIONS; + + /* SNR(X) dB = 10 * log10(X) dB */ + if (signal > noise) { + tmp = signal / noise; + *snr = div_u64((u64) 100 * intlog10(tmp), (1 << 24)); + } else { + *snr = 0; + } + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", + __func__); + ret = -EINVAL; + goto err; + } + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + unsigned int utmp; + u8 buf[3], u8tmp; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + + switch (c->delivery_system) { + case SYS_DVBS: + ret = m88ds3103_wr_reg(priv, 0xf9, 0x04); + if (ret) + goto err; + + ret = m88ds3103_rd_reg(priv, 0xf8, &u8tmp); + if (ret) + goto err; + + if (!(u8tmp & 0x10)) { + u8tmp |= 0x10; + + ret = m88ds3103_rd_regs(priv, 0xf6, buf, 2); + if (ret) + goto err; + + priv->ber = (buf[1] << 8) | (buf[0] << 0); + + /* restart counters */ + ret = m88ds3103_wr_reg(priv, 0xf8, u8tmp); + if (ret) + goto err; + } + break; + case SYS_DVBS2: + ret = m88ds3103_rd_regs(priv, 0xd5, buf, 3); + if (ret) + goto err; + + utmp = (buf[2] << 16) | (buf[1] << 8) | (buf[0] << 0); + + if (utmp > 3000) { + ret = m88ds3103_rd_regs(priv, 0xf7, buf, 2); + if (ret) + goto err; + + priv->ber = (buf[1] << 8) | (buf[0] << 0); + + /* restart counters */ + ret = m88ds3103_wr_reg(priv, 0xd1, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xf9, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xf9, 0x00); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xd1, 0x00); + if (ret) + goto err; + } + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", + __func__); + ret = -EINVAL; + goto err; + } + + *ber = priv->ber; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_set_tone(struct dvb_frontend *fe, + fe_sec_tone_mode_t fe_sec_tone_mode) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret; + u8 u8tmp, tone, reg_a1_mask; + + dev_dbg(&priv->i2c->dev, "%s: fe_sec_tone_mode=%d\n", __func__, + fe_sec_tone_mode); + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + switch (fe_sec_tone_mode) { + case SEC_TONE_ON: + tone = 0; + reg_a1_mask = 0x47; + break; + case SEC_TONE_OFF: + tone = 1; + reg_a1_mask = 0x00; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_tone_mode\n", + __func__); + ret = -EINVAL; + goto err; + } + + u8tmp = tone << 7 | priv->cfg->envelope_mode << 5; + ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0); + if (ret) + goto err; + + u8tmp = 1 << 2; + ret = m88ds3103_wr_reg_mask(priv, 0xa1, u8tmp, reg_a1_mask); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t fe_sec_voltage) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret; + u8 u8tmp; + bool voltage_sel, voltage_dis; + + dev_dbg(&priv->i2c->dev, "%s: fe_sec_voltage=%d\n", __func__, + fe_sec_voltage); + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + switch (fe_sec_voltage) { + case SEC_VOLTAGE_18: + voltage_sel = true; + voltage_dis = false; + break; + case SEC_VOLTAGE_13: + voltage_sel = false; + voltage_dis = false; + break; + case SEC_VOLTAGE_OFF: + voltage_sel = false; + voltage_dis = true; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_voltage\n", + __func__); + ret = -EINVAL; + goto err; + } + + /* output pin polarity */ + voltage_sel ^= priv->cfg->lnb_hv_pol; + voltage_dis ^= priv->cfg->lnb_en_pol; + + u8tmp = voltage_dis << 1 | voltage_sel << 0; + ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0x03); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_diseqc_send_master_cmd(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *diseqc_cmd) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret, i; + u8 u8tmp; + + dev_dbg(&priv->i2c->dev, "%s: msg=%*ph\n", __func__, + diseqc_cmd->msg_len, diseqc_cmd->msg); + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + if (diseqc_cmd->msg_len < 3 || diseqc_cmd->msg_len > 6) { + ret = -EINVAL; + goto err; + } + + u8tmp = priv->cfg->envelope_mode << 5; + ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0); + if (ret) + goto err; + + ret = m88ds3103_wr_regs(priv, 0xa3, diseqc_cmd->msg, + diseqc_cmd->msg_len); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xa1, + (diseqc_cmd->msg_len - 1) << 3 | 0x07); + if (ret) + goto err; + + /* DiSEqC message typical period is 54 ms */ + usleep_range(40000, 60000); + + /* wait DiSEqC TX ready */ + for (i = 20, u8tmp = 1; i && u8tmp; i--) { + usleep_range(5000, 10000); + + ret = m88ds3103_rd_reg_mask(priv, 0xa1, &u8tmp, 0x40); + if (ret) + goto err; + } + + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + + if (i == 0) { + dev_dbg(&priv->i2c->dev, "%s: diseqc tx timeout\n", __func__); + + ret = m88ds3103_wr_reg_mask(priv, 0xa1, 0x40, 0xc0); + if (ret) + goto err; + } + + ret = m88ds3103_wr_reg_mask(priv, 0xa2, 0x80, 0xc0); + if (ret) + goto err; + + if (i == 0) { + ret = -ETIMEDOUT; + goto err; + } + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_diseqc_send_burst(struct dvb_frontend *fe, + fe_sec_mini_cmd_t fe_sec_mini_cmd) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret, i; + u8 u8tmp, burst; + + dev_dbg(&priv->i2c->dev, "%s: fe_sec_mini_cmd=%d\n", __func__, + fe_sec_mini_cmd); + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + u8tmp = priv->cfg->envelope_mode << 5; + ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0); + if (ret) + goto err; + + switch (fe_sec_mini_cmd) { + case SEC_MINI_A: + burst = 0x02; + break; + case SEC_MINI_B: + burst = 0x01; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_mini_cmd\n", + __func__); + ret = -EINVAL; + goto err; + } + + ret = m88ds3103_wr_reg(priv, 0xa1, burst); + if (ret) + goto err; + + /* DiSEqC ToneBurst period is 12.5 ms */ + usleep_range(11000, 20000); + + /* wait DiSEqC TX ready */ + for (i = 5, u8tmp = 1; i && u8tmp; i--) { + usleep_range(800, 2000); + + ret = m88ds3103_rd_reg_mask(priv, 0xa1, &u8tmp, 0x40); + if (ret) + goto err; + } + + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + + ret = m88ds3103_wr_reg_mask(priv, 0xa2, 0x80, 0xc0); + if (ret) + goto err; + + if (i == 0) { + dev_dbg(&priv->i2c->dev, "%s: diseqc tx timeout\n", __func__); + ret = -ETIMEDOUT; + goto err; + } + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + s->min_delay_ms = 3000; + + return 0; +} + +static void m88ds3103_release(struct dvb_frontend *fe) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + + i2c_del_mux_adapter(priv->i2c_adapter); + kfree(priv); +} + +static int m88ds3103_select(struct i2c_adapter *adap, void *mux_priv, u32 chan) +{ + struct m88ds3103_priv *priv = mux_priv; + int ret; + struct i2c_msg gate_open_msg[1] = { + { + .addr = priv->cfg->i2c_addr, + .flags = 0, + .len = 2, + .buf = "\x03\x11", + } + }; + + mutex_lock(&priv->i2c_mutex); + + /* open tuner I2C repeater for 1 xfer, closes automatically */ + ret = __i2c_transfer(priv->i2c, gate_open_msg, 1); + if (ret != 1) { + dev_warn(&priv->i2c->dev, "%s: i2c wr failed=%d\n", + KBUILD_MODNAME, ret); + if (ret >= 0) + ret = -EREMOTEIO; + + return ret; + } + + return 0; +} + +static int m88ds3103_deselect(struct i2c_adapter *adap, void *mux_priv, + u32 chan) +{ + struct m88ds3103_priv *priv = mux_priv; + + mutex_unlock(&priv->i2c_mutex); + + return 0; +} + +struct dvb_frontend *m88ds3103_attach(const struct m88ds3103_config *cfg, + struct i2c_adapter *i2c, struct i2c_adapter **tuner_i2c_adapter) +{ + int ret; + struct m88ds3103_priv *priv; + u8 chip_id, u8tmp; + + /* allocate memory for the internal priv */ + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + dev_err(&i2c->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); + goto err; + } + + priv->cfg = cfg; + priv->i2c = i2c; + mutex_init(&priv->i2c_mutex); + + /* 0x00: chip id[6:0], 0x01: chip ver[7:0], 0x02: chip ver[15:8] */ + ret = m88ds3103_rd_reg(priv, 0x00, &chip_id); + if (ret) + goto err; + + chip_id >>= 1; + dev_info(&priv->i2c->dev, "%s: chip_id=%02x\n", __func__, chip_id); + + switch (chip_id) { + case M88RS6000_CHIP_ID: + case M88DS3103_CHIP_ID: + break; + default: + goto err; + } + priv->chip_id = chip_id; + + switch (priv->cfg->clock_out) { + case M88DS3103_CLOCK_OUT_DISABLED: + u8tmp = 0x80; + break; + case M88DS3103_CLOCK_OUT_ENABLED: + u8tmp = 0x00; + break; + case M88DS3103_CLOCK_OUT_ENABLED_DIV2: + u8tmp = 0x10; + break; + default: + goto err; + } + + /* 0x29 register is defined differently for m88rs6000. */ + /* set internal tuner address to 0x21 */ + if (chip_id == M88RS6000_CHIP_ID) + u8tmp = 0x00; + + ret = m88ds3103_wr_reg(priv, 0x29, u8tmp); + if (ret) + goto err; + + /* sleep */ + ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x00, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x01, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x10, 0x10); + if (ret) + goto err; + + /* create mux i2c adapter for tuner */ + priv->i2c_adapter = i2c_add_mux_adapter(i2c, &i2c->dev, priv, 0, 0, 0, + m88ds3103_select, m88ds3103_deselect); + if (priv->i2c_adapter == NULL) + goto err; + + *tuner_i2c_adapter = priv->i2c_adapter; + + /* create dvb_frontend */ + memcpy(&priv->fe.ops, &m88ds3103_ops, sizeof(struct dvb_frontend_ops)); + if (priv->chip_id == M88RS6000_CHIP_ID) + strncpy(priv->fe.ops.info.name, + "Montage M88RS6000", sizeof(priv->fe.ops.info.name)); + priv->fe.demodulator_priv = priv; + + return &priv->fe; +err: + dev_dbg(&i2c->dev, "%s: failed=%d\n", __func__, ret); + kfree(priv); + return NULL; +} +EXPORT_SYMBOL(m88ds3103_attach); + +static struct dvb_frontend_ops m88ds3103_ops = { + .delsys = { SYS_DVBS, SYS_DVBS2 }, + .info = { + .name = "Montage M88DS3103", + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_tolerance = 5000, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_4_5 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_6_7 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_8_9 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_RECOVER | + FE_CAN_2G_MODULATION + }, + + .release = m88ds3103_release, + + .get_tune_settings = m88ds3103_get_tune_settings, + + .init = m88ds3103_init, + .sleep = m88ds3103_sleep, + + .set_frontend = m88ds3103_set_frontend, + .get_frontend = m88ds3103_get_frontend, + + .read_status = m88ds3103_read_status, + .read_snr = m88ds3103_read_snr, + .read_ber = m88ds3103_read_ber, + + .diseqc_send_master_cmd = m88ds3103_diseqc_send_master_cmd, + .diseqc_send_burst = m88ds3103_diseqc_send_burst, + + .set_tone = m88ds3103_set_tone, + .set_voltage = m88ds3103_set_voltage, +}; + +MODULE_AUTHOR("Antti Palosaari "); +MODULE_DESCRIPTION("Montage M88DS3103 DVB-S/S2 demodulator driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(M88DS3103_FIRMWARE); +MODULE_FIRMWARE(M88RS6000_FIRMWARE); diff --git a/drivers/media/dvb-frontends/m88ds3103.h b/drivers/media/dvb-frontends/m88ds3103.h new file mode 100644 index 00000000000000..9b3b4962da7c16 --- /dev/null +++ b/drivers/media/dvb-frontends/m88ds3103.h @@ -0,0 +1,139 @@ +/* + * Montage M88DS3103 demodulator driver + * + * Copyright (C) 2013 Antti Palosaari + * + * 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. + */ + +#ifndef M88DS3103_H +#define M88DS3103_H + +#include + +struct m88ds3103_config { + /* + * I2C address + * Default: none, must set + * 0x68, ... + */ + u8 i2c_addr; + + /* + * clock + * Default: none, must set + * 27000000 + */ + u32 clock; + + /* + * max bytes I2C provider is asked to write at once + * Default: none, must set + * 33, 65, ... + */ + u16 i2c_wr_max; + + /* + * TS output mode + * Default: M88DS3103_TS_SERIAL + */ +#define M88DS3103_TS_SERIAL 0 /* TS output pin D0, normal */ +#define M88DS3103_TS_SERIAL_D7 1 /* TS output pin D7 */ +#define M88DS3103_TS_PARALLEL 2 /* TS Parallel mode */ +#define M88DS3103_TS_CI 3 /* TS CI Mode */ + u8 ts_mode; + + /* + * TS clk in KHz + * Default: 0. + */ + u32 ts_clk; + + /* + * TS clk polarity. + * Default: 0. 1-active at falling edge; 0-active at rising edge. + */ + u8 ts_clk_pol:1; + + /* + * spectrum inversion + * Default: 0 + */ + u8 spec_inv:1; + + /* + * AGC polarity + * Default: 0 + */ + u8 agc_inv:1; + + /* + * clock output + * Default: M88DS3103_CLOCK_OUT_DISABLED + */ +#define M88DS3103_CLOCK_OUT_DISABLED 0 +#define M88DS3103_CLOCK_OUT_ENABLED 1 +#define M88DS3103_CLOCK_OUT_ENABLED_DIV2 2 + u8 clock_out; + + /* + * DiSEqC envelope mode + * Default: 0 + */ + u8 envelope_mode:1; + + /* + * AGC configuration + * Default: none, must set + */ + u8 agc; + + /* + * LNB H/V pin polarity + * Default: 0. + * 1: pin high set to VOLTAGE_13, pin low to set VOLTAGE_18. + * 0: pin high set to VOLTAGE_18, pin low to set VOLTAGE_13. + */ + u8 lnb_hv_pol:1; + + /* + * LNB enable pin polarity + * Default: 0. + * 1: pin high to enable, pin low to disable. + * 0: pin high to disable, pin low to enable. + */ + u8 lnb_en_pol:1; +}; + +/* + * Driver implements own I2C-adapter for tuner I2C access. That's since chip + * has I2C-gate control which closes gate automatically after I2C transfer. + * Using own I2C adapter we can workaround that. + */ + +#if defined(CONFIG_DVB_M88DS3103) || \ + (defined(CONFIG_DVB_M88DS3103_MODULE) && defined(MODULE)) +extern struct dvb_frontend *m88ds3103_attach( + const struct m88ds3103_config *config, + struct i2c_adapter *i2c, + struct i2c_adapter **tuner_i2c); +#else +static inline struct dvb_frontend *m88ds3103_attach( + const struct m88ds3103_config *config, + struct i2c_adapter *i2c, + struct i2c_adapter **tuner_i2c) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif diff --git a/drivers/media/dvb-frontends/m88ds3103_priv.h b/drivers/media/dvb-frontends/m88ds3103_priv.h new file mode 100644 index 00000000000000..a2c0958111f83f --- /dev/null +++ b/drivers/media/dvb-frontends/m88ds3103_priv.h @@ -0,0 +1,398 @@ +/* + * Montage M88DS3103 demodulator driver + * + * Copyright (C) 2013 Antti Palosaari + * + * 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. + */ + +#ifndef M88DS3103_PRIV_H +#define M88DS3103_PRIV_H + +#include "dvb_frontend.h" +#include "m88ds3103.h" +#include "dvb_math.h" +#include +#include +#include + +#define M88DS3103_FIRMWARE "dvb-demod-m88ds3103.fw" +#define M88RS6000_FIRMWARE "dvb-demod-m88rs6000.fw" +#define M88DS3103_MCLK_KHZ 96000 +#define M88RS6000_CHIP_ID 0x74 +#define M88DS3103_CHIP_ID 0x70 + +struct m88ds3103_priv { + struct i2c_adapter *i2c; + /* mutex needed due to own tuner I2C adapter */ + struct mutex i2c_mutex; + const struct m88ds3103_config *cfg; + struct dvb_frontend fe; + fe_delivery_system_t delivery_system; + fe_status_t fe_status; + u32 ber; + bool warm; /* FW running */ + struct i2c_adapter *i2c_adapter; + /* auto detect chip id to do different config */ + u8 chip_id; + /* main mclk is calculated for M88RS6000 dynamically */ + u32 mclk_khz; +}; + +struct m88ds3103_reg_val { + u8 reg; + u8 val; +}; + +static const struct m88ds3103_reg_val m88ds3103_dvbs_init_reg_vals[] = { + {0x23, 0x07}, + {0x08, 0x03}, + {0x0c, 0x02}, + {0x21, 0x54}, + {0x25, 0x8a}, + {0x27, 0x31}, + {0x30, 0x08}, + {0x31, 0x40}, + {0x32, 0x32}, + {0x35, 0xff}, + {0x3a, 0x00}, + {0x37, 0x10}, + {0x38, 0x10}, + {0x39, 0x02}, + {0x42, 0x60}, + {0x4a, 0x80}, + {0x4b, 0x04}, + {0x4d, 0x91}, + {0x5d, 0xc8}, + {0x50, 0x36}, + {0x51, 0x36}, + {0x52, 0x36}, + {0x53, 0x36}, + {0x56, 0x01}, + {0x63, 0x0f}, + {0x64, 0x30}, + {0x65, 0x40}, + {0x68, 0x26}, + {0x69, 0x4c}, + {0x70, 0x20}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x40}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x60}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x80}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0xa0}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x1f}, + {0x76, 0x38}, + {0x77, 0xa6}, + {0x78, 0x0c}, + {0x79, 0x80}, + {0x7f, 0x14}, + {0x7c, 0x00}, + {0xae, 0x82}, + {0x80, 0x64}, + {0x81, 0x66}, + {0x82, 0x44}, + {0x85, 0x04}, + {0xcd, 0xf4}, + {0x90, 0x33}, + {0xa0, 0x44}, + {0xc0, 0x08}, + {0xc3, 0x10}, + {0xc4, 0x08}, + {0xc5, 0xf0}, + {0xc6, 0xff}, + {0xc7, 0x00}, + {0xc8, 0x1a}, + {0xc9, 0x80}, + {0xe0, 0xf8}, + {0xe6, 0x8b}, + {0xd0, 0x40}, + {0xf8, 0x20}, + {0xfa, 0x0f}, + {0x00, 0x00}, + {0xbd, 0x01}, + {0xb8, 0x00}, +}; + +static const struct m88ds3103_reg_val m88ds3103_dvbs2_init_reg_vals[] = { + {0x23, 0x07}, + {0x08, 0x07}, + {0x0c, 0x02}, + {0x21, 0x54}, + {0x25, 0x8a}, + {0x27, 0x31}, + {0x30, 0x08}, + {0x32, 0x32}, + {0x35, 0xff}, + {0x3a, 0x00}, + {0x37, 0x10}, + {0x38, 0x10}, + {0x39, 0x02}, + {0x42, 0x60}, + {0x4a, 0x80}, + {0x4b, 0x04}, + {0x4d, 0x91}, + {0x5d, 0xc8}, + {0x50, 0x36}, + {0x51, 0x36}, + {0x52, 0x36}, + {0x53, 0x36}, + {0x56, 0x01}, + {0x63, 0x0f}, + {0x64, 0x10}, + {0x65, 0x20}, + {0x68, 0x46}, + {0x69, 0xcd}, + {0x70, 0x20}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x40}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x60}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x80}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0xa0}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x1f}, + {0x76, 0x38}, + {0x77, 0xa6}, + {0x78, 0x0c}, + {0x79, 0x80}, + {0x7f, 0x14}, + {0x85, 0x08}, + {0xcd, 0xf4}, + {0x90, 0x33}, + {0x86, 0x00}, + {0x87, 0x0f}, + {0x89, 0x00}, + {0x8b, 0x44}, + {0x8c, 0x66}, + {0x9d, 0xc1}, + {0x8a, 0x10}, + {0xad, 0x40}, + {0xa0, 0x44}, + {0xc0, 0x08}, + {0xc1, 0x10}, + {0xc2, 0x08}, + {0xc3, 0x10}, + {0xc4, 0x08}, + {0xc5, 0xf0}, + {0xc6, 0xff}, + {0xc7, 0x00}, + {0xc8, 0x1a}, + {0xc9, 0x80}, + {0xca, 0x23}, + {0xcb, 0x24}, + {0xcc, 0xf4}, + {0xce, 0x74}, + {0x00, 0x00}, + {0xbd, 0x01}, + {0xb8, 0x00}, +}; + +static const struct m88ds3103_reg_val m88rs6000_dvbs_init_reg_vals[] = { + {0x23, 0x07}, + {0x08, 0x03}, + {0x0c, 0x02}, + {0x20, 0x00}, + {0x21, 0x54}, + {0x25, 0x82}, + {0x27, 0x31}, + {0x30, 0x08}, + {0x31, 0x40}, + {0x32, 0x32}, + {0x33, 0x35}, + {0x35, 0xff}, + {0x3a, 0x00}, + {0x37, 0x10}, + {0x38, 0x10}, + {0x39, 0x02}, + {0x42, 0x60}, + {0x4a, 0x80}, + {0x4b, 0x04}, + {0x4d, 0x91}, + {0x5d, 0xc8}, + {0x50, 0x36}, + {0x51, 0x36}, + {0x52, 0x36}, + {0x53, 0x36}, + {0x63, 0x0f}, + {0x64, 0x30}, + {0x65, 0x40}, + {0x68, 0x26}, + {0x69, 0x4c}, + {0x70, 0x20}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x40}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x60}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x80}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0xa0}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x1f}, + {0x76, 0x38}, + {0x77, 0xa6}, + {0x78, 0x0c}, + {0x79, 0x80}, + {0x7f, 0x14}, + {0x7c, 0x00}, + {0xae, 0x82}, + {0x80, 0x64}, + {0x81, 0x66}, + {0x82, 0x44}, + {0x85, 0x04}, + {0xcd, 0xf4}, + {0x90, 0x33}, + {0xa0, 0x44}, + {0xbe, 0x00}, + {0xc0, 0x08}, + {0xc3, 0x10}, + {0xc4, 0x08}, + {0xc5, 0xf0}, + {0xc6, 0xff}, + {0xc7, 0x00}, + {0xc8, 0x1a}, + {0xc9, 0x80}, + {0xe0, 0xf8}, + {0xe6, 0x8b}, + {0xd0, 0x40}, + {0xf8, 0x20}, + {0xfa, 0x0f}, + {0x00, 0x00}, + {0xbd, 0x01}, + {0xb8, 0x00}, + {0x29, 0x11}, +}; + +static const struct m88ds3103_reg_val m88rs6000_dvbs2_init_reg_vals[] = { + {0x23, 0x07}, + {0x08, 0x07}, + {0x0c, 0x02}, + {0x20, 0x00}, + {0x21, 0x54}, + {0x25, 0x82}, + {0x27, 0x31}, + {0x30, 0x08}, + {0x32, 0x32}, + {0x33, 0x35}, + {0x35, 0xff}, + {0x3a, 0x00}, + {0x37, 0x10}, + {0x38, 0x10}, + {0x39, 0x02}, + {0x42, 0x60}, + {0x4a, 0x80}, + {0x4b, 0x04}, + {0x4d, 0x91}, + {0x5d, 0xc8}, + {0x50, 0x36}, + {0x51, 0x36}, + {0x52, 0x36}, + {0x53, 0x36}, + {0x63, 0x0f}, + {0x64, 0x10}, + {0x65, 0x20}, + {0x68, 0x46}, + {0x69, 0xcd}, + {0x70, 0x20}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x40}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x60}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x80}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0xa0}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x1f}, + {0x76, 0x38}, + {0x77, 0xa6}, + {0x78, 0x0c}, + {0x79, 0x80}, + {0x7f, 0x14}, + {0x85, 0x08}, + {0xcd, 0xf4}, + {0x90, 0x33}, + {0x86, 0x00}, + {0x87, 0x0f}, + {0x89, 0x00}, + {0x8b, 0x44}, + {0x8c, 0x66}, + {0x9d, 0xc1}, + {0x8a, 0x10}, + {0xad, 0x40}, + {0xa0, 0x44}, + {0xbe, 0x00}, + {0xc0, 0x08}, + {0xc1, 0x10}, + {0xc2, 0x08}, + {0xc3, 0x10}, + {0xc4, 0x08}, + {0xc5, 0xf0}, + {0xc6, 0xff}, + {0xc7, 0x00}, + {0xc8, 0x1a}, + {0xc9, 0x80}, + {0xca, 0x23}, + {0xcb, 0x24}, + {0xcc, 0xf4}, + {0xce, 0x74}, + {0x00, 0x00}, + {0xbd, 0x01}, + {0xb8, 0x00}, + {0x29, 0x01}, +}; +#endif diff --git a/drivers/media/dvb-frontends/si2168.c b/drivers/media/dvb-frontends/si2168.c new file mode 100644 index 00000000000000..5db588ebfc24ef --- /dev/null +++ b/drivers/media/dvb-frontends/si2168.c @@ -0,0 +1,743 @@ +/* + * Silicon Labs Si2168 DVB-T/T2/C demodulator driver + * + * Copyright (C) 2014 Antti Palosaari + * + * 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. + */ + +#include "si2168_priv.h" + +static const struct dvb_frontend_ops si2168_ops; + +/* execute firmware command */ +static int si2168_cmd_execute(struct i2c_client *client, struct si2168_cmd *cmd) +{ + struct si2168_dev *dev = i2c_get_clientdata(client); + int ret; + unsigned long timeout; + + mutex_lock(&dev->i2c_mutex); + + if (cmd->wlen) { + /* write cmd and args for firmware */ + ret = i2c_master_send(client, cmd->args, cmd->wlen); + if (ret < 0) { + goto err_mutex_unlock; + } else if (ret != cmd->wlen) { + ret = -EREMOTEIO; + goto err_mutex_unlock; + } + } + + if (cmd->rlen) { + /* wait cmd execution terminate */ + #define TIMEOUT 70 + timeout = jiffies + msecs_to_jiffies(TIMEOUT); + while (!time_after(jiffies, timeout)) { + ret = i2c_master_recv(client, cmd->args, cmd->rlen); + if (ret < 0) { + goto err_mutex_unlock; + } else if (ret != cmd->rlen) { + ret = -EREMOTEIO; + goto err_mutex_unlock; + } + + /* firmware ready? */ + if ((cmd->args[0] >> 7) & 0x01) + break; + } + + dev_dbg(&client->dev, "cmd execution took %d ms\n", + jiffies_to_msecs(jiffies) - + (jiffies_to_msecs(timeout) - TIMEOUT)); + + if (!((cmd->args[0] >> 7) & 0x01)) { + ret = -ETIMEDOUT; + goto err_mutex_unlock; + } + } + + mutex_unlock(&dev->i2c_mutex); + return 0; + +err_mutex_unlock: + mutex_unlock(&dev->i2c_mutex); + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct i2c_client *client = fe->demodulator_priv; + struct si2168_dev *dev = i2c_get_clientdata(client); + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + struct si2168_cmd cmd; + + *status = 0; + + if (!dev->active) { + ret = -EAGAIN; + goto err; + } + + switch (c->delivery_system) { + case SYS_DVBT: + memcpy(cmd.args, "\xa0\x01", 2); + cmd.wlen = 2; + cmd.rlen = 13; + break; + case SYS_DVBC_ANNEX_A: + memcpy(cmd.args, "\x90\x01", 2); + cmd.wlen = 2; + cmd.rlen = 9; + break; + case SYS_DVBT2: + memcpy(cmd.args, "\x50\x01", 2); + cmd.wlen = 2; + cmd.rlen = 14; + break; + default: + ret = -EINVAL; + goto err; + } + + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + switch ((cmd.args[2] >> 1) & 0x03) { + case 0x01: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; + break; + case 0x03: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | + FE_HAS_SYNC | FE_HAS_LOCK; + break; + } + + dev->fe_status = *status; + + if (*status & FE_HAS_LOCK) { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + c->cnr.stat[0].svalue = cmd.args[3] * 1000 / 4; + } else { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + } + + dev_dbg(&client->dev, "status=%02x args=%*ph\n", + *status, cmd.rlen, cmd.args); + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_set_frontend(struct dvb_frontend *fe) +{ + struct i2c_client *client = fe->demodulator_priv; + struct si2168_dev *dev = i2c_get_clientdata(client); + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + struct si2168_cmd cmd; + u8 bandwidth, delivery_system; + + dev_dbg(&client->dev, + "delivery_system=%u modulation=%u frequency=%u bandwidth_hz=%u symbol_rate=%u inversion=%u stream_id=%u\n", + c->delivery_system, c->modulation, c->frequency, + c->bandwidth_hz, c->symbol_rate, c->inversion, + c->stream_id); + + if (!dev->active) { + ret = -EAGAIN; + goto err; + } + + switch (c->delivery_system) { + case SYS_DVBT: + delivery_system = 0x20; + break; + case SYS_DVBC_ANNEX_A: + delivery_system = 0x30; + break; + case SYS_DVBT2: + delivery_system = 0x70; + break; + default: + ret = -EINVAL; + goto err; + } + + if (c->bandwidth_hz == 0) { + ret = -EINVAL; + goto err; + } else if (c->bandwidth_hz <= 2000000) + bandwidth = 0x02; + else if (c->bandwidth_hz <= 5000000) + bandwidth = 0x05; + else if (c->bandwidth_hz <= 6000000) + bandwidth = 0x06; + else if (c->bandwidth_hz <= 7000000) + bandwidth = 0x07; + else if (c->bandwidth_hz <= 8000000) + bandwidth = 0x08; + else if (c->bandwidth_hz <= 9000000) + bandwidth = 0x09; + else if (c->bandwidth_hz <= 10000000) + bandwidth = 0x0a; + else + bandwidth = 0x0f; + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) { + ret = fe->ops.tuner_ops.set_params(fe); + if (ret) + goto err; + } + + memcpy(cmd.args, "\x88\x02\x02\x02\x02", 5); + cmd.wlen = 5; + cmd.rlen = 5; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + /* that has no big effect */ + if (c->delivery_system == SYS_DVBT) + memcpy(cmd.args, "\x89\x21\x06\x11\xff\x98", 6); + else if (c->delivery_system == SYS_DVBC_ANNEX_A) + memcpy(cmd.args, "\x89\x21\x06\x11\x89\xf0", 6); + else if (c->delivery_system == SYS_DVBT2) + memcpy(cmd.args, "\x89\x21\x06\x11\x89\x20", 6); + cmd.wlen = 6; + cmd.rlen = 3; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + if (c->delivery_system == SYS_DVBT2) { + /* select PLP */ + cmd.args[0] = 0x52; + cmd.args[1] = c->stream_id & 0xff; + cmd.args[2] = c->stream_id == NO_STREAM_ID_FILTER ? 0 : 1; + cmd.wlen = 3; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + } + + memcpy(cmd.args, "\x51\x03", 2); + cmd.wlen = 2; + cmd.rlen = 12; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x12\x08\x04", 3); + cmd.wlen = 3; + cmd.rlen = 3; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x0c\x10\x12\x00", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x06\x10\x24\x00", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x07\x10\x00\x24", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x0a\x10\x00\x00", 6); + cmd.args[4] = delivery_system | bandwidth; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + /* set DVB-C symbol rate */ + if (c->delivery_system == SYS_DVBC_ANNEX_A) { + memcpy(cmd.args, "\x14\x00\x02\x11", 4); + cmd.args[4] = ((c->symbol_rate / 1000) >> 0) & 0xff; + cmd.args[5] = ((c->symbol_rate / 1000) >> 8) & 0xff; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + } + + memcpy(cmd.args, "\x14\x00\x0f\x10\x10\x00", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x09\x10\xe3\x08", 6); + cmd.args[5] |= dev->ts_clock_inv ? 0x00 : 0x10; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x08\x10\xd7\x05", 6); + cmd.args[5] |= dev->ts_clock_inv ? 0x00 : 0x10; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x01\x12\x00\x00", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x01\x03\x0c\x00", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x85", 1); + cmd.wlen = 1; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + dev->delivery_system = c->delivery_system; + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_init(struct dvb_frontend *fe) +{ + struct i2c_client *client = fe->demodulator_priv; + struct si2168_dev *dev = i2c_get_clientdata(client); + int ret, len, remaining; + const struct firmware *fw; + const char *fw_name; + struct si2168_cmd cmd; + unsigned int chip_id; + + dev_dbg(&client->dev, "\n"); + + /* initialize */ + memcpy(cmd.args, "\xc0\x12\x00\x0c\x00\x0d\x16\x00\x00\x00\x00\x00\x00", 13); + cmd.wlen = 13; + cmd.rlen = 0; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + if (dev->fw_loaded) { + /* resume */ + memcpy(cmd.args, "\xc0\x06\x08\x0f\x00\x20\x21\x01", 8); + cmd.wlen = 8; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x85", 1); + cmd.wlen = 1; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + goto warm; + } + + /* power up */ + memcpy(cmd.args, "\xc0\x06\x01\x0f\x00\x20\x20\x01", 8); + cmd.wlen = 8; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + /* query chip revision */ + memcpy(cmd.args, "\x02", 1); + cmd.wlen = 1; + cmd.rlen = 13; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + chip_id = cmd.args[1] << 24 | cmd.args[2] << 16 | cmd.args[3] << 8 | + cmd.args[4] << 0; + + #define SI2168_A20 ('A' << 24 | 68 << 16 | '2' << 8 | '0' << 0) + #define SI2168_A30 ('A' << 24 | 68 << 16 | '3' << 8 | '0' << 0) + #define SI2168_B40 ('B' << 24 | 68 << 16 | '4' << 8 | '0' << 0) + + switch (chip_id) { + case SI2168_A20: + fw_name = SI2168_A20_FIRMWARE; + break; + case SI2168_A30: + fw_name = SI2168_A30_FIRMWARE; + break; + case SI2168_B40: + fw_name = SI2168_B40_FIRMWARE; + break; + default: + dev_err(&client->dev, "unknown chip version Si21%d-%c%c%c\n", + cmd.args[2], cmd.args[1], + cmd.args[3], cmd.args[4]); + ret = -EINVAL; + goto err; + } + + dev_info(&client->dev, "found a 'Silicon Labs Si21%d-%c%c%c'\n", + cmd.args[2], cmd.args[1], cmd.args[3], cmd.args[4]); + + /* request the firmware, this will block and timeout */ + ret = request_firmware(&fw, fw_name, &client->dev); + if (ret) { + /* fallback mechanism to handle old name for Si2168 B40 fw */ + if (chip_id == SI2168_B40) { + fw_name = SI2168_B40_FIRMWARE_FALLBACK; + ret = request_firmware(&fw, fw_name, &client->dev); + } + + if (ret == 0) { + dev_notice(&client->dev, + "please install firmware file '%s'\n", + SI2168_B40_FIRMWARE); + } else { + dev_err(&client->dev, + "firmware file '%s' not found\n", + fw_name); + goto err_release_firmware; + } + } + + dev_info(&client->dev, "downloading firmware from file '%s'\n", + fw_name); + + if ((fw->size % 17 == 0) && (fw->data[0] > 5)) { + /* firmware is in the new format */ + for (remaining = fw->size; remaining > 0; remaining -= 17) { + len = fw->data[fw->size - remaining]; + memcpy(cmd.args, &fw->data[(fw->size - remaining) + 1], len); + cmd.wlen = len; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + break; + } + } else if (fw->size % 8 == 0) { + /* firmware is in the old format */ + for (remaining = fw->size; remaining > 0; remaining -= 8) { + len = 8; + memcpy(cmd.args, &fw->data[fw->size - remaining], len); + cmd.wlen = len; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + break; + } + } else { + /* bad or unknown firmware format */ + ret = -EINVAL; + } + + if (ret) { + dev_err(&client->dev, "firmware download failed %d\n", ret); + goto err_release_firmware; + } + + release_firmware(fw); + + memcpy(cmd.args, "\x01\x01", 2); + cmd.wlen = 2; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + /* query firmware version */ + memcpy(cmd.args, "\x11", 1); + cmd.wlen = 1; + cmd.rlen = 10; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + dev_info(&client->dev, "firmware version: %c.%c.%d\n", + cmd.args[6], cmd.args[7], cmd.args[8]); + + /* set ts mode */ + memcpy(cmd.args, "\x14\x00\x01\x10\x10\x00", 6); + cmd.args[4] |= dev->ts_mode; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + dev->fw_loaded = true; +warm: + dev->active = true; + + return 0; + +err_release_firmware: + release_firmware(fw); +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_sleep(struct dvb_frontend *fe) +{ + struct i2c_client *client = fe->demodulator_priv; + struct si2168_dev *dev = i2c_get_clientdata(client); + int ret; + struct si2168_cmd cmd; + + dev_dbg(&client->dev, "\n"); + + dev->active = false; + + memcpy(cmd.args, "\x13", 1); + cmd.wlen = 1; + cmd.rlen = 0; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + s->min_delay_ms = 900; + + return 0; +} + +/* + * I2C gate logic + * We must use unlocked i2c_transfer() here because I2C lock is already taken + * by tuner driver. + */ +static int si2168_select(struct i2c_adapter *adap, void *mux_priv, u32 chan) +{ + struct i2c_client *client = mux_priv; + struct si2168_dev *dev = i2c_get_clientdata(client); + int ret; + struct i2c_msg gate_open_msg = { + .addr = client->addr, + .flags = 0, + .len = 3, + .buf = "\xc0\x0d\x01", + }; + + mutex_lock(&dev->i2c_mutex); + + /* open tuner I2C gate */ + ret = __i2c_transfer(client->adapter, &gate_open_msg, 1); + if (ret != 1) { + dev_warn(&client->dev, "i2c write failed=%d\n", ret); + if (ret >= 0) + ret = -EREMOTEIO; + } else { + ret = 0; + } + + return ret; +} + +static int si2168_deselect(struct i2c_adapter *adap, void *mux_priv, u32 chan) +{ + struct i2c_client *client = mux_priv; + struct si2168_dev *dev = i2c_get_clientdata(client); + int ret; + struct i2c_msg gate_close_msg = { + .addr = client->addr, + .flags = 0, + .len = 3, + .buf = "\xc0\x0d\x00", + }; + + /* close tuner I2C gate */ + ret = __i2c_transfer(client->adapter, &gate_close_msg, 1); + if (ret != 1) { + dev_warn(&client->dev, "i2c write failed=%d\n", ret); + if (ret >= 0) + ret = -EREMOTEIO; + } else { + ret = 0; + } + + mutex_unlock(&dev->i2c_mutex); + + return ret; +} + +static const struct dvb_frontend_ops si2168_ops = { + .delsys = {SYS_DVBT, SYS_DVBT2, SYS_DVBC_ANNEX_A}, + .info = { + .name = "Silicon Labs Si2168", + .symbol_rate_min = 1000000, + .symbol_rate_max = 7200000, + .caps = 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_32 | + FE_CAN_QAM_64 | + FE_CAN_QAM_128 | + FE_CAN_QAM_256 | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_MUTE_TS | + FE_CAN_2G_MODULATION | + FE_CAN_MULTISTREAM + }, + + .get_tune_settings = si2168_get_tune_settings, + + .init = si2168_init, + .sleep = si2168_sleep, + + .set_frontend = si2168_set_frontend, + + .read_status = si2168_read_status, +}; + +static int si2168_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct si2168_config *config = client->dev.platform_data; + struct si2168_dev *dev; + int ret; + + dev_dbg(&client->dev, "\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + dev_err(&client->dev, "kzalloc() failed\n"); + goto err; + } + + mutex_init(&dev->i2c_mutex); + + /* create mux i2c adapter for tuner */ + dev->adapter = i2c_add_mux_adapter(client->adapter, &client->dev, + client, 0, 0, 0, si2168_select, si2168_deselect); + if (dev->adapter == NULL) { + ret = -ENODEV; + goto err_kfree; + } + + /* create dvb_frontend */ + memcpy(&dev->fe.ops, &si2168_ops, sizeof(struct dvb_frontend_ops)); + dev->fe.demodulator_priv = client; + *config->i2c_adapter = dev->adapter; + *config->fe = &dev->fe; + dev->ts_mode = config->ts_mode; + dev->ts_clock_inv = config->ts_clock_inv; + dev->fw_loaded = false; + + i2c_set_clientdata(client, dev); + + dev_info(&client->dev, "Silicon Labs Si2168 successfully attached\n"); + return 0; +err_kfree: + kfree(dev); +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_remove(struct i2c_client *client) +{ + struct si2168_dev *dev = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "\n"); + + i2c_del_mux_adapter(dev->adapter); + + dev->fe.ops.release = NULL; + dev->fe.demodulator_priv = NULL; + + kfree(dev); + + return 0; +} + +static const struct i2c_device_id si2168_id_table[] = { + {"si2168", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, si2168_id_table); + +static struct i2c_driver si2168_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "si2168", + }, + .probe = si2168_probe, + .remove = si2168_remove, + .id_table = si2168_id_table, +}; + +module_i2c_driver(si2168_driver); + +MODULE_AUTHOR("Antti Palosaari "); +MODULE_DESCRIPTION("Silicon Labs Si2168 DVB-T/T2/C demodulator driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(SI2168_A20_FIRMWARE); +MODULE_FIRMWARE(SI2168_A30_FIRMWARE); +MODULE_FIRMWARE(SI2168_B40_FIRMWARE); diff --git a/drivers/media/dvb-frontends/si2168.h b/drivers/media/dvb-frontends/si2168.h new file mode 100644 index 00000000000000..70d702ae6f49b5 --- /dev/null +++ b/drivers/media/dvb-frontends/si2168.h @@ -0,0 +1,47 @@ +/* + * Silicon Labs Si2168 DVB-T/T2/C demodulator driver + * + * Copyright (C) 2014 Antti Palosaari + * + * 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. + */ + +#ifndef SI2168_H +#define SI2168_H + +#include +/* + * I2C address + * 0x64 + */ +struct si2168_config { + /* + * frontend + * returned by driver + */ + struct dvb_frontend **fe; + + /* + * tuner I2C adapter + * returned by driver + */ + struct i2c_adapter **i2c_adapter; + + /* TS mode */ +#define SI2168_TS_PARALLEL 0x06 +#define SI2168_TS_SERIAL 0x03 + u8 ts_mode; + + /* TS clock inverted */ + bool ts_clock_inv; +}; + +#endif diff --git a/drivers/media/dvb-frontends/si2168_priv.h b/drivers/media/dvb-frontends/si2168_priv.h new file mode 100644 index 00000000000000..aadd1367673fed --- /dev/null +++ b/drivers/media/dvb-frontends/si2168_priv.h @@ -0,0 +1,51 @@ +/* + * Silicon Labs Si2168 DVB-T/T2/C demodulator driver + * + * Copyright (C) 2014 Antti Palosaari + * + * 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. + */ + +#ifndef SI2168_PRIV_H +#define SI2168_PRIV_H + +#include "si2168.h" +#include "dvb_frontend.h" +#include +#include + +#define SI2168_A20_FIRMWARE "dvb-demod-si2168-a20-01.fw" +#define SI2168_A30_FIRMWARE "dvb-demod-si2168-a30-01.fw" +#define SI2168_B40_FIRMWARE "dvb-demod-si2168-b40-01.fw" +#define SI2168_B40_FIRMWARE_FALLBACK "dvb-demod-si2168-02.fw" + +/* state struct */ +struct si2168_dev { + struct i2c_adapter *adapter; + struct mutex i2c_mutex; + struct dvb_frontend fe; + fe_delivery_system_t delivery_system; + fe_status_t fe_status; + bool active; + bool fw_loaded; + u8 ts_mode; + bool ts_clock_inv; +}; + +/* firmare command struct */ +#define SI2168_ARGLEN 30 +struct si2168_cmd { + u8 args[SI2168_ARGLEN]; + unsigned wlen; + unsigned rlen; +}; + +#endif diff --git a/drivers/media/dvb-frontends/sp2.c b/drivers/media/dvb-frontends/sp2.c new file mode 100644 index 00000000000000..cc1ef966f99f2b --- /dev/null +++ b/drivers/media/dvb-frontends/sp2.c @@ -0,0 +1,444 @@ +/* + * CIMaX SP2/SP2HF (Atmel T90FJR) CI driver + * + * Copyright (C) 2014 Olli Salonen + * + * Heavily based on CIMax2(R) SP2 driver in conjunction with NetUp Dual + * DVB-S2 CI card (cimax2) with following copyrights: + * + * Copyright (C) 2009 NetUP Inc. + * Copyright (C) 2009 Igor M. Liplianin + * Copyright (C) 2009 Abylay Ospan + * + * 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. + */ + +#include "sp2_priv.h" + +static int sp2_read_i2c(struct sp2 *s, u8 reg, u8 *buf, int len) +{ + int ret; + struct i2c_client *client = s->client; + struct i2c_adapter *adap = client->adapter; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .buf = ®, + .len = 1 + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .buf = buf, + .len = len + } + }; + + ret = i2c_transfer(adap, msg, 2); + + if (ret != 2) { + dev_err(&client->dev, "i2c read error, reg = 0x%02x, status = %d\n", + reg, ret); + if (ret < 0) + return ret; + else + return -EIO; + } + + dev_dbg(&s->client->dev, "addr=0x%04x, reg = 0x%02x, data = %02x\n", + client->addr, reg, buf[0]); + + return 0; +} + +static int sp2_write_i2c(struct sp2 *s, u8 reg, u8 *buf, int len) +{ + int ret; + u8 buffer[35]; + struct i2c_client *client = s->client; + struct i2c_adapter *adap = client->adapter; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .buf = &buffer[0], + .len = len + 1 + }; + + if ((len + 1) > sizeof(buffer)) { + dev_err(&client->dev, "i2c wr reg=%02x: len=%d is too big!\n", + reg, len); + return -EINVAL; + } + + buffer[0] = reg; + memcpy(&buffer[1], buf, len); + + ret = i2c_transfer(adap, &msg, 1); + + if (ret != 1) { + dev_err(&client->dev, "i2c write error, reg = 0x%02x, status = %d\n", + reg, ret); + if (ret < 0) + return ret; + else + return -EIO; + } + + dev_dbg(&s->client->dev, "addr=0x%04x, reg = 0x%02x, data = %*ph\n", + client->addr, reg, len, buf); + + return 0; +} + +static int sp2_ci_op_cam(struct dvb_ca_en50221 *en50221, int slot, u8 acs, + u8 read, int addr, u8 data) +{ + struct sp2 *s = en50221->data; + u8 store; + int mem, ret; + int (*ci_op_cam)(void*, u8, int, u8, int*) = s->ci_control; + + if (slot != 0) + return -EINVAL; + + /* + * change module access type between IO space and attribute memory + * when needed + */ + if (s->module_access_type != acs) { + ret = sp2_read_i2c(s, 0x00, &store, 1); + + if (ret) + return ret; + + store &= ~(SP2_MOD_CTL_ACS1 | SP2_MOD_CTL_ACS0); + store |= acs; + + ret = sp2_write_i2c(s, 0x00, &store, 1); + if (ret) + return ret; + } + + s->module_access_type = acs; + + /* implementation of ci_op_cam is device specific */ + if (ci_op_cam) { + ret = ci_op_cam(s->priv, read, addr, data, &mem); + } else { + dev_err(&s->client->dev, "callback not defined"); + return -EINVAL; + } + + if (ret) + return ret; + + dev_dbg(&s->client->dev, "%s: slot=%d, addr=0x%04x, %s, data=%x", + (read) ? "read" : "write", slot, addr, + (acs == SP2_CI_ATTR_ACS) ? "attr" : "io", + (read) ? mem : data); + + if (read) + return mem; + else + return 0; + +} + +int sp2_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr) +{ + return sp2_ci_op_cam(en50221, slot, SP2_CI_ATTR_ACS, + SP2_CI_RD, addr, 0); +} + +int sp2_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr, u8 data) +{ + return sp2_ci_op_cam(en50221, slot, SP2_CI_ATTR_ACS, + SP2_CI_WR, addr, data); +} + +int sp2_ci_read_cam_control(struct dvb_ca_en50221 *en50221, + int slot, u8 addr) +{ + return sp2_ci_op_cam(en50221, slot, SP2_CI_IO_ACS, + SP2_CI_RD, addr, 0); +} + +int sp2_ci_write_cam_control(struct dvb_ca_en50221 *en50221, + int slot, u8 addr, u8 data) +{ + return sp2_ci_op_cam(en50221, slot, SP2_CI_IO_ACS, + SP2_CI_WR, addr, data); +} + +int sp2_ci_slot_reset(struct dvb_ca_en50221 *en50221, int slot) +{ + struct sp2 *s = en50221->data; + u8 buf; + int ret; + + dev_dbg(&s->client->dev, "slot: %d\n", slot); + + if (slot != 0) + return -EINVAL; + + /* RST on */ + buf = SP2_MOD_CTL_RST; + ret = sp2_write_i2c(s, 0x00, &buf, 1); + + if (ret) + return ret; + + usleep_range(500, 600); + + /* RST off */ + buf = 0x00; + ret = sp2_write_i2c(s, 0x00, &buf, 1); + + if (ret) + return ret; + + msleep(1000); + + return 0; +} + +int sp2_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot) +{ + struct sp2 *s = en50221->data; + + dev_dbg(&s->client->dev, "slot:%d\n", slot); + + /* not implemented */ + return 0; +} + +int sp2_ci_slot_ts_enable(struct dvb_ca_en50221 *en50221, int slot) +{ + struct sp2 *s = en50221->data; + u8 buf; + + dev_dbg(&s->client->dev, "slot:%d\n", slot); + + if (slot != 0) + return -EINVAL; + + sp2_read_i2c(s, 0x00, &buf, 1); + + /* disable bypass and enable TS */ + buf |= (SP2_MOD_CTL_TSOEN | SP2_MOD_CTL_TSIEN); + return sp2_write_i2c(s, 0, &buf, 1); +} + +int sp2_ci_poll_slot_status(struct dvb_ca_en50221 *en50221, + int slot, int open) +{ + struct sp2 *s = en50221->data; + u8 buf[2]; + int ret; + + dev_dbg(&s->client->dev, "slot:%d open:%d\n", slot, open); + + /* + * CAM module INSERT/REMOVE processing. Slow operation because of i2c + * transfers. Throttle read to one per sec. + */ + if (time_after(jiffies, s->next_status_checked_time)) { + ret = sp2_read_i2c(s, 0x00, buf, 1); + s->next_status_checked_time = jiffies + msecs_to_jiffies(1000); + + if (ret) + return 0; + + if (buf[0] & SP2_MOD_CTL_DET) + s->status = DVB_CA_EN50221_POLL_CAM_PRESENT | + DVB_CA_EN50221_POLL_CAM_READY; + else + s->status = 0; + } + + return s->status; +} + +static int sp2_init(struct sp2 *s) +{ + int ret = 0; + u8 buf; + u8 cimax_init[34] = { + 0x00, /* module A control*/ + 0x00, /* auto select mask high A */ + 0x00, /* auto select mask low A */ + 0x00, /* auto select pattern high A */ + 0x00, /* auto select pattern low A */ + 0x44, /* memory access time A, 600 ns */ + 0x00, /* invert input A */ + 0x00, /* RFU */ + 0x00, /* RFU */ + 0x00, /* module B control*/ + 0x00, /* auto select mask high B */ + 0x00, /* auto select mask low B */ + 0x00, /* auto select pattern high B */ + 0x00, /* auto select pattern low B */ + 0x44, /* memory access time B, 600 ns */ + 0x00, /* invert input B */ + 0x00, /* RFU */ + 0x00, /* RFU */ + 0x00, /* auto select mask high Ext */ + 0x00, /* auto select mask low Ext */ + 0x00, /* auto select pattern high Ext */ + 0x00, /* auto select pattern low Ext */ + 0x00, /* RFU */ + 0x02, /* destination - module A */ + 0x01, /* power control reg, VCC power on */ + 0x00, /* RFU */ + 0x00, /* int status read only */ + 0x00, /* Interrupt Mask Register */ + 0x05, /* EXTINT=active-high, INT=push-pull */ + 0x00, /* USCG1 */ + 0x04, /* ack active low */ + 0x00, /* LOCK = 0 */ + 0x22, /* unknown */ + 0x00, /* synchronization? */ + }; + + dev_dbg(&s->client->dev, "\n"); + + s->ca.owner = THIS_MODULE; + s->ca.read_attribute_mem = sp2_ci_read_attribute_mem; + s->ca.write_attribute_mem = sp2_ci_write_attribute_mem; + s->ca.read_cam_control = sp2_ci_read_cam_control; + s->ca.write_cam_control = sp2_ci_write_cam_control; + s->ca.slot_reset = sp2_ci_slot_reset; + s->ca.slot_shutdown = sp2_ci_slot_shutdown; + s->ca.slot_ts_enable = sp2_ci_slot_ts_enable; + s->ca.poll_slot_status = sp2_ci_poll_slot_status; + s->ca.data = s; + s->module_access_type = 0; + + /* initialize all regs */ + ret = sp2_write_i2c(s, 0x00, &cimax_init[0], 34); + if (ret) + goto err; + + /* lock registers */ + buf = 1; + ret = sp2_write_i2c(s, 0x1f, &buf, 1); + if (ret) + goto err; + + /* power on slots */ + ret = sp2_write_i2c(s, 0x18, &buf, 1); + if (ret) + goto err; + + ret = dvb_ca_en50221_init(s->dvb_adap, &s->ca, 0, 1); + if (ret) + goto err; + + return 0; + +err: + dev_dbg(&s->client->dev, "init failed=%d\n", ret); + return ret; +} + +static int sp2_exit(struct i2c_client *client) +{ + struct sp2 *s; + + dev_dbg(&client->dev, "\n"); + + if (client == NULL) + return 0; + + s = i2c_get_clientdata(client); + if (s == NULL) + return 0; + + if (s->ca.data == NULL) + return 0; + + dvb_ca_en50221_release(&s->ca); + + return 0; +} + +static int sp2_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sp2_config *cfg = client->dev.platform_data; + struct sp2 *s; + int ret; + + dev_dbg(&client->dev, "\n"); + + s = kzalloc(sizeof(struct sp2), GFP_KERNEL); + if (!s) { + ret = -ENOMEM; + dev_err(&client->dev, "kzalloc() failed\n"); + goto err; + } + + s->client = client; + s->dvb_adap = cfg->dvb_adap; + s->priv = cfg->priv; + s->ci_control = cfg->ci_control; + + i2c_set_clientdata(client, s); + + ret = sp2_init(s); + if (ret) + goto err; + + dev_info(&s->client->dev, "CIMaX SP2 successfully attached\n"); + return 0; +err: + dev_dbg(&client->dev, "init failed=%d\n", ret); + kfree(s); + + return ret; +} + +static int sp2_remove(struct i2c_client *client) +{ + struct sp2 *s = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "\n"); + + sp2_exit(client); + if (s != NULL) + kfree(s); + + return 0; +} + +static const struct i2c_device_id sp2_id[] = { + {"sp2", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, sp2_id); + +static struct i2c_driver sp2_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "sp2", + }, + .probe = sp2_probe, + .remove = sp2_remove, + .id_table = sp2_id, +}; + +module_i2c_driver(sp2_driver); + +MODULE_DESCRIPTION("CIMaX SP2/HF CI driver"); +MODULE_AUTHOR("Olli Salonen "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-frontends/sp2.h b/drivers/media/dvb-frontends/sp2.h new file mode 100644 index 00000000000000..6cceea022d49ec --- /dev/null +++ b/drivers/media/dvb-frontends/sp2.h @@ -0,0 +1,53 @@ +/* + * CIMaX SP2/HF CI driver + * + * Copyright (C) 2014 Olli Salonen + * + * 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. + */ + +#ifndef SP2_H +#define SP2_H + +#include +#include "dvb_ca_en50221.h" + +/* + * I2C address + * 0x40 (port 0) + * 0x41 (port 1) + */ +struct sp2_config { + /* dvb_adapter to attach the ci to */ + struct dvb_adapter *dvb_adap; + + /* function ci_control handles the device specific ci ops */ + void *ci_control; + + /* priv is passed back to function ci_control */ + void *priv; +}; + +extern int sp2_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr); +extern int sp2_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr, u8 data); +extern int sp2_ci_read_cam_control(struct dvb_ca_en50221 *en50221, + int slot, u8 addr); +extern int sp2_ci_write_cam_control(struct dvb_ca_en50221 *en50221, + int slot, u8 addr, u8 data); +extern int sp2_ci_slot_reset(struct dvb_ca_en50221 *en50221, int slot); +extern int sp2_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot); +extern int sp2_ci_slot_ts_enable(struct dvb_ca_en50221 *en50221, int slot); +extern int sp2_ci_poll_slot_status(struct dvb_ca_en50221 *en50221, + int slot, int open); + +#endif diff --git a/drivers/media/dvb-frontends/sp2_priv.h b/drivers/media/dvb-frontends/sp2_priv.h new file mode 100644 index 00000000000000..37fef7bcd63f5e --- /dev/null +++ b/drivers/media/dvb-frontends/sp2_priv.h @@ -0,0 +1,50 @@ +/* + * CIMaX SP2/HF CI driver + * + * Copyright (C) 2014 Olli Salonen + * + * 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. + */ + +#ifndef SP2_PRIV_H +#define SP2_PRIV_H + +#include "sp2.h" +#include "dvb_frontend.h" + +/* state struct */ +struct sp2 { + int status; + struct i2c_client *client; + struct dvb_adapter *dvb_adap; + struct dvb_ca_en50221 ca; + int module_access_type; + unsigned long next_status_checked_time; + void *priv; + void *ci_control; +}; + +#define SP2_CI_ATTR_ACS 0x00 +#define SP2_CI_IO_ACS 0x04 +#define SP2_CI_WR 0 +#define SP2_CI_RD 1 + +/* Module control register (0x00 module A, 0x09 module B) bits */ +#define SP2_MOD_CTL_DET 0x01 +#define SP2_MOD_CTL_AUTO 0x02 +#define SP2_MOD_CTL_ACS0 0x04 +#define SP2_MOD_CTL_ACS1 0x08 +#define SP2_MOD_CTL_HAD 0x10 +#define SP2_MOD_CTL_TSIEN 0x20 +#define SP2_MOD_CTL_TSOEN 0x40 +#define SP2_MOD_CTL_RST 0x80 + +#endif diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig index 15665debc57243..8c49e8af87e95f 100644 --- a/drivers/media/tuners/Kconfig +++ b/drivers/media/tuners/Kconfig @@ -1,7 +1,7 @@ # Analog TV tuners, auto-loaded via tuner.ko config MEDIA_TUNER tristate - depends on (MEDIA_ANALOG_TV_SUPPORT || MEDIA_RADIO_SUPPORT) && I2C + depends on (MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT || MEDIA_RADIO_SUPPORT || MEDIA_SDR_SUPPORT) && I2C default y select MEDIA_TUNER_XC2028 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_XC5000 if MEDIA_SUBDRV_AUTOSELECT @@ -16,7 +16,7 @@ config MEDIA_TUNER menu "Customize TV tuners" visible if !MEDIA_SUBDRV_AUTOSELECT - depends on MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT || MEDIA_RADIO_SUPPORT + depends on MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT || MEDIA_RADIO_SUPPORT || MEDIA_SDR_SUPPORT config MEDIA_TUNER_SIMPLE tristate "Simple tuner support" @@ -215,6 +215,22 @@ config MEDIA_TUNER_FC2580 help FCI FC2580 silicon tuner driver. +config MEDIA_TUNER_M88TS2022 + tristate "Montage M88TS2022 silicon tuner" + depends on MEDIA_SUPPORT && I2C + select REGMAP_I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Montage M88TS2022 silicon tuner driver. + +config MEDIA_TUNER_M88RS6000T + tristate "Montage M88RS6000 internal tuner" + depends on MEDIA_SUPPORT && I2C + select REGMAP_I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Montage M88RS6000 internal tuner. + config MEDIA_TUNER_TUA9001 tristate "Infineon TUA 9001 silicon tuner" depends on MEDIA_SUPPORT && I2C @@ -222,6 +238,13 @@ config MEDIA_TUNER_TUA9001 help Infineon TUA 9001 silicon tuner driver. +config MEDIA_TUNER_SI2157 + tristate "Silicon Labs Si2157 silicon tuner" + depends on MEDIA_SUPPORT && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Silicon Labs Si2157 silicon tuner driver. + config MEDIA_TUNER_IT913X tristate "ITE Tech IT913x silicon tuner" depends on MEDIA_SUPPORT && I2C diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile index 308f108eadba7c..a6ff0c628dfaa8 100644 --- a/drivers/media/tuners/Makefile +++ b/drivers/media/tuners/Makefile @@ -31,6 +31,8 @@ obj-$(CONFIG_MEDIA_TUNER_TDA18212) += tda18212.o obj-$(CONFIG_MEDIA_TUNER_E4000) += e4000.o obj-$(CONFIG_MEDIA_TUNER_FC2580) += fc2580.o obj-$(CONFIG_MEDIA_TUNER_TUA9001) += tua9001.o +obj-$(CONFIG_MEDIA_TUNER_SI2157) += si2157.o +obj-$(CONFIG_MEDIA_TUNER_M88TS2022) += m88ts2022.o obj-$(CONFIG_MEDIA_TUNER_FC0011) += fc0011.o obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o diff --git a/drivers/media/tuners/m88ts2022.c b/drivers/media/tuners/m88ts2022.c new file mode 100644 index 00000000000000..066e5431da930f --- /dev/null +++ b/drivers/media/tuners/m88ts2022.c @@ -0,0 +1,579 @@ +/* + * Montage M88TS2022 silicon tuner driver + * + * Copyright (C) 2013 Antti Palosaari + * + * 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. + * + * Some calculations are taken from existing TS2020 driver. + */ + +#include "m88ts2022_priv.h" + +static int m88ts2022_cmd(struct m88ts2022_dev *dev, int op, int sleep, u8 reg, + u8 mask, u8 val, u8 *reg_val) +{ + int ret, i; + unsigned int utmp; + struct m88ts2022_reg_val reg_vals[] = { + {0x51, 0x1f - op}, + {0x51, 0x1f}, + {0x50, 0x00 + op}, + {0x50, 0x00}, + }; + + for (i = 0; i < 2; i++) { + dev_dbg(&dev->client->dev, + "i=%d op=%02x reg=%02x mask=%02x val=%02x\n", + i, op, reg, mask, val); + + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { + ret = regmap_write(dev->regmap, reg_vals[i].reg, + reg_vals[i].val); + if (ret) + goto err; + } + + usleep_range(sleep * 1000, sleep * 10000); + + ret = regmap_read(dev->regmap, reg, &utmp); + if (ret) + goto err; + + if ((utmp & mask) != val) + break; + } + + if (reg_val) + *reg_val = utmp; +err: + return ret; +} + +static int m88ts2022_set_params(struct dvb_frontend *fe) +{ + struct m88ts2022_dev *dev = fe->tuner_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + unsigned int utmp, frequency_khz, frequency_offset_khz, f_3db_hz; + unsigned int f_ref_khz, f_vco_khz, div_ref, div_out, pll_n, gdiv28; + u8 buf[3], u8tmp, cap_code, lpf_gm, lpf_mxdiv, div_max, div_min; + u16 u16tmp; + + dev_dbg(&dev->client->dev, + "frequency=%d symbol_rate=%d rolloff=%d\n", + c->frequency, c->symbol_rate, c->rolloff); + /* + * Integer-N PLL synthesizer + * kHz is used for all calculations to keep calculations within 32-bit + */ + f_ref_khz = DIV_ROUND_CLOSEST(dev->cfg.clock, 1000); + div_ref = DIV_ROUND_CLOSEST(f_ref_khz, 2000); + + if (c->symbol_rate < 5000000) + frequency_offset_khz = 3000; /* 3 MHz */ + else + frequency_offset_khz = 0; + + frequency_khz = c->frequency + frequency_offset_khz; + + if (frequency_khz < 1103000) { + div_out = 4; + u8tmp = 0x1b; + } else { + div_out = 2; + u8tmp = 0x0b; + } + + buf[0] = u8tmp; + buf[1] = 0x40; + ret = regmap_bulk_write(dev->regmap, 0x10, buf, 2); + if (ret) + goto err; + + f_vco_khz = frequency_khz * div_out; + pll_n = f_vco_khz * div_ref / f_ref_khz; + pll_n += pll_n % 2; + dev->frequency_khz = pll_n * f_ref_khz / div_ref / div_out; + + if (pll_n < 4095) + u16tmp = pll_n - 1024; + else if (pll_n < 6143) + u16tmp = pll_n + 1024; + else + u16tmp = pll_n + 3072; + + buf[0] = (u16tmp >> 8) & 0x3f; + buf[1] = (u16tmp >> 0) & 0xff; + buf[2] = div_ref - 8; + ret = regmap_bulk_write(dev->regmap, 0x01, buf, 3); + if (ret) + goto err; + + dev_dbg(&dev->client->dev, + "frequency=%u offset=%d f_vco_khz=%u pll_n=%u div_ref=%u div_out=%u\n", + dev->frequency_khz, dev->frequency_khz - c->frequency, + f_vco_khz, pll_n, div_ref, div_out); + + ret = m88ts2022_cmd(dev, 0x10, 5, 0x15, 0x40, 0x00, NULL); + if (ret) + goto err; + + ret = regmap_read(dev->regmap, 0x14, &utmp); + if (ret) + goto err; + + utmp &= 0x7f; + if (utmp < 64) { + ret = regmap_update_bits(dev->regmap, 0x10, 0x80, 0x80); + if (ret) + goto err; + + ret = regmap_write(dev->regmap, 0x11, 0x6f); + if (ret) + goto err; + + ret = m88ts2022_cmd(dev, 0x10, 5, 0x15, 0x40, 0x00, NULL); + if (ret) + goto err; + } + + ret = regmap_read(dev->regmap, 0x14, &utmp); + if (ret) + goto err; + + utmp &= 0x1f; + if (utmp > 19) { + ret = regmap_update_bits(dev->regmap, 0x10, 0x02, 0x00); + if (ret) + goto err; + } + + ret = m88ts2022_cmd(dev, 0x08, 5, 0x3c, 0xff, 0x00, NULL); + if (ret) + goto err; + + ret = regmap_write(dev->regmap, 0x25, 0x00); + if (ret) + goto err; + + ret = regmap_write(dev->regmap, 0x27, 0x70); + if (ret) + goto err; + + ret = regmap_write(dev->regmap, 0x41, 0x09); + if (ret) + goto err; + + ret = regmap_write(dev->regmap, 0x08, 0x0b); + if (ret) + goto err; + + /* filters */ + gdiv28 = DIV_ROUND_CLOSEST(f_ref_khz * 1694U, 1000000U); + + ret = regmap_write(dev->regmap, 0x04, gdiv28); + if (ret) + goto err; + + ret = m88ts2022_cmd(dev, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); + if (ret) + goto err; + + cap_code = u8tmp & 0x3f; + + ret = regmap_write(dev->regmap, 0x41, 0x0d); + if (ret) + goto err; + + ret = m88ts2022_cmd(dev, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); + if (ret) + goto err; + + u8tmp &= 0x3f; + cap_code = (cap_code + u8tmp) / 2; + gdiv28 = gdiv28 * 207 / (cap_code * 2 + 151); + div_max = gdiv28 * 135 / 100; + div_min = gdiv28 * 78 / 100; + div_max = clamp_val(div_max, 0U, 63U); + + f_3db_hz = mult_frac(c->symbol_rate, 135, 200); + f_3db_hz += 2000000U + (frequency_offset_khz * 1000U); + f_3db_hz = clamp(f_3db_hz, 7000000U, 40000000U); + +#define LPF_COEFF 3200U + lpf_gm = DIV_ROUND_CLOSEST(f_3db_hz * gdiv28, LPF_COEFF * f_ref_khz); + lpf_gm = clamp_val(lpf_gm, 1U, 23U); + + lpf_mxdiv = DIV_ROUND_CLOSEST(lpf_gm * LPF_COEFF * f_ref_khz, f_3db_hz); + if (lpf_mxdiv < div_min) + lpf_mxdiv = DIV_ROUND_CLOSEST(++lpf_gm * LPF_COEFF * f_ref_khz, f_3db_hz); + lpf_mxdiv = clamp_val(lpf_mxdiv, 0U, div_max); + + ret = regmap_write(dev->regmap, 0x04, lpf_mxdiv); + if (ret) + goto err; + + ret = regmap_write(dev->regmap, 0x06, lpf_gm); + if (ret) + goto err; + + ret = m88ts2022_cmd(dev, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); + if (ret) + goto err; + + cap_code = u8tmp & 0x3f; + + ret = regmap_write(dev->regmap, 0x41, 0x09); + if (ret) + goto err; + + ret = m88ts2022_cmd(dev, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); + if (ret) + goto err; + + u8tmp &= 0x3f; + cap_code = (cap_code + u8tmp) / 2; + + u8tmp = cap_code | 0x80; + ret = regmap_write(dev->regmap, 0x25, u8tmp); + if (ret) + goto err; + + ret = regmap_write(dev->regmap, 0x27, 0x30); + if (ret) + goto err; + + ret = regmap_write(dev->regmap, 0x08, 0x09); + if (ret) + goto err; + + ret = m88ts2022_cmd(dev, 0x01, 20, 0x21, 0xff, 0x00, NULL); + if (ret) + goto err; +err: + if (ret) + dev_dbg(&dev->client->dev, "failed=%d\n", ret); + + return ret; +} + +static int m88ts2022_init(struct dvb_frontend *fe) +{ + struct m88ts2022_dev *dev = fe->tuner_priv; + int ret, i; + u8 u8tmp; + static const struct m88ts2022_reg_val reg_vals[] = { + {0x7d, 0x9d}, + {0x7c, 0x9a}, + {0x7a, 0x76}, + {0x3b, 0x01}, + {0x63, 0x88}, + {0x61, 0x85}, + {0x22, 0x30}, + {0x30, 0x40}, + {0x20, 0x23}, + {0x24, 0x02}, + {0x12, 0xa0}, + }; + + dev_dbg(&dev->client->dev, "\n"); + + ret = regmap_write(dev->regmap, 0x00, 0x01); + if (ret) + goto err; + + ret = regmap_write(dev->regmap, 0x00, 0x03); + if (ret) + goto err; + + switch (dev->cfg.clock_out) { + case M88TS2022_CLOCK_OUT_DISABLED: + u8tmp = 0x60; + break; + case M88TS2022_CLOCK_OUT_ENABLED: + u8tmp = 0x70; + ret = regmap_write(dev->regmap, 0x05, dev->cfg.clock_out_div); + if (ret) + goto err; + break; + case M88TS2022_CLOCK_OUT_ENABLED_XTALOUT: + u8tmp = 0x6c; + break; + default: + goto err; + } + + ret = regmap_write(dev->regmap, 0x42, u8tmp); + if (ret) + goto err; + + if (dev->cfg.loop_through) + u8tmp = 0xec; + else + u8tmp = 0x6c; + + ret = regmap_write(dev->regmap, 0x62, u8tmp); + if (ret) + goto err; + + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { + ret = regmap_write(dev->regmap, reg_vals[i].reg, reg_vals[i].val); + if (ret) + goto err; + } +err: + if (ret) + dev_dbg(&dev->client->dev, "failed=%d\n", ret); + return ret; +} + +static int m88ts2022_sleep(struct dvb_frontend *fe) +{ + struct m88ts2022_dev *dev = fe->tuner_priv; + int ret; + + dev_dbg(&dev->client->dev, "\n"); + + ret = regmap_write(dev->regmap, 0x00, 0x00); + if (ret) + goto err; +err: + if (ret) + dev_dbg(&dev->client->dev, "failed=%d\n", ret); + return ret; +} + +static int m88ts2022_get_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + struct m88ts2022_dev *dev = fe->tuner_priv; + + dev_dbg(&dev->client->dev, "\n"); + + *frequency = dev->frequency_khz; + return 0; +} + +static int m88ts2022_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + struct m88ts2022_dev *dev = fe->tuner_priv; + + dev_dbg(&dev->client->dev, "\n"); + + *frequency = 0; /* Zero-IF */ + return 0; +} + +static int m88ts2022_get_rf_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct m88ts2022_dev *dev = fe->tuner_priv; + int ret; + u16 gain, u16tmp; + unsigned int utmp, gain1, gain2, gain3; + + ret = regmap_read(dev->regmap, 0x3d, &utmp); + if (ret) + goto err; + + gain1 = (utmp >> 0) & 0x1f; + gain1 = clamp(gain1, 0U, 15U); + + ret = regmap_read(dev->regmap, 0x21, &utmp); + if (ret) + goto err; + + gain2 = (utmp >> 0) & 0x1f; + gain2 = clamp(gain2, 2U, 16U); + + ret = regmap_read(dev->regmap, 0x66, &utmp); + if (ret) + goto err; + + gain3 = (utmp >> 3) & 0x07; + gain3 = clamp(gain3, 0U, 6U); + + gain = gain1 * 265 + gain2 * 338 + gain3 * 285; + + /* scale value to 0x0000-0xffff */ + u16tmp = (0xffff - gain); + u16tmp = clamp_val(u16tmp, 59000U, 61500U); + + *strength = (u16tmp - 59000) * 0xffff / (61500 - 59000); +err: + if (ret) + dev_dbg(&dev->client->dev, "failed=%d\n", ret); + return ret; +} + +static const struct dvb_tuner_ops m88ts2022_tuner_ops = { + .info = { + .name = "Montage M88TS2022", + .frequency_min = 950000, + .frequency_max = 2150000, + }, + + .init = m88ts2022_init, + .sleep = m88ts2022_sleep, + .set_params = m88ts2022_set_params, + + .get_frequency = m88ts2022_get_frequency, + .get_if_frequency = m88ts2022_get_if_frequency, + .get_rf_strength = m88ts2022_get_rf_strength, +}; + +static int m88ts2022_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct m88ts2022_config *cfg = client->dev.platform_data; + struct dvb_frontend *fe = cfg->fe; + struct m88ts2022_dev *dev; + int ret; + u8 u8tmp; + unsigned int utmp; + static const struct regmap_config regmap_config = { + .reg_bits = 8, + .val_bits = 8, + }; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + dev_err(&client->dev, "kzalloc() failed\n"); + goto err; + } + + memcpy(&dev->cfg, cfg, sizeof(struct m88ts2022_config)); + dev->client = client; + dev->regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(dev->regmap)) { + ret = PTR_ERR(dev->regmap); + goto err; + } + + /* check if the tuner is there */ + ret = regmap_read(dev->regmap, 0x00, &utmp); + if (ret) + goto err; + + if ((utmp & 0x03) == 0x00) { + ret = regmap_write(dev->regmap, 0x00, 0x01); + if (ret) + goto err; + + usleep_range(2000, 50000); + } + + ret = regmap_write(dev->regmap, 0x00, 0x03); + if (ret) + goto err; + + usleep_range(2000, 50000); + + ret = regmap_read(dev->regmap, 0x00, &utmp); + if (ret) + goto err; + + dev_dbg(&dev->client->dev, "chip_id=%02x\n", utmp); + + switch (utmp) { + case 0xc3: + case 0x83: + break; + default: + ret = -ENODEV; + goto err; + } + + switch (dev->cfg.clock_out) { + case M88TS2022_CLOCK_OUT_DISABLED: + u8tmp = 0x60; + break; + case M88TS2022_CLOCK_OUT_ENABLED: + u8tmp = 0x70; + ret = regmap_write(dev->regmap, 0x05, dev->cfg.clock_out_div); + if (ret) + goto err; + break; + case M88TS2022_CLOCK_OUT_ENABLED_XTALOUT: + u8tmp = 0x6c; + break; + default: + ret = -EINVAL; + goto err; + } + + ret = regmap_write(dev->regmap, 0x42, u8tmp); + if (ret) + goto err; + + if (dev->cfg.loop_through) + u8tmp = 0xec; + else + u8tmp = 0x6c; + + ret = regmap_write(dev->regmap, 0x62, u8tmp); + if (ret) + goto err; + + /* sleep */ + ret = regmap_write(dev->regmap, 0x00, 0x00); + if (ret) + goto err; + + dev_info(&dev->client->dev, "Montage M88TS2022 successfully identified\n"); + + fe->tuner_priv = dev; + memcpy(&fe->ops.tuner_ops, &m88ts2022_tuner_ops, + sizeof(struct dvb_tuner_ops)); + + i2c_set_clientdata(client, dev); + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + kfree(dev); + return ret; +} + +static int m88ts2022_remove(struct i2c_client *client) +{ + struct m88ts2022_dev *dev = i2c_get_clientdata(client); + struct dvb_frontend *fe = dev->cfg.fe; + + dev_dbg(&client->dev, "\n"); + + memset(&fe->ops.tuner_ops, 0, sizeof(struct dvb_tuner_ops)); + fe->tuner_priv = NULL; + kfree(dev); + + return 0; +} + +static const struct i2c_device_id m88ts2022_id[] = { + {"m88ts2022", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, m88ts2022_id); + +static struct i2c_driver m88ts2022_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "m88ts2022", + }, + .probe = m88ts2022_probe, + .remove = m88ts2022_remove, + .id_table = m88ts2022_id, +}; + +module_i2c_driver(m88ts2022_driver); + +MODULE_DESCRIPTION("Montage M88TS2022 silicon tuner driver"); +MODULE_AUTHOR("Antti Palosaari "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/tuners/m88ts2022.h b/drivers/media/tuners/m88ts2022.h new file mode 100644 index 00000000000000..659fa1b1633a3f --- /dev/null +++ b/drivers/media/tuners/m88ts2022.h @@ -0,0 +1,54 @@ +/* + * Montage M88TS2022 silicon tuner driver + * + * Copyright (C) 2013 Antti Palosaari + * + * 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. + */ + +#ifndef M88TS2022_H +#define M88TS2022_H + +#include "dvb_frontend.h" + +struct m88ts2022_config { + /* + * clock + * 16000000 - 32000000 + */ + u32 clock; + + /* + * RF loop-through + */ + u8 loop_through:1; + + /* + * clock output + */ +#define M88TS2022_CLOCK_OUT_DISABLED 0 +#define M88TS2022_CLOCK_OUT_ENABLED 1 +#define M88TS2022_CLOCK_OUT_ENABLED_XTALOUT 2 + u8 clock_out:2; + + /* + * clock output divider + * 1 - 31 + */ + u8 clock_out_div:5; + + /* + * pointer to DVB frontend + */ + struct dvb_frontend *fe; +}; + +#endif diff --git a/drivers/media/tuners/m88ts2022_priv.h b/drivers/media/tuners/m88ts2022_priv.h new file mode 100644 index 00000000000000..feeb5ad6beef87 --- /dev/null +++ b/drivers/media/tuners/m88ts2022_priv.h @@ -0,0 +1,35 @@ +/* + * Montage M88TS2022 silicon tuner driver + * + * Copyright (C) 2013 Antti Palosaari + * + * 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. + */ + +#ifndef M88TS2022_PRIV_H +#define M88TS2022_PRIV_H + +#include "m88ts2022.h" +#include + +struct m88ts2022_dev { + struct m88ts2022_config cfg; + struct i2c_client *client; + struct regmap *regmap; + u32 frequency_khz; +}; + +struct m88ts2022_reg_val { + u8 reg; + u8 val; +}; + +#endif diff --git a/drivers/media/tuners/si2157.c b/drivers/media/tuners/si2157.c new file mode 100644 index 00000000000000..d8309b9de21e63 --- /dev/null +++ b/drivers/media/tuners/si2157.c @@ -0,0 +1,445 @@ +/* + * Silicon Labs Si2146/2147/2148/2157/2158 silicon tuner driver + * + * Copyright (C) 2014 Antti Palosaari + * + * 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. + */ + +#include "si2157_priv.h" + +static const struct dvb_tuner_ops si2157_ops; + +/* execute firmware command */ +static int si2157_cmd_execute(struct i2c_client *client, struct si2157_cmd *cmd) +{ + struct si2157_dev *dev = i2c_get_clientdata(client); + int ret; + unsigned long timeout; + + mutex_lock(&dev->i2c_mutex); + + if (cmd->wlen) { + /* write cmd and args for firmware */ + ret = i2c_master_send(client, cmd->args, cmd->wlen); + if (ret < 0) { + goto err_mutex_unlock; + } else if (ret != cmd->wlen) { + ret = -EREMOTEIO; + goto err_mutex_unlock; + } + } + + if (cmd->rlen) { + /* wait cmd execution terminate */ + #define TIMEOUT 80 + timeout = jiffies + msecs_to_jiffies(TIMEOUT); + while (!time_after(jiffies, timeout)) { + ret = i2c_master_recv(client, cmd->args, cmd->rlen); + if (ret < 0) { + goto err_mutex_unlock; + } else if (ret != cmd->rlen) { + ret = -EREMOTEIO; + goto err_mutex_unlock; + } + + /* firmware ready? */ + if ((cmd->args[0] >> 7) & 0x01) + break; + } + + dev_dbg(&client->dev, "cmd execution took %d ms\n", + jiffies_to_msecs(jiffies) - + (jiffies_to_msecs(timeout) - TIMEOUT)); + + if (!((cmd->args[0] >> 7) & 0x01)) { + ret = -ETIMEDOUT; + goto err_mutex_unlock; + } + } + + mutex_unlock(&dev->i2c_mutex); + return 0; + +err_mutex_unlock: + mutex_unlock(&dev->i2c_mutex); + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2157_init(struct dvb_frontend *fe) +{ + struct i2c_client *client = fe->tuner_priv; + struct si2157_dev *dev = i2c_get_clientdata(client); + int ret, len, remaining; + struct si2157_cmd cmd; + const struct firmware *fw; + const char *fw_name; + unsigned int chip_id; + + dev_dbg(&client->dev, "\n"); + + if (dev->fw_loaded) + goto warm; + + /* power up */ + if (dev->chiptype == SI2157_CHIPTYPE_SI2146) { + memcpy(cmd.args, "\xc0\x05\x01\x00\x00\x0b\x00\x00\x01", 9); + cmd.wlen = 9; + } else { + memcpy(cmd.args, "\xc0\x00\x0c\x00\x00\x01\x01\x01\x01\x01\x01\x02\x00\x00\x01", 15); + cmd.wlen = 15; + } + cmd.rlen = 1; + ret = si2157_cmd_execute(client, &cmd); + if (ret) + goto err; + + /* query chip revision */ + memcpy(cmd.args, "\x02", 1); + cmd.wlen = 1; + cmd.rlen = 13; + ret = si2157_cmd_execute(client, &cmd); + if (ret) + goto err; + + chip_id = cmd.args[1] << 24 | cmd.args[2] << 16 | cmd.args[3] << 8 | + cmd.args[4] << 0; + + #define SI2158_A20 ('A' << 24 | 58 << 16 | '2' << 8 | '0' << 0) + #define SI2148_A20 ('A' << 24 | 48 << 16 | '2' << 8 | '0' << 0) + #define SI2157_A30 ('A' << 24 | 57 << 16 | '3' << 8 | '0' << 0) + #define SI2147_A30 ('A' << 24 | 47 << 16 | '3' << 8 | '0' << 0) + #define SI2146_A10 ('A' << 24 | 46 << 16 | '1' << 8 | '0' << 0) + + switch (chip_id) { + case SI2158_A20: + case SI2148_A20: + fw_name = SI2158_A20_FIRMWARE; + break; + case SI2157_A30: + case SI2147_A30: + case SI2146_A10: + fw_name = NULL; + break; + default: + dev_err(&client->dev, "unknown chip version Si21%d-%c%c%c\n", + cmd.args[2], cmd.args[1], + cmd.args[3], cmd.args[4]); + ret = -EINVAL; + goto err; + } + + dev_info(&client->dev, "found a 'Silicon Labs Si21%d-%c%c%c'\n", + cmd.args[2], cmd.args[1], cmd.args[3], cmd.args[4]); + + if (fw_name == NULL) + goto skip_fw_download; + + /* request the firmware, this will block and timeout */ + ret = request_firmware(&fw, fw_name, &client->dev); + if (ret) { + dev_err(&client->dev, "firmware file '%s' not found\n", + fw_name); + goto err; + } + + /* firmware should be n chunks of 17 bytes */ + if (fw->size % 17 != 0) { + dev_err(&client->dev, "firmware file '%s' is invalid\n", + fw_name); + ret = -EINVAL; + goto err_release_firmware; + } + + dev_info(&client->dev, "downloading firmware from file '%s'\n", + fw_name); + + for (remaining = fw->size; remaining > 0; remaining -= 17) { + len = fw->data[fw->size - remaining]; + memcpy(cmd.args, &fw->data[(fw->size - remaining) + 1], len); + cmd.wlen = len; + cmd.rlen = 1; + ret = si2157_cmd_execute(client, &cmd); + if (ret) { + dev_err(&client->dev, "firmware download failed %d\n", + ret); + goto err_release_firmware; + } + } + + release_firmware(fw); + +skip_fw_download: + /* reboot the tuner with new firmware? */ + memcpy(cmd.args, "\x01\x01", 2); + cmd.wlen = 2; + cmd.rlen = 1; + ret = si2157_cmd_execute(client, &cmd); + if (ret) + goto err; + + /* query firmware version */ + memcpy(cmd.args, "\x11", 1); + cmd.wlen = 1; + cmd.rlen = 10; + ret = si2157_cmd_execute(client, &cmd); + if (ret) + goto err; + + dev_info(&client->dev, "firmware version: %c.%c.%d\n", + cmd.args[6], cmd.args[7], cmd.args[8]); + + dev->fw_loaded = true; + +warm: + dev->active = true; + return 0; + +err_release_firmware: + release_firmware(fw); +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2157_sleep(struct dvb_frontend *fe) +{ + struct i2c_client *client = fe->tuner_priv; + struct si2157_dev *dev = i2c_get_clientdata(client); + int ret; + struct si2157_cmd cmd; + + dev_dbg(&client->dev, "\n"); + + dev->active = false; + + /* standby */ + memcpy(cmd.args, "\x16\x00", 2); + cmd.wlen = 2; + cmd.rlen = 1; + ret = si2157_cmd_execute(client, &cmd); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2157_set_params(struct dvb_frontend *fe) +{ + struct i2c_client *client = fe->tuner_priv; + struct si2157_dev *dev = i2c_get_clientdata(client); + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + struct si2157_cmd cmd; + u8 bandwidth, delivery_system; + u32 if_frequency = 5000000; + + dev_dbg(&client->dev, + "delivery_system=%d frequency=%u bandwidth_hz=%u\n", + c->delivery_system, c->frequency, c->bandwidth_hz); + + if (!dev->active) { + ret = -EAGAIN; + goto err; + } + + if (c->bandwidth_hz <= 6000000) + bandwidth = 0x06; + else if (c->bandwidth_hz <= 7000000) + bandwidth = 0x07; + else if (c->bandwidth_hz <= 8000000) + bandwidth = 0x08; + else + bandwidth = 0x0f; + + switch (c->delivery_system) { + case SYS_ATSC: + delivery_system = 0x00; + if_frequency = 3250000; + break; + case SYS_DVBC_ANNEX_B: + delivery_system = 0x10; + if_frequency = 4000000; + break; + case SYS_DVBT: + case SYS_DVBT2: /* it seems DVB-T and DVB-T2 both are 0x20 here */ + delivery_system = 0x20; + break; + case SYS_DVBC_ANNEX_A: + delivery_system = 0x30; + break; + default: + ret = -EINVAL; + goto err; + } + + memcpy(cmd.args, "\x14\x00\x03\x07\x00\x00", 6); + cmd.args[4] = delivery_system | bandwidth; + if (dev->inversion) + cmd.args[5] = 0x01; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2157_cmd_execute(client, &cmd); + if (ret) + goto err; + + if (dev->chiptype == SI2157_CHIPTYPE_SI2146) + memcpy(cmd.args, "\x14\x00\x02\x07\x00\x01", 6); + else + memcpy(cmd.args, "\x14\x00\x02\x07\x01\x00", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2157_cmd_execute(client, &cmd); + if (ret) + goto err; + + /* set if frequency if needed */ + if (if_frequency != dev->if_frequency) { + memcpy(cmd.args, "\x14\x00\x06\x07", 4); + cmd.args[4] = (if_frequency / 1000) & 0xff; + cmd.args[5] = ((if_frequency / 1000) >> 8) & 0xff; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2157_cmd_execute(client, &cmd); + if (ret) + goto err; + + dev->if_frequency = if_frequency; + } + + /* set frequency */ + memcpy(cmd.args, "\x41\x00\x00\x00\x00\x00\x00\x00", 8); + cmd.args[4] = (c->frequency >> 0) & 0xff; + cmd.args[5] = (c->frequency >> 8) & 0xff; + cmd.args[6] = (c->frequency >> 16) & 0xff; + cmd.args[7] = (c->frequency >> 24) & 0xff; + cmd.wlen = 8; + cmd.rlen = 1; + ret = si2157_cmd_execute(client, &cmd); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2157_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + struct i2c_client *client = fe->tuner_priv; + struct si2157_dev *dev = i2c_get_clientdata(client); + + *frequency = dev->if_frequency; + return 0; +} + +static const struct dvb_tuner_ops si2157_ops = { + .info = { + .name = "Silicon Labs Si2146/2147/2148/2157/2158", + .frequency_min = 110000000, + .frequency_max = 862000000, + }, + + .init = si2157_init, + .sleep = si2157_sleep, + .set_params = si2157_set_params, + .get_if_frequency = si2157_get_if_frequency, +}; + +static int si2157_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct si2157_config *cfg = client->dev.platform_data; + struct dvb_frontend *fe = cfg->fe; + struct si2157_dev *dev; + struct si2157_cmd cmd; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + dev_err(&client->dev, "kzalloc() failed\n"); + goto err; + } + + i2c_set_clientdata(client, dev); + dev->fe = cfg->fe; + dev->inversion = cfg->inversion; + dev->fw_loaded = false; + dev->chiptype = (u8)id->driver_data; + dev->if_frequency = 5000000; /* default value of property 0x0706 */ + mutex_init(&dev->i2c_mutex); + + /* check if the tuner is there */ + cmd.wlen = 0; + cmd.rlen = 1; + ret = si2157_cmd_execute(client, &cmd); + if (ret) + goto err_kfree; + + memcpy(&fe->ops.tuner_ops, &si2157_ops, sizeof(struct dvb_tuner_ops)); + fe->tuner_priv = client; + + dev_info(&client->dev, "Silicon Labs %s successfully attached\n", + dev->chiptype == SI2157_CHIPTYPE_SI2146 ? + "Si2146" : "Si2147/2148/2157/2158"); + + return 0; + +err_kfree: + kfree(dev); +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2157_remove(struct i2c_client *client) +{ + struct si2157_dev *dev = i2c_get_clientdata(client); + struct dvb_frontend *fe = dev->fe; + + dev_dbg(&client->dev, "\n"); + + memset(&fe->ops.tuner_ops, 0, sizeof(struct dvb_tuner_ops)); + fe->tuner_priv = NULL; + kfree(dev); + + return 0; +} + +static const struct i2c_device_id si2157_id_table[] = { + {"si2157", SI2157_CHIPTYPE_SI2157}, + {"si2146", SI2157_CHIPTYPE_SI2146}, + {} +}; +MODULE_DEVICE_TABLE(i2c, si2157_id_table); + +static struct i2c_driver si2157_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "si2157", + }, + .probe = si2157_probe, + .remove = si2157_remove, + .id_table = si2157_id_table, +}; + +module_i2c_driver(si2157_driver); + +MODULE_DESCRIPTION("Silicon Labs Si2146/2147/2148/2157/2158 silicon tuner driver"); +MODULE_AUTHOR("Antti Palosaari "); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(SI2158_A20_FIRMWARE); diff --git a/drivers/media/tuners/si2157.h b/drivers/media/tuners/si2157.h new file mode 100644 index 00000000000000..a564c4a9fba7e6 --- /dev/null +++ b/drivers/media/tuners/si2157.h @@ -0,0 +1,39 @@ +/* + * Silicon Labs Si2146/2147/2148/2157/2158 silicon tuner driver + * + * Copyright (C) 2014 Antti Palosaari + * + * 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. + */ + +#ifndef SI2157_H +#define SI2157_H + +#include +#include "dvb_frontend.h" + +/* + * I2C address + * 0x60 + */ +struct si2157_config { + /* + * frontend + */ + struct dvb_frontend *fe; + + /* + * Spectral Inversion + */ + bool inversion; +}; + +#endif diff --git a/drivers/media/tuners/si2157_priv.h b/drivers/media/tuners/si2157_priv.h new file mode 100644 index 00000000000000..cd8fa5b2530470 --- /dev/null +++ b/drivers/media/tuners/si2157_priv.h @@ -0,0 +1,47 @@ +/* + * Silicon Labs Si2146/2147/2148/2157/2158 silicon tuner driver + * + * Copyright (C) 2014 Antti Palosaari + * + * 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. + */ + +#ifndef SI2157_PRIV_H +#define SI2157_PRIV_H + +#include +#include "si2157.h" + +/* state struct */ +struct si2157_dev { + struct mutex i2c_mutex; + struct dvb_frontend *fe; + bool active; + bool fw_loaded; + bool inversion; + u8 chiptype; + u32 if_frequency; +}; + +#define SI2157_CHIPTYPE_SI2157 0 +#define SI2157_CHIPTYPE_SI2146 1 + +/* firmware command struct */ +#define SI2157_ARGLEN 30 +struct si2157_cmd { + u8 args[SI2157_ARGLEN]; + unsigned wlen; + unsigned rlen; +}; + +#define SI2158_A20_FIRMWARE "dvb-tuner-si2158-a20-01.fw" + +#endif diff --git a/drivers/media/usb/dvb-usb-v2/Kconfig b/drivers/media/usb/dvb-usb-v2/Kconfig index a3c8ecf22078b4..2a2200fa574e34 100644 --- a/drivers/media/usb/dvb-usb-v2/Kconfig +++ b/drivers/media/usb/dvb-usb-v2/Kconfig @@ -1,6 +1,6 @@ config DVB_USB_V2 tristate "Support for various USB DVB devices v2" - depends on DVB_CORE && USB && I2C + depends on DVB_CORE && USB && I2C && (RC_CORE || RC_CORE=n) help By enabling this you will be able to choose the various supported USB1.1 and USB2.0 DVB devices. @@ -147,3 +147,13 @@ config DVB_USB_RTL28XXU help Say Y here to support the Realtek RTL28xxU DVB USB receiver. +config DVB_USB_DVBSKY + tristate "DVBSky USB support" + depends on DVB_USB_V2 + select DVB_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT + select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_M88TS2022 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT + select DVB_SP2 if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the USB receivers from DVBSky. diff --git a/drivers/media/usb/dvb-usb-v2/Makefile b/drivers/media/usb/dvb-usb-v2/Makefile index 2c06714b9ef03e..926f12d92f9a0c 100644 --- a/drivers/media/usb/dvb-usb-v2/Makefile +++ b/drivers/media/usb/dvb-usb-v2/Makefile @@ -40,6 +40,9 @@ obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-tuner.o dvb-usb-rtl28xxu-objs := rtl28xxu.o obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.o +dvb-usb-dvbsky-objs := dvbsky.o +obj-$(CONFIG_DVB_USB_DVBSKY) += dvb-usb-dvbsky.o + ccflags-y += -I$(srctree)/drivers/media/dvb-core ccflags-y += -I$(srctree)/drivers/media/dvb-frontends ccflags-y += -I$(srctree)/drivers/media/tuners diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb.h b/drivers/media/usb/dvb-usb-v2/dvb_usb.h index 658c6d47fdff7f..81e4501f94359c 100644 --- a/drivers/media/usb/dvb-usb-v2/dvb_usb.h +++ b/drivers/media/usb/dvb-usb-v2/dvb_usb.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "dvb_frontend.h" #include "dvb_demux.h" @@ -140,7 +141,7 @@ struct dvb_usb_rc { int (*change_protocol)(struct rc_dev *dev, u64 *rc_type); int (*query) (struct dvb_usb_device *d); unsigned int interval; - const enum rc_driver_type driver_type; + enum rc_driver_type driver_type; bool bulk_mode; }; @@ -214,6 +215,7 @@ struct dvb_usb_adapter_properties { * @read_config: called to resolve device configuration * @read_mac_address: called to resolve adapter mac-address * @frontend_attach: called to attach the possible frontends + * @frontend_detach: called to detach the possible frontends * @tuner_attach: called to attach the possible tuners * @frontend_ctrl: called to power on/off active frontend * @streaming_ctrl: called to start/stop the usb streaming of adapter @@ -254,7 +256,9 @@ struct dvb_usb_device_properties { int (*read_config) (struct dvb_usb_device *d); int (*read_mac_address) (struct dvb_usb_adapter *, u8 []); int (*frontend_attach) (struct dvb_usb_adapter *); + int (*frontend_detach)(struct dvb_usb_adapter *); int (*tuner_attach) (struct dvb_usb_adapter *); + int (*tuner_detach)(struct dvb_usb_adapter *); int (*frontend_ctrl) (struct dvb_frontend *, int); int (*streaming_ctrl) (struct dvb_frontend *, int); int (*init) (struct dvb_usb_device *); @@ -351,10 +355,9 @@ struct dvb_usb_adapter { * @name: device name * @rc_map: name of rc codes table * @rc_polling_active: set when RC polling is active + * @intf: pointer to the device's struct usb_interface * @udev: pointer to the device's struct usb_device - * @intf: pointer to the device's usb interface * @rc: remote controller configuration - * @probe_work: work to defer .probe() * @powered: indicated whether the device is power or not * @usb_mutex: mutex for usb control messages * @i2c_mutex: mutex for i2c-transfers @@ -369,11 +372,9 @@ struct dvb_usb_device { const char *name; const char *rc_map; bool rc_polling_active; - struct usb_device *udev; struct usb_interface *intf; + struct usb_device *udev; struct dvb_usb_rc rc; - struct work_struct probe_work; - pid_t work_pid; int powered; /* locking */ @@ -409,3 +410,4 @@ extern int dvb_usbv2_generic_rw_locked(struct dvb_usb_device *, extern int dvb_usbv2_generic_write_locked(struct dvb_usb_device *, u8 *, u16); #endif + diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c index 19f6737d98173e..82d6db33c1cfff 100644 --- a/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c +++ b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c @@ -21,7 +21,7 @@ #include "dvb_usb_common.h" -int dvb_usbv2_disable_rc_polling; +static int dvb_usbv2_disable_rc_polling; module_param_named(disable_rc_polling, dvb_usbv2_disable_rc_polling, int, 0644); MODULE_PARM_DESC(disable_rc_polling, "disable remote control polling (default: 0)"); @@ -273,8 +273,7 @@ static int dvb_usb_start_feed(struct dvb_demux_feed *dvbdmxfeed) dvbdmxfeed->pid, dvbdmxfeed->index); /* wait init is done */ - wait_on_bit(&adap->state_bits, ADAP_INIT, wait_schedule, - TASK_UNINTERRUPTIBLE); + wait_on_bit(&adap->state_bits, ADAP_INIT, wait_schedule, TASK_UNINTERRUPTIBLE); if (adap->active_fe == -1) return -EINVAL; @@ -568,8 +567,8 @@ static int dvb_usb_fe_sleep(struct dvb_frontend *fe) if (!adap->suspend_resume_active) { set_bit(ADAP_SLEEP, &adap->state_bits); - wait_on_bit(&adap->state_bits, ADAP_STREAMING, wait_schedule, - TASK_UNINTERRUPTIBLE); + wait_on_bit(&adap->state_bits, ADAP_STREAMING, + wait_schedule, TASK_UNINTERRUPTIBLE); } if (adap->fe_sleep[fe->id]) { @@ -672,9 +671,10 @@ static int dvb_usbv2_adapter_frontend_init(struct dvb_usb_adapter *adap) static int dvb_usbv2_adapter_frontend_exit(struct dvb_usb_adapter *adap) { - int i; - dev_dbg(&adap_to_d(adap)->udev->dev, "%s: adap=%d\n", __func__, - adap->id); + int ret, i; + struct dvb_usb_device *d = adap_to_d(adap); + + dev_dbg(&d->udev->dev, "%s: adap=%d\n", __func__, adap->id); for (i = MAX_NO_OF_FE_PER_ADAP - 1; i >= 0; i--) { if (adap->fe[i]) { @@ -683,6 +683,23 @@ static int dvb_usbv2_adapter_frontend_exit(struct dvb_usb_adapter *adap) } } + if (d->props->tuner_detach) { + ret = d->props->tuner_detach(adap); + if (ret < 0) { + dev_dbg(&d->udev->dev, "%s: tuner_detach() failed=%d\n", + __func__, ret); + } + } + + if (d->props->frontend_detach) { + ret = d->props->frontend_detach(adap); + if (ret < 0) { + dev_dbg(&d->udev->dev, + "%s: frontend_detach() failed=%d\n", + __func__, ret); + } + } + return 0; } @@ -770,9 +787,9 @@ static int dvb_usbv2_adapter_exit(struct dvb_usb_device *d) for (i = MAX_NO_OF_ADAPTER_PER_DEVICE - 1; i >= 0; i--) { if (d->adapter[i].props) { - dvb_usbv2_adapter_frontend_exit(&d->adapter[i]); dvb_usbv2_adapter_dvb_exit(&d->adapter[i]); dvb_usbv2_adapter_stream_exit(&d->adapter[i]); + dvb_usbv2_adapter_frontend_exit(&d->adapter[i]); } } @@ -833,20 +850,44 @@ static int dvb_usbv2_init(struct dvb_usb_device *d) return ret; } -/* - * udev, which is used for the firmware downloading, requires we cannot - * block during module_init(). module_init() calls USB probe() which - * is this routine. Due to that we delay actual operation using workqueue - * and return always success here. - */ -static void dvb_usbv2_init_work(struct work_struct *work) +int dvb_usbv2_probe(struct usb_interface *intf, + const struct usb_device_id *id) { int ret; - struct dvb_usb_device *d = - container_of(work, struct dvb_usb_device, probe_work); + struct dvb_usb_device *d; + struct usb_device *udev = interface_to_usbdev(intf); + struct dvb_usb_driver_info *driver_info = + (struct dvb_usb_driver_info *) id->driver_info; + + dev_dbg(&udev->dev, "%s: bInterfaceNumber=%d\n", __func__, + intf->cur_altsetting->desc.bInterfaceNumber); + + if (!id->driver_info) { + dev_err(&udev->dev, "%s: driver_info failed\n", KBUILD_MODNAME); + ret = -ENODEV; + goto err; + } + + d = kzalloc(sizeof(struct dvb_usb_device), GFP_KERNEL); + if (!d) { + dev_err(&udev->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); + ret = -ENOMEM; + goto err; + } + + d->name = driver_info->name; + d->rc_map = driver_info->rc_map; + d->udev = udev; + d->props = driver_info->props; + + if (intf->cur_altsetting->desc.bInterfaceNumber != + d->props->bInterfaceNumber) { + ret = -ENODEV; + goto err_free_all; + } - d->work_pid = current->pid; - dev_dbg(&d->udev->dev, "%s: work_pid=%d\n", __func__, d->work_pid); + mutex_init(&d->usb_mutex); + mutex_init(&d->i2c_mutex); if (d->props->size_of_priv) { d->priv = kzalloc(d->props->size_of_priv, GFP_KERNEL); @@ -854,7 +895,7 @@ static void dvb_usbv2_init_work(struct work_struct *work) dev_err(&d->udev->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); ret = -ENOMEM; - goto err_usb_driver_release_interface; + goto err_free_all; } } @@ -884,20 +925,12 @@ static void dvb_usbv2_init_work(struct work_struct *work) * device. As 'new' device is warm we should * never go here again. */ - return; + goto exit; } else { - /* - * Unexpected error. We must unregister driver - * manually from the device, because device is - * already register by returning from probe() - * with success. usb_driver_release_interface() - * finally calls disconnect() in order to free - * resources. - */ - goto err_usb_driver_release_interface; + goto err_free_all; } } else { - goto err_usb_driver_release_interface; + goto err_free_all; } } @@ -906,73 +939,17 @@ static void dvb_usbv2_init_work(struct work_struct *work) ret = dvb_usbv2_init(d); if (ret < 0) - goto err_usb_driver_release_interface; + goto err_free_all; dev_info(&d->udev->dev, "%s: '%s' successfully initialized and connected\n", KBUILD_MODNAME, d->name); - - return; -err_usb_driver_release_interface: - dev_info(&d->udev->dev, "%s: '%s' error while loading driver (%d)\n", - KBUILD_MODNAME, d->name, ret); - usb_driver_release_interface(to_usb_driver(d->intf->dev.driver), - d->intf); - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); - return; -} - -int dvb_usbv2_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - int ret; - struct dvb_usb_device *d; - struct usb_device *udev = interface_to_usbdev(intf); - struct dvb_usb_driver_info *driver_info = - (struct dvb_usb_driver_info *) id->driver_info; - - dev_dbg(&udev->dev, "%s: bInterfaceNumber=%d\n", __func__, - intf->cur_altsetting->desc.bInterfaceNumber); - - if (!id->driver_info) { - dev_err(&udev->dev, "%s: driver_info failed\n", KBUILD_MODNAME); - ret = -ENODEV; - goto err; - } - - d = kzalloc(sizeof(struct dvb_usb_device), GFP_KERNEL); - if (!d) { - dev_err(&udev->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); - ret = -ENOMEM; - goto err; - } - - d->name = driver_info->name; - d->rc_map = driver_info->rc_map; - d->udev = udev; - d->intf = intf; - d->props = driver_info->props; - - if (d->intf->cur_altsetting->desc.bInterfaceNumber != - d->props->bInterfaceNumber) { - ret = -ENODEV; - goto err_kfree; - } - - mutex_init(&d->usb_mutex); - mutex_init(&d->i2c_mutex); - INIT_WORK(&d->probe_work, dvb_usbv2_init_work); +exit: usb_set_intfdata(intf, d); - ret = schedule_work(&d->probe_work); - if (ret < 0) { - dev_err(&d->udev->dev, "%s: schedule_work() failed\n", - KBUILD_MODNAME); - goto err_kfree; - } return 0; -err_kfree: - kfree(d); +err_free_all: + dvb_usbv2_exit(d); err: dev_dbg(&udev->dev, "%s: failed=%d\n", __func__, ret); return ret; @@ -984,12 +961,8 @@ void dvb_usbv2_disconnect(struct usb_interface *intf) struct dvb_usb_device *d = usb_get_intfdata(intf); const char *name = d->name; struct device dev = d->udev->dev; - dev_dbg(&d->udev->dev, "%s: pid=%d work_pid=%d\n", __func__, - current->pid, d->work_pid); - - /* ensure initialization work is finished until release resources */ - if (d->work_pid != current->pid) - cancel_work_sync(&d->probe_work); + dev_dbg(&d->udev->dev, "%s: bInterfaceNumber=%d\n", __func__, + intf->cur_altsetting->desc.bInterfaceNumber); if (d->props->exit) d->props->exit(d); diff --git a/drivers/media/usb/dvb-usb-v2/dvbsky.c b/drivers/media/usb/dvb-usb-v2/dvbsky.c new file mode 100644 index 00000000000000..a99ec7b1daa9d8 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/dvbsky.c @@ -0,0 +1,869 @@ +/* + * Driver for DVBSky USB2.0 receiver + * + * Copyright (C) 2013 Max nibble + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dvb_usb.h" +#include "m88ds3103.h" +#include "m88ts2022.h" +#include "sp2.h" +#include "si2168.h" +#include "si2157.h" + +#define DVBSKY_MSG_DELAY 0/*2000*/ +#define DVBSKY_BUF_LEN 64 + +static int dvb_usb_dvbsky_disable_rc; +module_param_named(disable_rc, dvb_usb_dvbsky_disable_rc, int, 0644); +MODULE_PARM_DESC(disable_rc, "Disable inbuilt IR receiver."); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +struct dvbsky_state { + struct mutex stream_mutex; + u8 ibuf[DVBSKY_BUF_LEN]; + u8 obuf[DVBSKY_BUF_LEN]; + u8 last_lock; + struct i2c_client *i2c_client_demod; + struct i2c_client *i2c_client_tuner; + struct i2c_client *i2c_client_ci; + + /* fe hook functions*/ + int (*fe_set_voltage)(struct dvb_frontend *fe, + fe_sec_voltage_t voltage); + int (*fe_read_status)(struct dvb_frontend *fe, + fe_status_t *status); +}; + +static int dvbsky_usb_generic_rw(struct dvb_usb_device *d, + u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) +{ + int ret; + struct dvbsky_state *state = d_to_priv(d); + + mutex_lock(&d->usb_mutex); + if (wlen != 0) + memcpy(state->obuf, wbuf, wlen); + + ret = dvb_usbv2_generic_rw_locked(d, state->obuf, wlen, + state->ibuf, rlen); + + if (!ret && (rlen != 0)) + memcpy(rbuf, state->ibuf, rlen); + + mutex_unlock(&d->usb_mutex); + return ret; +} + +static int dvbsky_stream_ctrl(struct dvb_usb_device *d, u8 onoff) +{ + struct dvbsky_state *state = d_to_priv(d); + int ret; + u8 obuf_pre[3] = { 0x37, 0, 0 }; + u8 obuf_post[3] = { 0x36, 3, 0 }; + + mutex_lock(&state->stream_mutex); + ret = dvbsky_usb_generic_rw(d, obuf_pre, 3, NULL, 0); + if (!ret && onoff) { + msleep(20); + ret = dvbsky_usb_generic_rw(d, obuf_post, 3, NULL, 0); + } + mutex_unlock(&state->stream_mutex); + return ret; +} + +static int dvbsky_streaming_ctrl(struct dvb_frontend *fe, int onoff) +{ + struct dvb_usb_device *d = fe_to_d(fe); + + return dvbsky_stream_ctrl(d, (onoff == 0) ? 0 : 1); +} + +/* GPIO */ +static int dvbsky_gpio_ctrl(struct dvb_usb_device *d, u8 gport, u8 value) +{ + int ret; + u8 obuf[3], ibuf[2]; + + obuf[0] = 0x0e; + obuf[1] = gport; + obuf[2] = value; + ret = dvbsky_usb_generic_rw(d, obuf, 3, ibuf, 1); + if (ret) + dev_err(&d->udev->dev, "failed=%d\n", ret); + return ret; +} + +/* I2C */ +static int dvbsky_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], + int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + int ret = 0; + u8 ibuf[64], obuf[64]; + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + if (num > 2) { + dev_err(&d->udev->dev, + "too many i2c messages[%d], max 2.", num); + ret = -EOPNOTSUPP; + goto i2c_error; + } + + if (num == 1) { + if (msg[0].len > 60) { + dev_err(&d->udev->dev, + "too many i2c bytes[%d], max 60.", + msg[0].len); + ret = -EOPNOTSUPP; + goto i2c_error; + } + if (msg[0].flags & I2C_M_RD) { + /* single read */ + obuf[0] = 0x09; + obuf[1] = 0; + obuf[2] = msg[0].len; + obuf[3] = msg[0].addr; + ret = dvbsky_usb_generic_rw(d, obuf, 4, + ibuf, msg[0].len + 1); + if (ret) + dev_err(&d->udev->dev, "failed=%d\n", ret); + if (!ret) + memcpy(msg[0].buf, &ibuf[1], msg[0].len); + } else { + /* write */ + obuf[0] = 0x08; + obuf[1] = msg[0].addr; + obuf[2] = msg[0].len; + memcpy(&obuf[3], msg[0].buf, msg[0].len); + ret = dvbsky_usb_generic_rw(d, obuf, + msg[0].len + 3, ibuf, 1); + if (ret) + dev_err(&d->udev->dev, "failed=%d\n", ret); + } + } else { + if ((msg[0].len > 60) || (msg[1].len > 60)) { + dev_err(&d->udev->dev, + "too many i2c bytes[w-%d][r-%d], max 60.", + msg[0].len, msg[1].len); + ret = -EOPNOTSUPP; + goto i2c_error; + } + /* write then read */ + obuf[0] = 0x09; + obuf[1] = msg[0].len; + obuf[2] = msg[1].len; + obuf[3] = msg[0].addr; + memcpy(&obuf[4], msg[0].buf, msg[0].len); + ret = dvbsky_usb_generic_rw(d, obuf, + msg[0].len + 4, ibuf, msg[1].len + 1); + if (ret) + dev_err(&d->udev->dev, "failed=%d\n", ret); + + if (!ret) + memcpy(msg[1].buf, &ibuf[1], msg[1].len); + } +i2c_error: + mutex_unlock(&d->i2c_mutex); + return (ret) ? ret : num; +} + +static u32 dvbsky_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm dvbsky_i2c_algo = { + .master_xfer = dvbsky_i2c_xfer, + .functionality = dvbsky_i2c_func, +}; + +#if IS_ENABLED(CONFIG_RC_CORE) +static int dvbsky_rc_query(struct dvb_usb_device *d) +{ + u32 code = 0xffff, scancode; + u8 rc5_command, rc5_system; + u8 obuf[2], ibuf[2], toggle; + int ret; + + obuf[0] = 0x10; + ret = dvbsky_usb_generic_rw(d, obuf, 1, ibuf, 2); + if (ret) + dev_err(&d->udev->dev, "failed=%d\n", ret); + if (ret == 0) + code = (ibuf[0] << 8) | ibuf[1]; + if (code != 0xffff) { + dev_dbg(&d->udev->dev, "rc code: %x\n", code); + rc5_command = code & 0x3F; + rc5_system = (code & 0x7C0) >> 6; + toggle = (code & 0x800) ? 1 : 0; + scancode = rc5_system << 8 | rc5_command; + rc_keydown(d->rc_dev, RC_TYPE_RC5, toggle); + } + return 0; +} + +static int dvbsky_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc) +{ + if (dvb_usb_dvbsky_disable_rc) { + rc->map_name = NULL; + return 0; + } + + rc->allowed_protos = RC_BIT_RC5; + rc->query = dvbsky_rc_query; + rc->interval = 300; + return 0; +} +#else + #define dvbsky_get_rc_config NULL +#endif + +static int dvbsky_usb_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t voltage) +{ + struct dvb_usb_device *d = fe_to_d(fe); + struct dvbsky_state *state = d_to_priv(d); + u8 value; + + if (voltage == SEC_VOLTAGE_OFF) + value = 0; + else + value = 1; + dvbsky_gpio_ctrl(d, 0x80, value); + + return state->fe_set_voltage(fe, voltage); +} + +static int dvbsky_read_mac_addr(struct dvb_usb_adapter *adap, u8 mac[6]) +{ + struct dvb_usb_device *d = adap_to_d(adap); + u8 obuf[] = { 0x1e, 0x00 }; + u8 ibuf[6] = { 0 }; + struct i2c_msg msg[] = { + { + .addr = 0x51, + .flags = 0, + .buf = obuf, + .len = 2, + }, { + .addr = 0x51, + .flags = I2C_M_RD, + .buf = ibuf, + .len = 6, + } + }; + + if (i2c_transfer(&d->i2c_adap, msg, 2) == 2) + memcpy(mac, ibuf, 6); + + return 0; +} + +static int dvbsky_usb_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct dvb_usb_device *d = fe_to_d(fe); + struct dvbsky_state *state = d_to_priv(d); + int ret; + + ret = state->fe_read_status(fe, status); + + /* it need resync slave fifo when signal change from unlock to lock.*/ + if ((*status & FE_HAS_LOCK) && (!state->last_lock)) + dvbsky_stream_ctrl(d, 1); + + state->last_lock = (*status & FE_HAS_LOCK) ? 1 : 0; + return ret; +} + +static const struct m88ds3103_config dvbsky_s960_m88ds3103_config = { + .i2c_addr = 0x68, + .clock = 27000000, + .i2c_wr_max = 33, + .clock_out = 0, + .ts_mode = M88DS3103_TS_CI, + .ts_clk = 16000, + .ts_clk_pol = 0, + .agc = 0x99, + .lnb_hv_pol = 1, + .lnb_en_pol = 1, +}; + +static int dvbsky_s960_attach(struct dvb_usb_adapter *adap) +{ + struct dvbsky_state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + int ret = 0; + /* demod I2C adapter */ + struct i2c_adapter *i2c_adapter; + struct i2c_client *client; + struct i2c_board_info info; + struct m88ts2022_config m88ts2022_config = { + .clock = 27000000, + }; + memset(&info, 0, sizeof(struct i2c_board_info)); + + /* attach demod */ + adap->fe[0] = dvb_attach(m88ds3103_attach, + &dvbsky_s960_m88ds3103_config, + &d->i2c_adap, + &i2c_adapter); + if (!adap->fe[0]) { + dev_err(&d->udev->dev, "dvbsky_s960_attach fail.\n"); + ret = -ENODEV; + goto fail_attach; + } + + /* attach tuner */ + m88ts2022_config.fe = adap->fe[0]; + strlcpy(info.type, "m88ts2022", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &m88ts2022_config; + request_module("m88ts2022"); + client = i2c_new_device(i2c_adapter, &info); + if (client == NULL || client->dev.driver == NULL) { + dvb_frontend_detach(adap->fe[0]); + ret = -ENODEV; + goto fail_attach; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + dvb_frontend_detach(adap->fe[0]); + ret = -ENODEV; + goto fail_attach; + } + + /* delegate signal strength measurement to tuner */ + adap->fe[0]->ops.read_signal_strength = + adap->fe[0]->ops.tuner_ops.get_rf_strength; + + /* hook fe: need to resync the slave fifo when signal locks. */ + state->fe_read_status = adap->fe[0]->ops.read_status; + adap->fe[0]->ops.read_status = dvbsky_usb_read_status; + + /* hook fe: LNB off/on is control by Cypress usb chip. */ + state->fe_set_voltage = adap->fe[0]->ops.set_voltage; + adap->fe[0]->ops.set_voltage = dvbsky_usb_set_voltage; + + state->i2c_client_tuner = client; + +fail_attach: + return ret; +} + +static int dvbsky_usb_ci_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t voltage) +{ + struct dvb_usb_device *d = fe_to_d(fe); + struct dvbsky_state *state = d_to_priv(d); + u8 value; + + if (voltage == SEC_VOLTAGE_OFF) + value = 0; + else + value = 1; + dvbsky_gpio_ctrl(d, 0x00, value); + + return state->fe_set_voltage(fe, voltage); +} + +static int dvbsky_ci_ctrl(void *priv, u8 read, int addr, + u8 data, int *mem) +{ + struct dvb_usb_device *d = priv; + int ret = 0; + u8 command[4], respond[2], command_size, respond_size; + + command[1] = (u8)((addr >> 8) & 0xff); /*high part of address*/ + command[2] = (u8)(addr & 0xff); /*low part of address*/ + if (read) { + command[0] = 0x71; + command_size = 3; + respond_size = 2; + } else { + command[0] = 0x70; + command[3] = data; + command_size = 4; + respond_size = 1; + } + ret = dvbsky_usb_generic_rw(d, command, command_size, + respond, respond_size); + if (ret) + goto err; + if (read) + *mem = respond[1]; + return ret; +err: + dev_err(&d->udev->dev, "ci control failed=%d\n", ret); + return ret; +} + +static const struct m88ds3103_config dvbsky_s960c_m88ds3103_config = { + .i2c_addr = 0x68, + .clock = 27000000, + .i2c_wr_max = 33, + .clock_out = 0, + .ts_mode = M88DS3103_TS_CI, + .ts_clk = 10000, + .ts_clk_pol = 1, + .agc = 0x99, + .lnb_hv_pol = 0, + .lnb_en_pol = 1, +}; + +static int dvbsky_s960c_attach(struct dvb_usb_adapter *adap) +{ + struct dvbsky_state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + int ret = 0; + /* demod I2C adapter */ + struct i2c_adapter *i2c_adapter; + struct i2c_client *client_tuner, *client_ci; + struct i2c_board_info info; + struct sp2_config sp2_config; + struct m88ts2022_config m88ts2022_config = { + .clock = 27000000, + }; + memset(&info, 0, sizeof(struct i2c_board_info)); + + /* attach demod */ + adap->fe[0] = dvb_attach(m88ds3103_attach, + &dvbsky_s960c_m88ds3103_config, + &d->i2c_adap, + &i2c_adapter); + if (!adap->fe[0]) { + dev_err(&d->udev->dev, "dvbsky_s960ci_attach fail.\n"); + ret = -ENODEV; + goto fail_attach; + } + + /* attach tuner */ + m88ts2022_config.fe = adap->fe[0]; + strlcpy(info.type, "m88ts2022", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &m88ts2022_config; + request_module("m88ts2022"); + client_tuner = i2c_new_device(i2c_adapter, &info); + if (client_tuner == NULL || client_tuner->dev.driver == NULL) { + ret = -ENODEV; + goto fail_tuner_device; + } + + if (!try_module_get(client_tuner->dev.driver->owner)) { + ret = -ENODEV; + goto fail_tuner_module; + } + + /* attach ci controller */ + memset(&sp2_config, 0, sizeof(sp2_config)); + sp2_config.dvb_adap = &adap->dvb_adap; + sp2_config.priv = d; + sp2_config.ci_control = dvbsky_ci_ctrl; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "sp2", I2C_NAME_SIZE); + info.addr = 0x40; + info.platform_data = &sp2_config; + request_module("sp2"); + client_ci = i2c_new_device(&d->i2c_adap, &info); + if (client_ci == NULL || client_ci->dev.driver == NULL) { + ret = -ENODEV; + goto fail_ci_device; + } + + if (!try_module_get(client_ci->dev.driver->owner)) { + ret = -ENODEV; + goto fail_ci_module; + } + + /* delegate signal strength measurement to tuner */ + adap->fe[0]->ops.read_signal_strength = + adap->fe[0]->ops.tuner_ops.get_rf_strength; + + /* hook fe: need to resync the slave fifo when signal locks. */ + state->fe_read_status = adap->fe[0]->ops.read_status; + adap->fe[0]->ops.read_status = dvbsky_usb_read_status; + + /* hook fe: LNB off/on is control by Cypress usb chip. */ + state->fe_set_voltage = adap->fe[0]->ops.set_voltage; + adap->fe[0]->ops.set_voltage = dvbsky_usb_ci_set_voltage; + + state->i2c_client_tuner = client_tuner; + state->i2c_client_ci = client_ci; + return ret; +fail_ci_module: + i2c_unregister_device(client_ci); +fail_ci_device: + module_put(client_tuner->dev.driver->owner); +fail_tuner_module: + i2c_unregister_device(client_tuner); +fail_tuner_device: + dvb_frontend_detach(adap->fe[0]); +fail_attach: + return ret; +} + +static int dvbsky_t680c_attach(struct dvb_usb_adapter *adap) +{ + struct dvbsky_state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + int ret = 0; + struct i2c_adapter *i2c_adapter; + struct i2c_client *client_demod, *client_tuner, *client_ci; + struct i2c_board_info info; + struct si2168_config si2168_config; + struct si2157_config si2157_config; + struct sp2_config sp2_config; + + /* attach demod */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &i2c_adapter; + si2168_config.fe = &adap->fe[0]; + si2168_config.ts_mode = SI2168_TS_PARALLEL; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0x64; + info.platform_data = &si2168_config; + + request_module(info.type); + client_demod = i2c_new_device(&d->i2c_adap, &info); + if (client_demod == NULL || + client_demod->dev.driver == NULL) + goto fail_demod_device; + if (!try_module_get(client_demod->dev.driver->owner)) + goto fail_demod_module; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = adap->fe[0]; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + + request_module(info.type); + client_tuner = i2c_new_device(i2c_adapter, &info); + if (client_tuner == NULL || + client_tuner->dev.driver == NULL) + goto fail_tuner_device; + if (!try_module_get(client_tuner->dev.driver->owner)) + goto fail_tuner_module; + + /* attach ci controller */ + memset(&sp2_config, 0, sizeof(sp2_config)); + sp2_config.dvb_adap = &adap->dvb_adap; + sp2_config.priv = d; + sp2_config.ci_control = dvbsky_ci_ctrl; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "sp2", I2C_NAME_SIZE); + info.addr = 0x40; + info.platform_data = &sp2_config; + + request_module(info.type); + client_ci = i2c_new_device(&d->i2c_adap, &info); + + if (client_ci == NULL || client_ci->dev.driver == NULL) + goto fail_ci_device; + + if (!try_module_get(client_ci->dev.driver->owner)) + goto fail_ci_module; + + state->i2c_client_demod = client_demod; + state->i2c_client_tuner = client_tuner; + state->i2c_client_ci = client_ci; + return ret; +fail_ci_module: + i2c_unregister_device(client_ci); +fail_ci_device: + module_put(client_tuner->dev.driver->owner); +fail_tuner_module: + i2c_unregister_device(client_tuner); +fail_tuner_device: + module_put(client_demod->dev.driver->owner); +fail_demod_module: + i2c_unregister_device(client_demod); +fail_demod_device: + ret = -ENODEV; + return ret; +} + +static int dvbsky_t330_attach(struct dvb_usb_adapter *adap) +{ + struct dvbsky_state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + int ret = 0; + struct i2c_adapter *i2c_adapter; + struct i2c_client *client_demod, *client_tuner; + struct i2c_board_info info; + struct si2168_config si2168_config; + struct si2157_config si2157_config; + + /* attach demod */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &i2c_adapter; + si2168_config.fe = &adap->fe[0]; + si2168_config.ts_mode = SI2168_TS_PARALLEL | 0x40; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0x64; + info.platform_data = &si2168_config; + + request_module(info.type); + client_demod = i2c_new_device(&d->i2c_adap, &info); + if (client_demod == NULL || + client_demod->dev.driver == NULL) + goto fail_demod_device; + if (!try_module_get(client_demod->dev.driver->owner)) + goto fail_demod_module; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = adap->fe[0]; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + + request_module(info.type); + client_tuner = i2c_new_device(i2c_adapter, &info); + if (client_tuner == NULL || + client_tuner->dev.driver == NULL) + goto fail_tuner_device; + if (!try_module_get(client_tuner->dev.driver->owner)) + goto fail_tuner_module; + + state->i2c_client_demod = client_demod; + state->i2c_client_tuner = client_tuner; + return ret; +fail_tuner_module: + i2c_unregister_device(client_tuner); +fail_tuner_device: + module_put(client_demod->dev.driver->owner); +fail_demod_module: + i2c_unregister_device(client_demod); +fail_demod_device: + ret = -ENODEV; + return ret; +} + +static int dvbsky_identify_state(struct dvb_usb_device *d, const char **name) +{ + dvbsky_gpio_ctrl(d, 0x04, 1); + msleep(20); + dvbsky_gpio_ctrl(d, 0x83, 0); + dvbsky_gpio_ctrl(d, 0xc0, 1); + msleep(100); + dvbsky_gpio_ctrl(d, 0x83, 1); + dvbsky_gpio_ctrl(d, 0xc0, 0); + msleep(50); + + return WARM; +} + +static int dvbsky_init(struct dvb_usb_device *d) +{ + struct dvbsky_state *state = d_to_priv(d); + + /* use default interface */ + /* + ret = usb_set_interface(d->udev, 0, 0); + if (ret) + return ret; + */ + mutex_init(&state->stream_mutex); + + state->last_lock = 0; + + return 0; +} + +static void dvbsky_exit(struct dvb_usb_device *d) +{ + struct dvbsky_state *state = d_to_priv(d); + struct i2c_client *client; + + client = state->i2c_client_tuner; + /* remove I2C tuner */ + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + client = state->i2c_client_demod; + /* remove I2C demod */ + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + client = state->i2c_client_ci; + /* remove I2C ci */ + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } +} + +/* DVB USB Driver stuff */ +static struct dvb_usb_device_properties dvbsky_s960_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct dvbsky_state), + + .generic_bulk_ctrl_endpoint = 0x01, + .generic_bulk_ctrl_endpoint_response = 0x81, + .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY, + + .i2c_algo = &dvbsky_i2c_algo, + .frontend_attach = dvbsky_s960_attach, + .init = dvbsky_init, + .get_rc_config = dvbsky_get_rc_config, + .streaming_ctrl = dvbsky_streaming_ctrl, + .identify_state = dvbsky_identify_state, + .exit = dvbsky_exit, + .read_mac_address = dvbsky_read_mac_addr, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096), + } + } +}; + +static struct dvb_usb_device_properties dvbsky_s960c_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct dvbsky_state), + + .generic_bulk_ctrl_endpoint = 0x01, + .generic_bulk_ctrl_endpoint_response = 0x81, + .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY, + + .i2c_algo = &dvbsky_i2c_algo, + .frontend_attach = dvbsky_s960c_attach, + .init = dvbsky_init, + .get_rc_config = dvbsky_get_rc_config, + .streaming_ctrl = dvbsky_streaming_ctrl, + .identify_state = dvbsky_identify_state, + .exit = dvbsky_exit, + .read_mac_address = dvbsky_read_mac_addr, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096), + } + } +}; + +static struct dvb_usb_device_properties dvbsky_t680c_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct dvbsky_state), + + .generic_bulk_ctrl_endpoint = 0x01, + .generic_bulk_ctrl_endpoint_response = 0x81, + .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY, + + .i2c_algo = &dvbsky_i2c_algo, + .frontend_attach = dvbsky_t680c_attach, + .init = dvbsky_init, + .get_rc_config = dvbsky_get_rc_config, + .streaming_ctrl = dvbsky_streaming_ctrl, + .identify_state = dvbsky_identify_state, + .exit = dvbsky_exit, + .read_mac_address = dvbsky_read_mac_addr, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096), + } + } +}; + +static struct dvb_usb_device_properties dvbsky_t330_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct dvbsky_state), + + .generic_bulk_ctrl_endpoint = 0x01, + .generic_bulk_ctrl_endpoint_response = 0x81, + .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY, + + .i2c_algo = &dvbsky_i2c_algo, + .frontend_attach = dvbsky_t330_attach, + .init = dvbsky_init, + .get_rc_config = dvbsky_get_rc_config, + .streaming_ctrl = dvbsky_streaming_ctrl, + .identify_state = dvbsky_identify_state, + .exit = dvbsky_exit, + .read_mac_address = dvbsky_read_mac_addr, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096), + } + } +}; + +static const struct usb_device_id dvbsky_id_table[] = { + { DVB_USB_DEVICE(0x0572, 0x6831, + &dvbsky_s960_props, "DVBSky S960/S860", RC_MAP_DVBSKY) }, + { DVB_USB_DEVICE(0x0572, 0x960c, + &dvbsky_s960c_props, "DVBSky S960CI", RC_MAP_DVBSKY) }, + { DVB_USB_DEVICE(0x0572, 0x680c, + &dvbsky_t680c_props, "DVBSky T680CI", RC_MAP_DVBSKY) }, + { DVB_USB_DEVICE(0x0572, 0x0320, + &dvbsky_t330_props, "DVBSky T330", RC_MAP_DVBSKY) }, + { DVB_USB_DEVICE(USB_VID_TECHNOTREND, + USB_PID_TECHNOTREND_TVSTICK_CT2_4400, + &dvbsky_t330_props, "TechnoTrend TVStick CT2-4400", + RC_MAP_TT_1500) }, + { DVB_USB_DEVICE(USB_VID_TECHNOTREND, + USB_PID_TECHNOTREND_CONNECT_CT2_4650_CI, + &dvbsky_t680c_props, "TechnoTrend CT2-4650 CI", + RC_MAP_TT_1500) }, + { } +}; +MODULE_DEVICE_TABLE(usb, dvbsky_id_table); + +static struct usb_driver dvbsky_usb_driver = { + .name = KBUILD_MODNAME, + .id_table = dvbsky_id_table, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .suspend = dvb_usbv2_suspend, + .resume = dvb_usbv2_resume, + .reset_resume = dvb_usbv2_reset_resume, + .no_dynamic_id = 1, + .soft_unbind = 1, +}; + +module_usb_driver(dvbsky_usb_driver); + +MODULE_AUTHOR("Max nibble "); +MODULE_DESCRIPTION("Driver for DVBSky USB"); +MODULE_LICENSE("GPL"); + diff --git a/include/media/media-entity.h b/include/media/media-entity.h index 0c16f518ee092c..eb03aa37985e6f 100644 --- a/include/media/media-entity.h +++ b/include/media/media-entity.h @@ -95,6 +95,10 @@ struct media_entity { u32 device; u32 subdevice; } alsa; + struct { + u32 major; + u32 minor; + } dev; int dvb; /* Sub-device specifications */ diff --git a/include/media/rc-map.h b/include/media/rc-map.h index 5d5d3a30f04aac..d56fe2fcde95d6 100644 --- a/include/media/rc-map.h +++ b/include/media/rc-map.h @@ -118,6 +118,7 @@ void rc_map_init(void); #define RC_MAP_DM1105_NEC "rc-dm1105-nec" #define RC_MAP_DNTV_LIVE_DVBT_PRO "rc-dntv-live-dvbt-pro" #define RC_MAP_DNTV_LIVE_DVB_T "rc-dntv-live-dvb-t" +#define RC_MAP_DVBSKY "rc-dvbsky" #define RC_MAP_EMPTY "rc-empty" #define RC_MAP_EM_TERRATEC "rc-em-terratec" #define RC_MAP_ENCORE_ENLTV2 "rc-encore-enltv2" @@ -142,7 +143,7 @@ void rc_map_init(void); #define RC_MAP_KWORLD_PC150U "rc-kworld-pc150u" #define RC_MAP_KWORLD_PLUS_TV_ANALOG "rc-kworld-plus-tv-analog" #define RC_MAP_LEADTEK_Y04G0051 "rc-leadtek-y04g0051" -#define RC_MAP_LIRC "rc-lirc" +#define RC_MAP_LIRC "rc-lirc" #define RC_MAP_LME2510 "rc-lme2510" #define RC_MAP_MANLI "rc-manli" #define RC_MAP_MEDION_X10 "rc-medion-x10" diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h index ed49574ad7573c..6fb05fc50d04c5 100644 --- a/include/uapi/linux/media.h +++ b/include/uapi/linux/media.h @@ -50,7 +50,15 @@ struct media_device_info { #define MEDIA_ENT_T_DEVNODE_V4L (MEDIA_ENT_T_DEVNODE + 1) #define MEDIA_ENT_T_DEVNODE_FB (MEDIA_ENT_T_DEVNODE + 2) #define MEDIA_ENT_T_DEVNODE_ALSA (MEDIA_ENT_T_DEVNODE + 3) -#define MEDIA_ENT_T_DEVNODE_DVB (MEDIA_ENT_T_DEVNODE + 4) + +#define MEDIA_ENT_T_DEVNODE_DVB_FE (MEDIA_ENT_T_DEVNODE + 4) +#define MEDIA_ENT_T_DEVNODE_DVB_DEMUX (MEDIA_ENT_T_DEVNODE + 5) +#define MEDIA_ENT_T_DEVNODE_DVB_DVR (MEDIA_ENT_T_DEVNODE + 6) +#define MEDIA_ENT_T_DEVNODE_DVB_CA (MEDIA_ENT_T_DEVNODE + 7) +#define MEDIA_ENT_T_DEVNODE_DVB_NET (MEDIA_ENT_T_DEVNODE + 8) + +/* Legacy symbol. Use it to avoid userspace compilation breakages */ +#define MEDIA_ENT_T_DEVNODE_DVB MEDIA_ENT_T_DEVNODE_DVB_FE #define MEDIA_ENT_T_V4L2_SUBDEV (2 << MEDIA_ENT_TYPE_SHIFT) #define MEDIA_ENT_T_V4L2_SUBDEV_SENSOR (MEDIA_ENT_T_V4L2_SUBDEV + 1) @@ -59,6 +67,8 @@ struct media_device_info { /* A converter of analogue video to its digital representation. */ #define MEDIA_ENT_T_V4L2_SUBDEV_DECODER (MEDIA_ENT_T_V4L2_SUBDEV + 4) +#define MEDIA_ENT_T_V4L2_SUBDEV_TUNER (MEDIA_ENT_T_V4L2_SUBDEV + 5) + #define MEDIA_ENT_FL_DEFAULT (1 << 0) struct media_entity_desc {