diff options
author | Arnaud Bignon <arnaud.bignon-ext@st.com> | 2015-04-14 13:54:33 +0200 |
---|---|---|
committer | Peter Griffin <peter.griffin@linaro.org> | 2015-06-17 23:06:31 +0100 |
commit | 3cd1fd2516357184d519201683f0e504670a8918 (patch) | |
tree | 9f64a8f4142bb89f7526471530bc3b4718b2be6e | |
parent | 0e1593858ec9fc76d59c285a7e51f3f64d8cf3de (diff) |
media: c8sectpfe: stih4xx initial DVB support
This adds support for DVB front-end cards connected
to TS input of STiH407/410 SoC.
It currently supports the TS input, memdma engine
and hw PID filtering blocks of C8SECTPFE subsystem.
Change-Id: I85ed90618e9b3752faed3ed8c4a77a8f247d9967
Reviewed-on: https://gerrit.st.com/27641
Reviewed-by: Hugues FRUCHET <hugues.fruchet@st.com>
Tested-by: Hugues FRUCHET <hugues.fruchet@st.com>
-rw-r--r-- | Documentation/devicetree/bindings/media/stih407-c8sectpfe.txt | 89 | ||||
-rw-r--r-- | arch/arm/boot/dts/stihxxx-b2120.dtsi | 3 | ||||
-rw-r--r-- | drivers/media/Kconfig | 1 | ||||
-rw-r--r-- | drivers/media/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/tsin/c8sectpfe/Kconfig | 26 | ||||
-rw-r--r-- | drivers/media/tsin/c8sectpfe/Makefile | 11 | ||||
-rw-r--r-- | drivers/media/tsin/c8sectpfe/c8sectpfe-common.c | 267 | ||||
-rw-r--r-- | drivers/media/tsin/c8sectpfe/c8sectpfe-common.h | 72 | ||||
-rw-r--r-- | drivers/media/tsin/c8sectpfe/c8sectpfe-core.c | 1107 | ||||
-rw-r--r-- | drivers/media/tsin/c8sectpfe/c8sectpfe-core.h | 288 | ||||
-rw-r--r-- | drivers/media/tsin/c8sectpfe/c8sectpfe-debugfs.c | 276 | ||||
-rw-r--r-- | drivers/media/tsin/c8sectpfe/c8sectpfe-debugfs.h | 26 | ||||
-rw-r--r-- | drivers/media/tsin/c8sectpfe/c8sectpfe-dvb.c | 309 | ||||
-rw-r--r-- | drivers/media/tsin/c8sectpfe/c8sectpfe-dvb.h | 20 | ||||
-rw-r--r-- | include/dt-bindings/media/c8sectpfe.h | 14 |
15 files changed, 2509 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/media/stih407-c8sectpfe.txt b/Documentation/devicetree/bindings/media/stih407-c8sectpfe.txt new file mode 100644 index 000000000000..f6e9801ebac6 --- /dev/null +++ b/Documentation/devicetree/bindings/media/stih407-c8sectpfe.txt @@ -0,0 +1,89 @@ +STMicroelectronics STi c8sectpfe binding +============================================ + +This binding describes the c8sectpfe device that is used to get transport stream +data into the SoC on the TS pins, and into DDR for further processing. + +It is typically used in conjunction with one or more demodulator and tuner devices +wich converts from the RF to digital domain. Demodulator and tuner are usually +located on an external DVB frontend card connected to SoC TS input pins. +Currently 7 TS input (tsin) channels are supported on the stih407 family SoC. + +Required properties (controller (parent) node): +- compatible : Should be "stih407-c8sectpfe" + +- reg : Address and length of register sets for each device in + "reg-names" + +- reg-names : The names of the register addresses corresponding to the + registers filled in "reg": + - c8sectpfe: c8sectpfe registers + - c8sectpfe-ram: c8sectpfe internal sram + +- clocks : phandle list of c8sectpfe clocks +- clock-names : should be "c8sectpfe" +See: Documentation/devicetree/bindings/clock/clock-bindings.txt + +- pinctrl-names : a pinctrl state named tsin%d-serial or tsin%d-parallel (where %d is tsin-num) + must be defined for each tsin child node. +- pinctrl-0 : phandle referencing pin configuration for this tsin configuration +See: Documentation/devicetree/bindings/pinctrl/pinctrl-binding.txt + + +Required properties (tsin (child) node): + +- tsin-num : tsin id of the InputBlock (must be between 0 to 6) +- i2c-bus : phandle to the I2C bus DT node which the demodulators & tuners on this tsin channel are connected. +- rst-gpio : reset gpio for this tsin channel. + +Optional properties (tsin (child) node): + +- invert-ts-clk : Bool property to control sense of ts input clock (data stored on falling edge of clk). +- serial-not-parallel : Bool property to configure input bus width (serial on ts_data<7>). +- async-not-sync : Bool property to control if data is received in asynchronous mode + (all bits/bytes with ts_valid or ts_packet asserted are valid). + +- dvb-card : Describes the NIM card connected to this tsin channel. + +Example: + +/* stih410 SoC b2120 + b2004a + stv0367-pll(NIMB) + stv0367-tda18212 (NIMA) DT example) */ + + c8sectpfe@08a20000 { + compatible = "st,stih407-c8sectpfe"; + status = "okay"; + reg = <0x08a20000 0x10000>, <0x08a00000 0x4000>; + reg-names = "stfe", "stfe-ram"; + interrupts = <0 34 0>, <0 35 0>; + interrupt-names = "stfe-error-irq", "stfe-idle-irq"; + + pinctrl-names = "tsin0-serial", "tsin0-parallel", "tsin3-serial", + "tsin4-serial", "tsin5-serial"; + + pinctrl-0 = <&pinctrl_tsin0_serial>; + pinctrl-1 = <&pinctrl_tsin0_parallel>; + pinctrl-2 = <&pinctrl_tsin3_serial>; + pinctrl-3 = <&pinctrl_tsin4_serial_alt3>; + pinctrl-4 = <&pinctrl_tsin5_serial_alt1>; + + clocks = <&clk_s_c0_flexgen CLK_PROC_STFE>; + clock-names = "stfe"; + + /* tsin0 is TSA on NIMA */ + tsin0: port@0 { + tsin-num = <0>; + serial-not-parallel; + i2c-bus = <&ssc2>; + rst-gpio = <&pio15 4 0>; + dvb-card = <STV0367_TDA18212_NIMA_1>; + }; + + /* tsin3 is TSB on NIMB */ + tsin3: port@3 { + tsin-num = <3>; + serial-not-parallel; + i2c-bus = <&ssc3>; + rst-gpio = <&pio15 7 0>; + dvb-card = <STV0367_PLL_BOARD_NIMB>; + }; + }; diff --git a/arch/arm/boot/dts/stihxxx-b2120.dtsi b/arch/arm/boot/dts/stihxxx-b2120.dtsi index 5d70747dfc09..636f9db7d91d 100644 --- a/arch/arm/boot/dts/stihxxx-b2120.dtsi +++ b/arch/arm/boot/dts/stihxxx-b2120.dtsi @@ -7,6 +7,7 @@ * published by the Free Software Foundation. */ +#include <dt-bindings/clock/stih407-clks.h> #include <dt-bindings/media/c8sectpfe.h> / { @@ -73,7 +74,7 @@ phy_port1: port@9b2a000 { st,osc-force-ext; }; - } + }; ethernet0: dwmac@9630000 { st,tx-retime-src = "clkgen"; diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index 3ef0f90b128f..9feffe1e312f 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -169,6 +169,7 @@ source "drivers/media/pci/Kconfig" source "drivers/media/platform/Kconfig" source "drivers/media/mmc/Kconfig" source "drivers/media/radio/Kconfig" +source "drivers/media/tsin/c8sectpfe/Kconfig" comment "Supported FireWire (IEEE 1394) Adapters" depends on DVB_CORE && FIREWIRE diff --git a/drivers/media/Makefile b/drivers/media/Makefile index e608bbce0c35..0a567b80e1f9 100644 --- a/drivers/media/Makefile +++ b/drivers/media/Makefile @@ -29,5 +29,6 @@ obj-y += rc/ # obj-y += common/ platform/ pci/ usb/ mmc/ firewire/ +obj-$(CONFIG_DVB_C8SECTPFE) += tsin/c8sectpfe/ obj-$(CONFIG_VIDEO_DEV) += radio/ diff --git a/drivers/media/tsin/c8sectpfe/Kconfig b/drivers/media/tsin/c8sectpfe/Kconfig new file mode 100644 index 000000000000..d110225f0591 --- /dev/null +++ b/drivers/media/tsin/c8sectpfe/Kconfig @@ -0,0 +1,26 @@ +config DVB_C8SECTPFE + tristate "STMicroelectronics C8SECTPFE DVB support" + depends on DVB_CORE && I2C && (ARCH_STI || ARCH_MULTIPLATFORM) + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0367 if MEDIA_SUBDRV_AUTOSELECT + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18212 if MEDIA_SUBDRV_AUTOSELECT + + ---help--- + This adds support for DVB front-end cards connected + to TS input of STiH407/410 SoC. + + It currently supports C8SECTPFE's TS input block, + memdma engine, and hw PID filtering. + + Supported DVB front-end cards are: + - STMicroelectronics DVB-T B2100A (STV0367 + TDA18212) + - STMicroelectronics DVB-T STV0367 PLL board + - STMicroelectronics DVB-S STV0903 + STV6110 + LNBP21 board + + To compile this driver as a module, choose M here: the + module will be called c8sectpfe. diff --git a/drivers/media/tsin/c8sectpfe/Makefile b/drivers/media/tsin/c8sectpfe/Makefile new file mode 100644 index 000000000000..777f06dbe297 --- /dev/null +++ b/drivers/media/tsin/c8sectpfe/Makefile @@ -0,0 +1,11 @@ +c8sectpfe-y += c8sectpfe-core.o c8sectpfe-common.o c8sectpfe-dvb.o + +obj-$(CONFIG_DVB_C8SECTPFE) += c8sectpfe.o + +ifneq ($(CONFIG_DVB_C8SECTPFE),) + c8sectpfe-y += c8sectpfe-debugfs.o +endif + +ccflags-y += -Idrivers/media/i2c +ccflags-y += -Idrivers/media/common +ccflags-y += -Idrivers/media/dvb-core/ -Idrivers/media/dvb-frontends/ -Idrivers/media/tuners/ diff --git a/drivers/media/tsin/c8sectpfe/c8sectpfe-common.c b/drivers/media/tsin/c8sectpfe/c8sectpfe-common.c new file mode 100644 index 000000000000..0da55d0e4344 --- /dev/null +++ b/drivers/media/tsin/c8sectpfe/c8sectpfe-common.c @@ -0,0 +1,267 @@ +/* + * c8sectpfe-common.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author:Peter Bennett <peter.bennett@st.com> + * Peter Griffin <peter.griffin@linaro.org> + * + * 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. + */ +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dvb/dmx.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/wait.h> + +#include "dmxdev.h" +#include "dvbdev.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "dvb_net.h" + +#include "c8sectpfe-common.h" +#include "c8sectpfe-core.h" +#include "c8sectpfe-dvb.h" + +static int register_dvb(struct stdemux *demux, struct dvb_adapter *adap, + void *start_feed, void *stop_feed, + struct c8sectpfei *fei) +{ + int result; + + demux->dvb_demux.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + + demux->dvb_demux.priv = demux; + demux->dvb_demux.filternum = C8SECTPFE_MAXCHANNEL; + demux->dvb_demux.feednum = C8SECTPFE_MAXCHANNEL; + + demux->dvb_demux.start_feed = start_feed; + demux->dvb_demux.stop_feed = stop_feed; + demux->dvb_demux.write_to_decoder = NULL; + + result = dvb_dmx_init(&demux->dvb_demux); + if (result < 0) { + dev_err(fei->dev, "dvb_dmx_init failed (errno = %d)\n", + result); + goto err_dmx; + } + + demux->dmxdev.filternum = demux->dvb_demux.filternum; + demux->dmxdev.demux = &demux->dvb_demux.dmx; + demux->dmxdev.capabilities = 0; + + result = dvb_dmxdev_init(&demux->dmxdev, adap); + if (result < 0) { + dev_err(fei->dev, "dvb_dmxdev_init failed (errno = %d)\n", + result); + + goto err_dmxdev; + } + + demux->hw_frontend.source = DMX_FRONTEND_0 + demux->tsin_index; + + result = demux->dvb_demux.dmx.add_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); + if (result < 0) { + dev_err(fei->dev, "add_frontend failed (errno = %d)\n", result); + goto err_fe_hw; + } + + demux->mem_frontend.source = DMX_MEMORY_FE; + result = demux->dvb_demux.dmx.add_frontend(&demux->dvb_demux.dmx, + &demux->mem_frontend); + if (result < 0) { + dev_err(fei->dev, "add_frontend failed (%d)\n", result); + goto err_fe_mem; + } + + result = demux->dvb_demux.dmx.connect_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); + if (result < 0) { + dev_err(fei->dev, "connect_frontend (%d)\n", result); + goto err_fe_con; + } + + return 0; + +err_fe_con: + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->mem_frontend); +err_fe_mem: + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); +err_fe_hw: + dvb_dmxdev_release(&demux->dmxdev); +err_dmxdev: + dvb_dmx_release(&demux->dvb_demux); +err_dmx: + return result; + +} + +static void unregister_dvb(struct stdemux *demux) +{ + + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->mem_frontend); + + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); + + dvb_dmxdev_release(&demux->dmxdev); + + dvb_dmx_release(&demux->dvb_demux); +} + +static struct c8sectpfe *c8sectpfe_create(struct c8sectpfei *fei, + void *start_feed, + void *stop_feed) +{ + struct c8sectpfe *c8sectpfe; + int result; + int i, j; + + short int ids[] = { -1 }; + + c8sectpfe = kzalloc(sizeof(struct c8sectpfe), GFP_KERNEL); + if (!c8sectpfe) + goto err1; + + mutex_init(&c8sectpfe->lock); + + c8sectpfe->device = fei->dev; + + result = dvb_register_adapter(&c8sectpfe->adapter, "STi c8sectpfe", + THIS_MODULE, fei->dev, ids); + if (result < 0) { + dev_err(fei->dev, "dvb_register_adapter failed (errno = %d)\n", + result); + goto err2; + } + + c8sectpfe->adapter.priv = fei; + + for (i = 0; i < fei->tsin_count; i++) { + + c8sectpfe->demux[i].tsin_index = i; + c8sectpfe->demux[i].tsm_channel = fei->channel_data[i]->tsin_id; + c8sectpfe->demux[i].c8sectpfei = fei; + + result = register_dvb(&c8sectpfe->demux[i], &c8sectpfe->adapter, + start_feed, stop_feed, fei); + if (result < 0) { + dev_err(fei->dev, + "register_dvb feed=%d failed (errno = %d)\n", + result, i); + + /* we take a all or nothing approach */ + for (j = 0; j < i; j++) + unregister_dvb(&c8sectpfe->demux[j]); + goto err3; + } + } + + c8sectpfe->num_feeds = fei->tsin_count; + + return c8sectpfe; +err3: + dvb_unregister_adapter(&c8sectpfe->adapter); +err2: + kfree(c8sectpfe); +err1: + return NULL; +}; + +static void c8sectpfe_delete(struct c8sectpfe *c8sectpfe) +{ + int i; + + if (!c8sectpfe) + return; + + for (i = 0; i < c8sectpfe->num_feeds; i++) + unregister_dvb(&c8sectpfe->demux[i]); + + dvb_unregister_adapter(&c8sectpfe->adapter); + + kfree(c8sectpfe); +}; + +void c8sectpfe_tuner_unregister_frontend(struct c8sectpfe *c8sectpfe, + struct c8sectpfei *fei) +{ + int n; + struct channel_info *tsin; + + for (n = 0; n < fei->tsin_count; n++) { + + tsin = fei->channel_data[n]; + + if (tsin && tsin->frontend) { + dvb_unregister_frontend(tsin->frontend); + dvb_frontend_detach(tsin->frontend); + } + + if (tsin && tsin->i2c_adapter) + i2c_put_adapter(tsin->i2c_adapter); + + if (tsin && tsin->i2c_client) { + if (tsin->i2c_client->dev.driver->owner) + module_put(tsin->i2c_client->dev.driver->owner); + i2c_unregister_device(tsin->i2c_client); + } + } + + c8sectpfe_delete(c8sectpfe); +}; + +int c8sectpfe_tuner_register_frontend(struct c8sectpfe **c8sectpfe, + struct c8sectpfei *fei, + void *start_feed, + void *stop_feed) +{ + struct channel_info *tsin; + struct dvb_frontend *frontend; + int n, res; + + *c8sectpfe = c8sectpfe_create(fei, start_feed, stop_feed); + if (!*c8sectpfe) + return -ENOMEM; + + for (n = 0; n < fei->tsin_count; n++) { + tsin = fei->channel_data[n]; + + res = c8sectpfe_frontend_attach(&frontend, *c8sectpfe, tsin, n); + if (res) + goto err; + + res = dvb_register_frontend(&c8sectpfe[0]->adapter, frontend); + if (res < 0) { + dev_err(fei->dev, "dvb_register_frontend failed (%d)\n", + res); + goto err; + } + + tsin->frontend = frontend; + } + + return 0; + +err: + c8sectpfe_tuner_unregister_frontend(*c8sectpfe, fei); + return res; +} diff --git a/drivers/media/tsin/c8sectpfe/c8sectpfe-common.h b/drivers/media/tsin/c8sectpfe/c8sectpfe-common.h new file mode 100644 index 000000000000..ec6b2fe97a9a --- /dev/null +++ b/drivers/media/tsin/c8sectpfe/c8sectpfe-common.h @@ -0,0 +1,72 @@ +/* + * c8sectpfe-common.h - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author:Peter Bennett <peter.bennett@st.com> + * + * 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. + */ +#ifndef _C8SECTPFE_COMMON_H_ +#define _C8SECTPFE_COMMON_H_ + +#include <linux/dvb/dmx.h> +#include <linux/dvb/frontend.h> +#include <linux/gpio.h> +#include <linux/version.h> + +#include "dmxdev.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "dvb_net.h" + +/* Maximum number of channels */ +#define C8SECTPFE_MAXADAPTER (4) +#define C8SECTPFE_MAXCHANNEL 64 +#define STPTI_MAXCHANNEL 64 + +#define MAX_INPUTBLOCKS 7 + +struct c8sectpfe; +struct stdemux; + +struct stdemux { + struct dvb_demux dvb_demux; + struct dmxdev dmxdev; + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + int tsm_channel; + int stream_channel; + int tsin_index; + int running_feed_count; + struct c8sectpfei *c8sectpfei; +}; + +struct c8sectpfe { + + struct stdemux demux[MAX_INPUTBLOCKS]; + + int mapping; + + struct mutex lock; + + struct dvb_adapter adapter; + + struct device *device; + + int num_feeds; +}; + +/* Channel registration */ +int c8sectpfe_tuner_register_frontend(struct c8sectpfe **c8sectpfe, + struct c8sectpfei *fei, + void *start_feed, + void *stop_feed); + +void c8sectpfe_tuner_unregister_frontend(struct c8sectpfe *c8sectpfe, + struct c8sectpfei *fei); + +#endif diff --git a/drivers/media/tsin/c8sectpfe/c8sectpfe-core.c b/drivers/media/tsin/c8sectpfe/c8sectpfe-core.c new file mode 100644 index 000000000000..0d4a883d5ae4 --- /dev/null +++ b/drivers/media/tsin/c8sectpfe/c8sectpfe-core.c @@ -0,0 +1,1107 @@ +/* + * c8sectpfe-core.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author:Peter Bennett <peter.bennett@st.com> + * Peter Griffin <peter.griffin@linaro.org> + * + * 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. + */ +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/dvb/dmx.h> +#include <linux/dvb/frontend.h> +#include <linux/errno.h> +#include <linux/firmware.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/version.h> +#include <linux/wait.h> + +#include "c8sectpfe-core.h" +#include "c8sectpfe-common.h" +#include "c8sectpfe-debugfs.h" +#include "dmxdev.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "dvb_net.h" + +#define FIRMWARE_MEMDMA "pti_memdma_h407.elf" +MODULE_FIRMWARE(FIRMWARE_MEMDMA); + +#define PID_TABLE_SIZE 1024 +#define POLL_20_HZ_DIV 20 /* poll at 20 Hz */ + +static int load_slim_core_fw(struct c8sectpfei *fei); + +#define TS_PKT_SIZE 188 +#define HEADER_SIZE (4) +#define PACKET_SIZE (TS_PKT_SIZE+HEADER_SIZE) + +#define FEI_ALIGNMENT (32) +/* hw requires minimum of 8*PACKET_SIZE and padded to 8byte boundary */ +#define FEI_BUFFER_SIZE (8*PACKET_SIZE*340) + +#define FIFO_LEN 1024 + +static void c8sectpfe_timer_interrupt(unsigned long ac8sectpfei) +{ + struct c8sectpfei *fei = (struct c8sectpfei *)ac8sectpfei; + struct channel_info *channel; + int chan_num; + + /* iterate through input block channels */ + for (chan_num = 0; chan_num < fei->tsin_count; chan_num++) { + channel = fei->channel_data[chan_num]; + + /* is this descriptor initialised and TP enabled */ + if (channel->irec && readl((void __iomem *) + &channel->irec->tp_enable)) + tasklet_schedule(&channel->tsklet); + } + + fei->timer.expires = jiffies + (HZ / POLL_20_HZ_DIV); + add_timer(&fei->timer); +} + +static void channel_swdemux_tsklet(unsigned long data) +{ + struct channel_info *channel = (struct channel_info *)data; + struct c8sectpfei *fei = channel->fei; + struct tpentry *ptrblk; + unsigned long wp, rp; + int pos, num_packets, n, size; + u8 *buf; + + BUG_ON(!channel); + BUG_ON(!channel->irec); + + ptrblk = &channel->irec->ptr_data[0]; + + wp = readl((void __iomem *)&ptrblk->dma_bus_wp); + rp = readl((void __iomem *)&ptrblk->dma_bus_rp); + + pos = rp - channel->back_buffer_busaddr; + + /* has it wrapped */ + if (wp < rp) + wp = channel->back_buffer_busaddr + FEI_BUFFER_SIZE; + + size = wp - rp; + num_packets = size / PACKET_SIZE; + + /* manage cache so data is visible to CPU */ + dma_sync_single_for_cpu(fei->dev, + rp, + size, + DMA_FROM_DEVICE); + + buf = (u8 *) channel->back_buffer_aligned; + + dev_dbg(fei->dev, + "chan=%d channel=%p num_packets = %d, buf = %p, pos = 0x%x\n\t" + "rp=0x%lx, wp=0x%lx\n", + channel->tsin_id, channel, num_packets, buf, pos, rp, wp); + + for (n = 0; n < num_packets; n++) { + dvb_dmx_swfilter_packets( + &fei->c8sectpfe[0]-> + demux[channel->demux_mapping].dvb_demux, + &buf[pos], 1); + + pos += PACKET_SIZE; + } + + /* advance the read pointer */ + if (wp == (channel->back_buffer_busaddr + FEI_BUFFER_SIZE)) + writel(channel->back_buffer_busaddr, + (void __iomem *)&ptrblk->dma_bus_rp); + else + writel(wp, (void __iomem *)&ptrblk->dma_bus_rp); +} + +static int c8sectpfe_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *demux = dvbdmxfeed->demux; + struct stdemux *stdemux = (struct stdemux *)demux->priv; + struct c8sectpfei *fei = stdemux->c8sectpfei; + struct channel_info *channel; + struct tpentry *ptrblk; + u32 tmp; + unsigned long *bitmap; + + switch (dvbdmxfeed->type) { + case DMX_TYPE_TS: + break; + case DMX_TYPE_SEC: + break; + default: + dev_err(fei->dev, "%s:%d Error bailing\n" + , __func__, __LINE__); + return -EINVAL; + } + + if (dvbdmxfeed->type == DMX_TYPE_TS) { + switch (dvbdmxfeed->pes_type) { + case DMX_PES_VIDEO: + case DMX_PES_AUDIO: + case DMX_PES_TELETEXT: + case DMX_PES_PCR: + case DMX_PES_OTHER: + break; + default: + dev_err(fei->dev, "%s:%d Error bailing\n" + , __func__, __LINE__); + return -EINVAL; + } + } + + mutex_lock(&fei->lock); + + channel = fei->channel_data[stdemux->tsin_index]; + + bitmap = (unsigned long *) channel->pid_buffer_aligned; + + bitmap_set(bitmap, dvbdmxfeed->pid, 1); + + /* manage cache so PID bitmap is visible to HW */ + dma_sync_single_for_device(fei->dev, + channel->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + channel->active = 1; + + if (fei->global_feed_count == 0) { + fei->timer.expires = jiffies + (HZ / POLL_20_HZ_DIV); + add_timer(&fei->timer); + } + + if (stdemux->running_feed_count == 0) { + + dev_info(fei->dev, "Starting channel=%p\n", channel); + + tasklet_init(&channel->tsklet, channel_swdemux_tsklet, + (unsigned long) channel); + + /* Reset the internal inputblock sram pointers */ + writel(channel->fifo, + fei->io + C8SECTPFE_IB_BUFF_STRT(channel->tsin_id)); + writel(channel->fifo + FIFO_LEN - 1, + fei->io + C8SECTPFE_IB_BUFF_END(channel->tsin_id)); + + writel(channel->fifo, + fei->io + C8SECTPFE_IB_READ_PNT(channel->tsin_id)); + writel(channel->fifo, + fei->io + C8SECTPFE_IB_WRT_PNT(channel->tsin_id)); + + + /* reset read / write memdma ptrs for this channel */ + ptrblk = &channel->irec->ptr_data[0]; + writel(channel->back_buffer_busaddr, + (void __iomem *)&ptrblk->dma_busbase); + + tmp = channel->back_buffer_busaddr + FEI_BUFFER_SIZE - 1; + writel(tmp, (void __iomem *)&ptrblk->dma_bustop); + + writel(channel->back_buffer_busaddr, + (void __iomem *)&ptrblk->dma_bus_wp); + + /* Issue a reset and enable InputBlock */ + writel(C8SECTPFE_SYS_ENABLE | C8SECTPFE_SYS_RESET + , fei->io + C8SECTPFE_IB_SYS(channel->tsin_id)); + + /* and enable the tp */ + writel(0x1, (void __iomem *)&channel->irec->tp_enable); + + dev_info(fei->dev, "%s:%d Starting DMA feed on stdemux=%p\n" + , __func__, __LINE__, stdemux); + } + + stdemux->running_feed_count++; + fei->global_feed_count++; + + mutex_unlock(&fei->lock); + + return 0; +} + +static int c8sectpfe_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + + struct dvb_demux *demux = dvbdmxfeed->demux; + struct stdemux *stdemux = (struct stdemux *)demux->priv; + struct c8sectpfei *fei = stdemux->c8sectpfei; + struct channel_info *channel; + struct tpentry *ptrblk; + int idlereq; + u32 tmp; + int ret; + unsigned long *bitmap; + + mutex_lock(&fei->lock); + + channel = fei->channel_data[stdemux->tsin_index]; + + bitmap = (unsigned long *) channel->pid_buffer_aligned; + bitmap_clear(bitmap, dvbdmxfeed->pid, 1); + + /* manage cache so data is visible to HW */ + dma_sync_single_for_device(fei->dev, + channel->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + if (--stdemux->running_feed_count == 0) { + + channel = fei->channel_data[stdemux->tsin_index]; + ptrblk = &channel->irec->ptr_data[0]; + + /* TP re-configuration on page 168 */ + + /* disable IB (prevents more TS data going to memdma) */ + writel(0 , fei->io + C8SECTPFE_IB_SYS(channel->tsin_id)); + + /* disable this channels descriptor */ + writel(0, (void __iomem *)&channel->irec->tp_enable); + + tasklet_disable(&channel->tsklet); + + /* now request memdma channel goes idle */ + idlereq = (1 << channel->tsin_id) | IDLEREQ; + writel(idlereq, fei->io + DMA_IDLE_REQ); + + /* wait for idle irq handler to signal completion */ + ret = wait_for_completion_timeout(&channel->idle_completion, + msecs_to_jiffies(100)); + + if (ret == 0) + dev_warn(fei->dev, + "Timeout waiting for idle irq on tsin%d\n", + channel->tsin_id); + + /* reset read / write ptrs for this channel */ + writel(channel->back_buffer_busaddr, + (void __iomem *)&ptrblk->dma_busbase); + + tmp = channel->back_buffer_busaddr + FEI_BUFFER_SIZE - 1; + writel(tmp, (void __iomem *)&ptrblk->dma_bustop); + + writel(channel->back_buffer_busaddr, + (void __iomem *)&ptrblk->dma_bus_wp); + + dev_info(fei->dev, + "%s:%d stopping DMA feed on stdemux=%p channel=%d\n", + __func__, __LINE__, stdemux, channel->tsin_id); + + /* turn off all PIDS in the bitmap*/ + memset((void *)channel->pid_buffer_aligned + , 0x00, PID_TABLE_SIZE); + + /* manage cache so data is visible to HW */ + dma_sync_single_for_device(fei->dev, + channel->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + channel->active = 0; + } + + if (--fei->global_feed_count == 0) { + dev_dbg(fei->dev, "%s:%d global_feed_count=%d\n" + , __func__, __LINE__, fei->global_feed_count); + + del_timer(&fei->timer); + } + + mutex_unlock(&fei->lock); + + return 0; +} + +static struct channel_info *find_channel(struct c8sectpfei *fei, int tsin_num) +{ + int i; + + for (i = 0; i < C8SECTPFE_MAXCHANNEL; i++) { + if (!fei->channel_data[i]) + continue; + + if (fei->channel_data[i]->tsin_id == tsin_num) + return fei->channel_data[i]; + } + + return NULL; +} + +static void c8sectpfe_getconfig(struct c8sectpfei *fei) +{ + struct c8sectpfe_hw *hw = &fei->hw_stats; + + hw->num_ib = readl(fei->io + SYS_CFG_NUM_IB); + hw->num_mib = readl(fei->io + SYS_CFG_NUM_MIB); + hw->num_swts = readl(fei->io + SYS_CFG_NUM_SWTS); + hw->num_tsout = readl(fei->io + SYS_CFG_NUM_TSOUT); + hw->num_ccsc = readl(fei->io + SYS_CFG_NUM_CCSC); + hw->num_ram = readl(fei->io + SYS_CFG_NUM_RAM); + hw->num_tp = readl(fei->io + SYS_CFG_NUM_TP); + + dev_info(fei->dev, "C8SECTPFE hw supports the following:\n"); + dev_info(fei->dev, "Input Blocks: %d\n", hw->num_ib); + dev_info(fei->dev, "Merged Input Blocks: %d\n", hw->num_mib); + dev_info(fei->dev, "Software Transport Stream Inputs: %d\n" + , hw->num_swts); + dev_info(fei->dev, "Transport Stream Output: %d\n", hw->num_tsout); + dev_info(fei->dev, "Cable Card Convertor: %d\n", hw->num_ccsc); + dev_info(fei->dev, "RAMs supported by C8SECTPFE: %d\n", hw->num_ram); + dev_info(fei->dev, "Tango TPs supported by C8SECTPFE: %d\n" + , hw->num_tp); +} + +static irqreturn_t c8sectpfe_idle_irq_handler(int irq, void *priv) +{ + struct c8sectpfei *fei = priv; + struct channel_info *chan; + int bit; + unsigned long tmp = readl(fei->io + DMA_IDLE_REQ); + + /* page 168 of functional spec: Clear the idle request + by writing 0 to the C8SECTPFE_DMA_IDLE_REQ register. */ + + /* signal idle completion */ + for_each_set_bit(bit, &tmp, fei->hw_stats.num_ib) { + + chan = find_channel(fei, bit); + + if (chan) + complete(&chan->idle_completion); + } + + writel(0, fei->io + DMA_IDLE_REQ); + + return IRQ_HANDLED; +} + + +static void free_input_block(struct c8sectpfei *fei, struct channel_info *tsin) +{ + BUG_ON(!fei); + BUG_ON(!tsin); + + if (tsin->back_buffer_busaddr) + if (!dma_mapping_error(fei->dev, tsin->back_buffer_busaddr)) + dma_unmap_single(fei->dev, tsin->back_buffer_busaddr, + FEI_BUFFER_SIZE, DMA_BIDIRECTIONAL); + + if (tsin->back_buffer_start) + kfree(tsin->back_buffer_start); + + if (tsin->pid_buffer_busaddr) + if (!dma_mapping_error(fei->dev, tsin->pid_buffer_busaddr)) + dma_unmap_single(fei->dev, tsin->pid_buffer_busaddr, + PID_TABLE_SIZE, DMA_BIDIRECTIONAL); + + if (tsin->pid_buffer_start) + kfree(tsin->pid_buffer_start); +} + +#define MAX_NAME 20 + +static int configure_input_block(struct c8sectpfei *fei, + struct channel_info *tsin) +{ + int ret; + u32 tmp = 0; + struct tpentry *ptrblk; + char tsin_pin_name[MAX_NAME]; + + BUG_ON(!fei); + BUG_ON(!tsin); + + dev_info(fei->dev, "%s:%d Configuring channel=%p tsin=%d\n" + , __func__, __LINE__, tsin, tsin->tsin_id); + + init_completion(&tsin->idle_completion); + + tsin->back_buffer_start = kzalloc(FEI_BUFFER_SIZE + + FEI_ALIGNMENT, GFP_KERNEL); + + if (!tsin->back_buffer_start) { + dev_warn(fei->dev, "%s: kzalloc failed\n", __func__); + ret = -ENOMEM; + goto err_unmap; + } + + /* Ensure backbuffer is 32byte aligned */ + tsin->back_buffer_aligned = tsin->back_buffer_start + + FEI_ALIGNMENT; + + tsin->back_buffer_aligned = (void *) + (((uintptr_t) tsin->back_buffer_aligned) & ~0x1F); + + tsin->back_buffer_busaddr = dma_map_single(fei->dev, + (void *)tsin->back_buffer_aligned, + FEI_BUFFER_SIZE, + DMA_BIDIRECTIONAL); + + if (dma_mapping_error(fei->dev, tsin->back_buffer_busaddr)) { + dev_err(fei->dev, "failed to map back_buffer\n"); + ret = -EFAULT; + goto err_unmap; + } + + /* + * The pid buffer can be configured (in hw) for byte or bit + * per pid. By powers of deduction we conclude stih407 family + * is configured (at SoC design stage) for bit per pid. + */ + tsin->pid_buffer_start = kzalloc(2048, GFP_KERNEL); + + if (!tsin->pid_buffer_start) { + dev_err(fei->dev, "%s: kzalloc failed\n", __func__); + ret = -ENOMEM; + goto err_unmap; + } + + /* + * PID buffer needs to be aligned to size of the pid table + * which at bit per pid is 1024 bytes (8192 pids / 8). + * PIDF_BASE register enforces this alignment when writing + * the register. + */ + + tsin->pid_buffer_aligned = tsin->pid_buffer_start + + PID_TABLE_SIZE; + + tsin->pid_buffer_aligned = (void *) + (((uintptr_t) tsin->pid_buffer_aligned) & ~0x3ff); + + tsin->pid_buffer_busaddr = dma_map_single(fei->dev, + tsin->pid_buffer_aligned, + PID_TABLE_SIZE, + DMA_BIDIRECTIONAL); + + if (dma_mapping_error(fei->dev, tsin->pid_buffer_busaddr)) { + dev_err(fei->dev, "failed to map pid_bitmap\n"); + ret = -EFAULT; + goto err_unmap; + } + + /* manage cache so pid bitmap is visible to HW */ + dma_sync_single_for_device(fei->dev, + tsin->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + snprintf(tsin_pin_name, MAX_NAME, "tsin%d-%s", tsin->tsin_id, + (tsin->serial_not_parallel ? "serial" : "parallel")); + + tsin->pstate = pinctrl_lookup_state(fei->pinctrl, tsin_pin_name); + if (IS_ERR(tsin->pstate)) { + dev_err(fei->dev, "%s: pinctrl_lookup_state couldn't find %s state\n" + , __func__, tsin_pin_name); + ret = PTR_ERR(tsin->pstate); + goto err_unmap; + } + + ret = pinctrl_select_state(fei->pinctrl, tsin->pstate); + + if (ret) { + dev_err(fei->dev, "%s: pinctrl_select_state failed\n" + , __func__); + goto err_unmap; + } + + /* Enable this input block */ + tmp = readl(fei->io + SYS_INPUT_CLKEN); + tmp |= BIT(tsin->tsin_id); + writel(tmp, fei->io + SYS_INPUT_CLKEN); + + if (tsin->serial_not_parallel) + tmp |= C8SECTPFE_SERIAL_NOT_PARALLEL; + + if (tsin->invert_ts_clk) + tmp |= C8SECTPFE_INVERT_TSCLK; + + if (tsin->async_not_sync) + tmp |= C8SECTPFE_AYSNC_NOT_SYNC; + + tmp |= C8SECTPFE_ALIGN_BYTE_SOP | C8SECTPFE_BYTE_ENDIANESS_MSB; + + writel(tmp, fei->io + C8SECTPFE_IB_IP_FMT_CFG(tsin->tsin_id)); + + writel(C8SECTPFE_SYNC(0x9) | + C8SECTPFE_DROP(0x9) | + C8SECTPFE_TOKEN(0x47), + fei->io + C8SECTPFE_IB_SYNCLCKDRP_CFG(tsin->tsin_id)); + + writel(TS_PKT_SIZE, fei->io + C8SECTPFE_IB_PKT_LEN(tsin->tsin_id)); + + /* Place the FIFO's at the end of the irec descriptors */ + + tsin->fifo = (tsin->tsin_id * FIFO_LEN); + + writel(tsin->fifo, fei->io + C8SECTPFE_IB_BUFF_STRT(tsin->tsin_id)); + writel(tsin->fifo + FIFO_LEN - 1, + fei->io + C8SECTPFE_IB_BUFF_END(tsin->tsin_id)); + + writel(tsin->fifo, fei->io + C8SECTPFE_IB_READ_PNT(tsin->tsin_id)); + writel(tsin->fifo, fei->io + C8SECTPFE_IB_WRT_PNT(tsin->tsin_id)); + + writel(tsin->pid_buffer_busaddr, + fei->io + PIDF_BASE(tsin->tsin_id)); + + dev_dbg(fei->dev, "chan=%d PIDF_BASE=0x%x pid_bus_addr=0x%x\n", + tsin->tsin_id, readl(fei->io + PIDF_BASE(tsin->tsin_id)), + tsin->pid_buffer_busaddr); + + /* Configure and enable HW PID filtering */ + + /* + * The PID value is created by assembling the first 8 bytes of + * the TS packet into a 64-bit word in big-endian format. A + * slice of that 64-bit word is taken from + * (PID_OFFSET+PID_NUM_BITS-1) to PID_OFFSET. + */ + tmp = (C8SECTPFE_PID_ENABLE | C8SECTPFE_PID_NUMBITS(13) + | C8SECTPFE_PID_OFFSET(40)); + + writel(tmp , fei->io + C8SECTPFE_IB_PID_SET(tsin->tsin_id)); + + dev_dbg(fei->dev, "chan=%d setting wp: %d, rp: %d, buf: %d-%d\n", + tsin->tsin_id, + readl(fei->io + C8SECTPFE_IB_WRT_PNT(tsin->tsin_id)), + readl(fei->io + C8SECTPFE_IB_READ_PNT(tsin->tsin_id)), + readl(fei->io + C8SECTPFE_IB_BUFF_STRT(tsin->tsin_id)), + readl(fei->io + C8SECTPFE_IB_BUFF_END(tsin->tsin_id))); + + /* + * Base address of pointer record block relative to + * base address of DMEM + */ + fei->irec = fei->io + DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + + readl(fei->io + DMA_PTRREC_BASE); + + tsin->irec = fei->irec += tsin->tsin_id; + + ptrblk = &tsin->irec->ptr_data[0]; + + writel(tsin->fifo, + (void __iomem *)&tsin->irec->dma_membase); + + writel(tsin->fifo + FIFO_LEN - 1, + (void __iomem *)&tsin->irec->dma_memtop); + + writel((188 + 7)&~7, + (void __iomem *)&tsin->irec->dma_ts_pktsize); + + writel(0x1, (void __iomem *)&tsin->irec->tp_enable); + + /* read/write pointers with physical bus address */ + writel(tsin->back_buffer_busaddr, + (void __iomem *)&ptrblk->dma_busbase); + + tmp = tsin->back_buffer_busaddr + FEI_BUFFER_SIZE - 1; + writel(tmp, (void __iomem *)&ptrblk->dma_bustop); + + writel(tsin->back_buffer_busaddr, + (void __iomem *)&ptrblk->dma_bus_wp); + + writel(tsin->back_buffer_busaddr, + (void __iomem *)&ptrblk->dma_bus_rp); + + /* initialize tasklet */ + tasklet_init(&tsin->tsklet, channel_swdemux_tsklet, + (unsigned long) tsin); + + return 0; + +err_unmap: + free_input_block(fei, tsin); + return ret; +} + +static irqreturn_t c8sectpfe_error_irq_handler(int irq, void *priv) +{ + struct c8sectpfei *fei = priv; + + dev_err(fei->dev, "%s: error handling not yet implemented\n" + , __func__); + + /* + * TODO FIXME we should detect some error conditions here + * and ideally so something about them! + */ + + return IRQ_HANDLED; +} + +static int c8sectpfe_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *child, *np = dev->of_node; + struct c8sectpfei *fei; + struct resource *res; + int ret, n; + struct channel_info *tsin; + + /* Allocate the c8sectpfei structure */ + fei = devm_kzalloc(dev, sizeof(struct c8sectpfei), GFP_KERNEL); + if (!fei) + return -ENOMEM; + + fei->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "c8sectpfe"); + fei->io = devm_ioremap_resource(dev, res); + if (IS_ERR(fei->io)) + return PTR_ERR(fei->io); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "c8sectpfe-ram"); + fei->sram = devm_ioremap_resource(dev, res); + if (IS_ERR(fei->sram)) + return PTR_ERR(fei->sram); + + fei->sram_size = res->end - res->start; + + fei->idle_irq = platform_get_irq_byname(pdev, "c8sectpfe-idle-irq"); + if (fei->idle_irq < 0) { + dev_err(dev, "Can't get c8sectpfe-idle-irq\n"); + return fei->idle_irq; + } + + fei->error_irq = platform_get_irq_byname(pdev, "c8sectpfe-error-irq"); + if (fei->error_irq < 0) { + dev_err(dev, "Can't get c8sectpfe-error-irq\n"); + return fei->error_irq; + } + + platform_set_drvdata(pdev, fei); + + fei->c8sectpfeclk = devm_clk_get(dev, "c8sectpfe"); + if (IS_ERR(fei->c8sectpfeclk)) { + dev_err(dev, "c8sectpfe clk not found\n"); + return PTR_ERR(fei->c8sectpfeclk); + } + + ret = clk_prepare_enable(fei->c8sectpfeclk); + if (ret) { + dev_err(dev, "Failed to enable c8sectpfe clock\n"); + return ret; + } + + /* to save power disable all IP's (on by default) */ + writel(0, fei->io + SYS_INPUT_CLKEN); + + /* Enable memdma clock */ + writel(MEMDMAENABLE, fei->io + SYS_OTHER_CLKEN); + + /* clear internal sram */ + memset_io(fei->sram, 0x0, fei->sram_size); + + c8sectpfe_getconfig(fei); + + ret = load_slim_core_fw(fei); + if (ret) { + dev_err(dev, "Couldn't load slim core firmware\n"); + goto err_clk_disable; + } + + ret = devm_request_irq(dev, fei->idle_irq, c8sectpfe_idle_irq_handler, + 0, "c8sectpfe-idle-irq", fei); + if (ret) { + dev_err(dev, "Can't register c8sectpfe-idle-irq IRQ.\n"); + goto err_clk_disable; + } + + ret = devm_request_irq(dev, fei->error_irq, + c8sectpfe_error_irq_handler, 0, + "c8sectpfe-error-irq", fei); + if (ret) { + dev_err(dev, "Can't register c8sectpfe-error-irq IRQ.\n"); + goto err_clk_disable; + } + + fei->tsin_count = of_get_child_count(np); + + if (fei->tsin_count > C8SECTPFE_MAX_TSIN_CHAN || + fei->tsin_count > fei->hw_stats.num_ib) { + + dev_err(dev, "More tsin declared than exist on SoC!\n"); + ret = -EINVAL; + goto err_clk_disable; + } + + fei->pinctrl = devm_pinctrl_get(dev); + + if (IS_ERR(fei->pinctrl)) { + dev_err(dev, "Error getting tsin pins\n"); + ret = PTR_ERR(fei->pinctrl); + goto err_clk_disable; + } + + n = 0; + for_each_child_of_node(np, child) { + struct device_node *i2c_bus; + + fei->channel_data[n] = devm_kzalloc(dev, + sizeof(struct channel_info), + GFP_KERNEL); + + if (!fei->channel_data[n]) { + ret = -ENOMEM; + goto err_clk_disable; + } + + tsin = fei->channel_data[n]; + + tsin->fei = fei; + + ret = of_property_read_u32(child, "tsin-num", &tsin->tsin_id); + if (ret) { + dev_err(&pdev->dev, "No tsin_num found\n"); + goto err_clk_disable; + } + + /* sanity check value */ + if (tsin->tsin_id > fei->hw_stats.num_ib) { + dev_err(&pdev->dev, + "tsin-num %d specified greater than number\n\t" + "of input block hw in SoC! (%d)", + tsin->tsin_id, fei->hw_stats.num_ib); + ret = -EINVAL; + goto err_clk_disable; + } + + tsin->invert_ts_clk = of_property_read_bool(child, + "invert-ts-clk"); + + tsin->serial_not_parallel = of_property_read_bool(child, + "serial-not-parallel"); + + tsin->async_not_sync = of_property_read_bool(child, + "async-not-sync"); + + ret = of_property_read_u32(child, "dvb-card", + &tsin->dvb_card); + if (ret) { + dev_err(&pdev->dev, "No dvb-card found\n"); + goto err_clk_disable; + } + + i2c_bus = of_parse_phandle(child, "i2c-bus", 0); + if (!i2c_bus) { + dev_err(&pdev->dev, "No i2c-bus found\n"); + goto err_clk_disable; + } + tsin->i2c_adapter = + of_find_i2c_adapter_by_node(i2c_bus); + if (!tsin->i2c_adapter) { + dev_err(&pdev->dev, "No i2c adapter found\n"); + of_node_put(i2c_bus); + goto err_clk_disable; + } + of_node_put(i2c_bus); + + tsin->rst_gpio = of_get_named_gpio(child, "rst-gpio", 0); + + ret = gpio_is_valid(tsin->rst_gpio); + if (!ret) { + dev_err(dev, + "reset gpio for tsin%d not valid (gpio=%d)\n", + tsin->tsin_id, tsin->rst_gpio); + goto err_clk_disable; + } + + ret = devm_gpio_request_one(dev, tsin->rst_gpio, + GPIOF_OUT_INIT_LOW, "NIM reset"); + if (ret && ret != -EBUSY) { + dev_err(dev, "Can't request tsin%d reset gpio\n" + , fei->channel_data[n]->tsin_id); + goto err_clk_disable; + } + + if (!ret) { + /* toggle reset lines */ + gpio_direction_output(tsin->rst_gpio, 0); + msleep(1); + gpio_direction_output(tsin->rst_gpio, 1); + msleep(1); + } + + tsin->demux_mapping = n; + + dev_info(fei->dev, + "channel=%p n=%d tsin_num=%d, invert-ts-clk=%d\n\t" + "serial-not-parallel=%d pkt-clk-valid=%d dvb-card=%d\n", + fei->channel_data[n], n, + tsin->tsin_id, tsin->invert_ts_clk, + tsin->serial_not_parallel, tsin->async_not_sync, + tsin->dvb_card); + + ret = configure_input_block(fei, fei->channel_data[n]); + + if (ret) { + dev_err(fei->dev, + "configure_input_block tsin=%d failed\n", + fei->channel_data[n]->tsin_id); + goto err_unmap; + } + + n++; + } + + /* Setup timer interrupt */ + init_timer(&fei->timer); + fei->timer.function = c8sectpfe_timer_interrupt; + fei->timer.data = (unsigned long)fei; + + mutex_init(&fei->lock); + + /* Get the configuration information about the tuners */ + ret = c8sectpfe_tuner_register_frontend(&fei->c8sectpfe[0], + (void *)fei, + c8sectpfe_start_feed, + c8sectpfe_stop_feed); + if (ret) + goto err_unmap; + + /* + * STBus target port can access IMEM and DMEM ports + * without waiting for CPU + */ + writel(0x1, fei->io + DMA_PER_STBUS_SYNC); + + dev_info(dev, "Boot the memdma SLIM core\n"); + writel(0x1, fei->io + DMA_CPU_RUN); + + c8sectpfe_debugfs_init(fei); + + return 0; + +err_unmap: + for (n = 0; n < fei->tsin_count; n++) { + tsin = fei->channel_data[n]; + + if (tsin) + free_input_block(fei, tsin); + } + +err_clk_disable: + /* TODO uncomment when upstream has taken a reference on this clk */ + /*clk_disable_unprepare(fei->c8sectpfeclk);*/ + return ret; +} + +static int c8sectpfe_remove(struct platform_device *pdev) +{ + struct c8sectpfei *fei = platform_get_drvdata(pdev); + struct channel_info *channel; + unsigned int n; + + c8sectpfe_tuner_unregister_frontend(fei->c8sectpfe[0], fei); + + /* + * Now loop through and un-configure each of the InputBlock resources + */ + for (n = 0; n < fei->tsin_count; n++) { + channel = fei->channel_data[n]; + if (channel) + free_input_block(fei, channel); + } + + c8sectpfe_debugfs_exit(fei); + + dev_info(fei->dev, "Stopping memdma SLIM core\n"); + if (readl(fei->io + DMA_CPU_RUN)) + writel(0x0, fei->io + DMA_CPU_RUN); + + /* unclock all internal IP's */ + if (readl(fei->io + SYS_INPUT_CLKEN)) + writel(0, fei->io + SYS_INPUT_CLKEN); + if (readl(fei->io + SYS_OTHER_CLKEN)) + writel(0, fei->io + SYS_OTHER_CLKEN); + + /* TODO uncomment when upstream has taken a reference on this clk */ + /* + if (fei->c8sectpfeclk) + clk_disable_unprepare(fei->c8sectpfeclk); + */ + + return 0; +} + +static int load_slim_core_fw(struct c8sectpfei *fei) +{ + const struct firmware *fw = NULL; + unsigned char *pImem = NULL; + unsigned char *pDmem = NULL; + Elf32_Ehdr *ehdr; + Elf32_Phdr *phdr; + int err = 0; + int i; + + dev_info(fei->dev, "Loading firmware: %s\n", FIRMWARE_MEMDMA); + + err = request_firmware(&fw, FIRMWARE_MEMDMA, fei->dev); + if (err) { + dev_err(fei->dev, "Failed to load %s, %d.\n", + FIRMWARE_MEMDMA, err); + return -EINVAL; + } + + /* Check ELF magic */ + ehdr = (Elf32_Ehdr *)fw->data; + if (ehdr->e_ident[EI_MAG0] != ELFMAG0 || + ehdr->e_ident[EI_MAG1] != ELFMAG1 || + ehdr->e_ident[EI_MAG2] != ELFMAG2 || + ehdr->e_ident[EI_MAG3] != ELFMAG3) { + dev_err(fei->dev, "Invalid ELF magic\n"); + err = -ENODEV; + goto done; + } else { + dev_dbg(fei->dev, "Valid ELF header found\n"); + } + + /* Check program headers are within firmware size */ + if (ehdr->e_phoff + (ehdr->e_phnum * sizeof(Elf32_Phdr)) > fw->size) { + dev_err(fei->dev, "Program headers outside of firmware file\n"); + err = -ENODEV; + goto done; + } + + pImem = (unsigned char *) fei->io + DMA_MEMDMA_OFFSET + DMA_IMEM_OFFSET; + pDmem = (unsigned char *) fei->io + DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET; + + phdr = (Elf32_Phdr *)(fw->data + ehdr->e_phoff); + + /* go through the available ELF segments */ + for (i = 0; i < ehdr->e_phnum && !err; i++, phdr++) { + unsigned char *dest; + int imem = 0; + + /* Only consider LOAD segments */ + if (phdr->p_type != PT_LOAD) + continue; + + /* + * Check segment is contained within the fw->data buffer + */ + if (phdr->p_offset + phdr->p_filesz > fw->size) { + dev_err(fei->dev, + "Segment %d is outside of firmware file\n", i); + err = -EINVAL; + break; + } + + /* + * MEMDMA IMEM has executable flag set, otherwise load + * this segment to DMEM. + */ + + if (phdr->p_flags & PF_X) { + dest = pImem; + imem = 1; + } else { + dest = pDmem; + } + + /* + * The Slim ELF file uses 32-bit word addressing for + * load offsets. + */ + + dest += (phdr->p_paddr & 0xFFFFF) * sizeof(unsigned int); + + /* + * For DMEM segments copy the segment data from the ELF + * file and pad segment with zeroes + * + * For IMEM segments, the segment contains 24-bit + * instructions which must be padded to 32-bit + * instructions before being written. The written + * segment is padded with NOP instructions. + */ + + if (!imem) { + dev_info(fei->dev, + "Loading DMEM segment %d 0x%08x\n\t" + "(0x%x bytes) -> 0x%08x (0x%x bytes)\n", + i, phdr->p_paddr, phdr->p_filesz, + (unsigned int)dest, phdr->p_memsz); + + memcpy((void *)dest, (void *)fw->data + phdr->p_offset, + phdr->p_filesz); + + memset((void *)dest + phdr->p_filesz, 0, + phdr->p_memsz - phdr->p_filesz); + } else { + const unsigned char *imem_src = fw->data + + phdr->p_offset; + unsigned char *imem_dest = dest; + int j; + + dev_info(fei->dev, + "Loading IMEM segment %d 0x%08x\n\t" + " (0x%x bytes) -> 0x%08x (0x%x bytes)\n", i, + phdr->p_paddr, phdr->p_filesz, + (unsigned int)dest, + phdr->p_memsz + phdr->p_memsz / 3); + + for (j = 0; j < phdr->p_filesz; j++) { + *imem_dest = *imem_src; + + /* Every 3 bytes, add an additional + * padding zero in destination */ + if (j % 3 == 2) { + imem_dest++; + *imem_dest = 0x00; + } + imem_dest++; + imem_src++; + } + } + } + +done: + release_firmware(fw); + return err; +} + +static struct of_device_id c8sectpfe_match[] = { + { .compatible = "st,stih407-c8sectpfe" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, c8sectpfe_match); + +static struct platform_driver c8sectpfe_driver = { + .driver = { + .name = "c8sectpfe", + .of_match_table = of_match_ptr(c8sectpfe_match), + }, + .probe = c8sectpfe_probe, + .remove = c8sectpfe_remove, +}; + +module_platform_driver(c8sectpfe_driver); + +MODULE_AUTHOR("Peter Bennett <peter.bennett@st.com>"); +MODULE_AUTHOR("Peter Griffin <peter.griffin@linaro.org>"); +MODULE_DESCRIPTION("C8SECTPFE STi DVB Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/tsin/c8sectpfe/c8sectpfe-core.h b/drivers/media/tsin/c8sectpfe/c8sectpfe-core.h new file mode 100644 index 000000000000..6c0a578bbe6c --- /dev/null +++ b/drivers/media/tsin/c8sectpfe/c8sectpfe-core.h @@ -0,0 +1,288 @@ +/* + * c8sectpfe-core.h - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author:Peter Bennett <peter.bennett@st.com> + * Peter Griffin <peter.griffin@linaro.org> + * + * 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. + */ +#ifndef _C8SECTPFE_CORE_H_ +#define _C8SECTPFE_CORE_H_ + +#define C8SECTPFEI_MAXCHANNEL 16 +#define C8SECTPFEI_MAXADAPTER 3 + +#define C8SECTPFE_MAX_TSIN_CHAN 8 + +struct channel_info { + + int tsin_id; + bool invert_ts_clk; + bool serial_not_parallel; + bool async_not_sync; + int i2c; + int dvb_card; + + int rst_gpio; + + struct i2c_adapter *i2c_adapter; + struct i2c_adapter *tuner_i2c; + struct i2c_adapter *lnb_i2c; + struct i2c_client *i2c_client; + struct dvb_frontend *frontend; + + struct pinctrl_state *pstate; + + int demux_mapping; + int active; + + void *back_buffer_start; + void *back_buffer_aligned; + dma_addr_t back_buffer_busaddr; + + void *pid_buffer_start; + void *pid_buffer_aligned; + dma_addr_t pid_buffer_busaddr; + + unsigned long fifo; + + struct completion idle_completion; + struct tasklet_struct tsklet; + + struct c8sectpfei *fei; + struct c8sectpfe_input_record *irec; + +}; + +struct c8sectpfe_hw { + int num_ib; + int num_mib; + int num_swts; + int num_tsout; + int num_ccsc; + int num_ram; + int num_tp; +}; + +struct c8sectpfei { + + struct device *dev; + struct pinctrl *pinctrl; + + struct dentry *root; + struct debugfs_regset32 *regset; + + int tsin_count; + + struct c8sectpfe_hw hw_stats; + + struct c8sectpfe *c8sectpfe[C8SECTPFEI_MAXADAPTER]; + + int mapping[C8SECTPFEI_MAXCHANNEL]; + + struct mutex lock; + + struct timer_list timer; /* timer interrupts for outputs */ + + void __iomem *io; + void __iomem *sram; + + unsigned long sram_size; + + struct channel_info *channel_data[C8SECTPFE_MAX_TSIN_CHAN]; + + struct clk *c8sectpfeclk; + int nima_rst_gpio; + int nimb_rst_gpio; + + int idle_irq; + int error_irq; + + int global_feed_count; + + struct c8sectpfe_input_record *irec; +}; + +/* C8SECTPFE SYS Regs list */ + +#define SYS_INPUT_ERR_STATUS 0x0 +#define SYS_OTHER_ERR_STATUS 0x8 +#define SYS_INPUT_ERR_MASK 0x10 +#define SYS_OTHER_ERR_MASK 0x18 +#define SYS_DMA_ROUTE 0x20 +#define SYS_INPUT_CLKEN 0x30 +#define IBENABLE_MASK 0x7F + +#define SYS_OTHER_CLKEN 0x38 +#define TSDMAENABLE BIT(1) +#define MEMDMAENABLE BIT(0) + +#define SYS_CFG_NUM_IB 0x200 +#define SYS_CFG_NUM_MIB 0x204 +#define SYS_CFG_NUM_SWTS 0x208 +#define SYS_CFG_NUM_TSOUT 0x20C +#define SYS_CFG_NUM_CCSC 0x210 +#define SYS_CFG_NUM_RAM 0x214 +#define SYS_CFG_NUM_TP 0x218 + +/* Input Block Regs */ + +#define C8SECTPFE_INPUTBLK_OFFSET 0x1000 +#define C8SECTPFE_CHANNEL_OFFSET(x) ((x*0x40) + C8SECTPFE_INPUTBLK_OFFSET) + +#define C8SECTPFE_IB_IP_FMT_CFG(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x00) +#define C8SECTPFE_IGNORE_ERR_AT_SOP BIT(7) +#define C8SECTPFE_IGNORE_ERR_IN_PKT BIT(6) +#define C8SECTPFE_IGNORE_ERR_IN_BYTE BIT(5) +#define C8SECTPFE_INVERT_TSCLK BIT(4) +#define C8SECTPFE_ALIGN_BYTE_SOP BIT(3) +#define C8SECTPFE_AYSNC_NOT_SYNC BIT(2) +#define C8SECTPFE_BYTE_ENDIANESS_MSB BIT(1) +#define C8SECTPFE_SERIAL_NOT_PARALLEL BIT(0) + +#define C8SECTPFE_IB_SYNCLCKDRP_CFG(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x04) +#define C8SECTPFE_SYNC(x) (x & 0xf) +#define C8SECTPFE_DROP(x) ((x<<4) & 0xf) +#define C8SECTPFE_TOKEN(x) ((x<<8) & 0xff00) +#define C8SECTPFE_SLDENDIANNESS BIT(16) + +#define C8SECTPFE_IB_TAGBYTES_CFG(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x08) +#define C8SECTPFE_TAG_HEADER(x) (x << 16) +#define C8SECTPFE_TAG_COUNTER(x) ((x<<1) & 0x7fff) +#define C8SECTPFE_TAG_ENABLE BIT(0) + +#define C8SECTPFE_IB_PID_SET(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x0C) +#define C8SECTPFE_PID_OFFSET(x) (x & 0x3f) +#define C8SECTPFE_PID_NUMBITS(x) ((x << 6) & 0xfff) +#define C8SECTPFE_PID_ENABLE BIT(31) + +#define C8SECTPFE_IB_PKT_LEN(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x10) + +#define C8SECTPFE_IB_BUFF_STRT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x14) +#define C8SECTPFE_IB_BUFF_END(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x18) +#define C8SECTPFE_IB_READ_PNT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x1C) +#define C8SECTPFE_IB_WRT_PNT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x20) + +#define C8SECTPFE_IB_PRI_THRLD(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x24) +#define C8SECTPFE_PRI_VALUE(x) (x & 0x7fffff) +#define C8SECTPFE_PRI_LOWPRI(x) ((x & 0xf) << 24) +#define C8SECTPFE_PRI_HIGHPRI(x) ((x & 0xf) << 28) + +#define C8SECTPFE_IB_STAT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x28) +#define C8SECTPFE_STAT_FIFO_OVERFLOW(x) (x & 0x1) +#define C8SECTPFE_STAT_BUFFER_OVERFLOW(x) (x & 0x2) +#define C8SECTPFE_STAT_OUTOFORDERRP(x) (x & 0x4) +#define C8SECTPFE_STAT_PID_OVERFLOW(x) (x & 0x8) +#define C8SECTPFE_STAT_PKT_OVERFLOW(x) (x & 0x10) +#define C8SECTPFE_STAT_ERROR_PACKETS(x) ((x >> 8) & 0xf) +#define C8SECTPFE_STAT_SHORT_PACKETS(x) ((x >> 12) & 0xf) + +#define C8SECTPFE_IB_MASK(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x2C) +#define C8SECTPFE_MASK_FIFO_OVERFLOW BIT(0) +#define C8SECTPFE_MASK_BUFFER_OVERFLOW BIT(1) +#define C8SECTPFE_MASK_OUTOFORDERRP(x) BIT(2) +#define C8SECTPFE_MASK_PID_OVERFLOW(x) BIT(3) +#define C8SECTPFE_MASK_PKT_OVERFLOW(x) BIT(4) +#define C8SECTPFE_MASK_ERROR_PACKETS(x) ((x & 0xf) << 8) +#define C8SECTPFE_MASK_SHORT_PACKETS(x) ((x & 0xf) >> 12) + +#define C8SECTPFE_IB_SYS(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x30) +#define C8SECTPFE_SYS_RESET BIT(1) +#define C8SECTPFE_SYS_ENABLE BIT(0) + +/* + * Ponter record data structure required for each input block + * see Table 82 on page 167 + */ + +struct tpentry { + /* The following entries are bus addresses for memdma */ + unsigned long dma_busbase; + unsigned long dma_bustop; + unsigned long dma_bus_wp; + unsigned long dma_bus_rp; +}; + +struct c8sectpfe_input_record { + unsigned long dma_membase; /* Internal sram base address */ + unsigned long dma_memtop; /* Internal sram top address */ + + /* + * TS packet size, including tag bytes added by input block, + * rounded up to the next multiple of 8 bytes. The packet size, + * including any tagging bytes and rounded up to the nearest + * multiple of 8 bytes must be less than 255 bytes. + */ + unsigned long dma_ts_pktsize; + unsigned long tp_enable; + struct tpentry ptr_data[1]; +}; + +#define DMA_MEMDMA_OFFSET 0x4000 +#define DMA_IMEM_OFFSET 0x0 +#define DMA_DMEM_OFFSET 0x4000 +#define DMA_CPU 0x8000 +#define DMA_PER_OFFSET 0xb000 + +/* XP70 Slim core regs */ +#define DMA_CPU_ID (DMA_MEMDMA_OFFSET + DMA_CPU + 0x0) +#define DMA_CPU_VCR (DMA_MEMDMA_OFFSET + DMA_CPU + 0x4) +#define DMA_CPU_RUN (DMA_MEMDMA_OFFSET + DMA_CPU + 0x8) +#define DMA_CPU_CLOCKGATE (DMA_MEMDMA_OFFSET + DMA_CPU + 0xc) +#define DMA_CPU_PC (DMA_MEMDMA_OFFSET + DMA_CPU + 0x20) + +/* Enable Interrupt for a IB */ +#define DMA_PER_TPn_DREQ_MASK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xd00) +/* Ack interrupt by setting corresponding bit */ +#define DMA_PER_TPn_DACK_SET (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xd80) +#define DMA_PER_TPn_DREQ (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xe00) +#define DMA_PER_TPn_DACK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xe80) +#define DMA_PER_DREQ_MODE (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf80) +#define DMA_PER_STBUS_SYNC (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf88) +#define DMA_PER_STBUS_ACCESS (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf8c) +#define DMA_PER_STBUS_ADDRESS (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf90) +#define DMA_PER_IDLE_INT (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfa8) +#define DMA_PER_PRIORITY (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfac) +#define DMA_PER_MAX_OPCODE (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfb0) +#define DMA_PER_MAX_CHUNK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfb4) +#define DMA_PER_PAGE_SIZE (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfbc) +#define DMA_PER_MBOX_STATUS (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfc0) +#define DMA_PER_MBOX_SET (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfc8) +#define DMA_PER_MBOX_CLEAR (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfd0) +#define DMA_PER_MBOX_MASK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfd8) +#define DMA_PER_INJECT_PKT_SRC (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfe0) +#define DMA_PER_INJECT_PKT_DEST (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfe4) +#define DMA_PER_INJECT_PKT_ADDR (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfe8) +#define DMA_PER_INJECT_PKT (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfec) +#define DMA_PER_PAT_PTR_INIT (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xff0) +#define DMA_PER_PAT_PTR (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xff4) +#define DMA_PER_SLEEP_MASK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xff8) +#define DMA_PER_SLEEP_COUNTER (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xffc) +/* #define DMA_RF_CPUREGn DMA_RFBASEADDR n=0 to 15) slim regsa */ + +/* The following are from DMA_DMEM_BaseAddress */ +#define DMA_FIRMWARE_VERSION (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x0) +#define DMA_PTRREC_BASE (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x4) +#define DMA_PTRREC_INPUT_OFFSET (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x8) +#define DMA_ERRREC_BASE (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0xc) +#define DMA_ERROR_RECORD(n) ((n*4) + DMA_ERRREC_BASE + 0x4) +#define DMA_IDLE_REQ (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x10) +#define IDLEREQ BIT(31) + +#define DMA_FIRMWARE_CONFIG (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x14) + +/* Regs for PID Filter */ + +#define PIDF_OFFSET 0x2800 +#define PIDF_BASE(n) ((n*4) + PIDF_OFFSET) +#define PIDF_LEAK_ENABLE (PIDF_OFFSET + 0x100) +#define PIDF_LEAK_STATUS (PIDF_OFFSET + 0x108) +#define PIDF_LEAK_COUNT_RESET (PIDF_OFFSET + 0x110) +#define PIDF_LEAK_COUNTER (PIDF_OFFSET + 0x114) + +#endif /* _C8SECTPFE_CORE_H_ */ diff --git a/drivers/media/tsin/c8sectpfe/c8sectpfe-debugfs.c b/drivers/media/tsin/c8sectpfe/c8sectpfe-debugfs.c new file mode 100644 index 000000000000..e055d11f2b96 --- /dev/null +++ b/drivers/media/tsin/c8sectpfe/c8sectpfe-debugfs.c @@ -0,0 +1,276 @@ +/* + * c8sectpfe-debugfs.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author: Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include "c8sectpfe-core.h" + +#define dump_register(nm ...) \ +{ \ + .name = #nm, \ + .offset = nm, \ +} + +static const struct debugfs_reg32 fei_sys_regs[] = { + dump_register(SYS_INPUT_ERR_STATUS), + dump_register(SYS_OTHER_ERR_STATUS), + dump_register(SYS_INPUT_ERR_MASK), + dump_register(SYS_DMA_ROUTE), + dump_register(SYS_INPUT_CLKEN), + dump_register(IBENABLE_MASK), + dump_register(SYS_OTHER_CLKEN), + dump_register(SYS_CFG_NUM_IB), + dump_register(SYS_CFG_NUM_MIB), + dump_register(SYS_CFG_NUM_SWTS), + dump_register(SYS_CFG_NUM_TSOUT), + dump_register(SYS_CFG_NUM_CCSC), + dump_register(SYS_CFG_NUM_RAM), + dump_register(SYS_CFG_NUM_TP), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(0)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(0)), + dump_register(C8SECTPFE_IB_PID_SET(0)), + dump_register(C8SECTPFE_IB_PKT_LEN(0)), + dump_register(C8SECTPFE_IB_BUFF_STRT(0)), + dump_register(C8SECTPFE_IB_BUFF_END(0)), + dump_register(C8SECTPFE_IB_READ_PNT(0)), + dump_register(C8SECTPFE_IB_WRT_PNT(0)), + dump_register(C8SECTPFE_IB_PRI_THRLD(0)), + dump_register(C8SECTPFE_IB_STAT(0)), + dump_register(C8SECTPFE_IB_MASK(0)), + dump_register(C8SECTPFE_IB_SYS(0)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(1)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(1)), + dump_register(C8SECTPFE_IB_PID_SET(1)), + dump_register(C8SECTPFE_IB_PKT_LEN(1)), + dump_register(C8SECTPFE_IB_BUFF_STRT(1)), + dump_register(C8SECTPFE_IB_BUFF_END(1)), + dump_register(C8SECTPFE_IB_READ_PNT(1)), + dump_register(C8SECTPFE_IB_WRT_PNT(1)), + dump_register(C8SECTPFE_IB_PRI_THRLD(1)), + dump_register(C8SECTPFE_IB_STAT(1)), + dump_register(C8SECTPFE_IB_MASK(1)), + dump_register(C8SECTPFE_IB_SYS(1)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(2)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(2)), + dump_register(C8SECTPFE_IB_PID_SET(2)), + dump_register(C8SECTPFE_IB_PKT_LEN(2)), + dump_register(C8SECTPFE_IB_BUFF_STRT(2)), + dump_register(C8SECTPFE_IB_BUFF_END(2)), + dump_register(C8SECTPFE_IB_READ_PNT(2)), + dump_register(C8SECTPFE_IB_WRT_PNT(2)), + dump_register(C8SECTPFE_IB_PRI_THRLD(2)), + dump_register(C8SECTPFE_IB_STAT(2)), + dump_register(C8SECTPFE_IB_MASK(2)), + dump_register(C8SECTPFE_IB_SYS(2)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(3)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(3)), + dump_register(C8SECTPFE_IB_PID_SET(3)), + dump_register(C8SECTPFE_IB_PKT_LEN(3)), + dump_register(C8SECTPFE_IB_BUFF_STRT(3)), + dump_register(C8SECTPFE_IB_BUFF_END(3)), + dump_register(C8SECTPFE_IB_READ_PNT(3)), + dump_register(C8SECTPFE_IB_WRT_PNT(3)), + dump_register(C8SECTPFE_IB_PRI_THRLD(3)), + dump_register(C8SECTPFE_IB_STAT(3)), + dump_register(C8SECTPFE_IB_MASK(3)), + dump_register(C8SECTPFE_IB_SYS(3)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(4)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(4)), + dump_register(C8SECTPFE_IB_PID_SET(4)), + dump_register(C8SECTPFE_IB_PKT_LEN(4)), + dump_register(C8SECTPFE_IB_BUFF_STRT(4)), + dump_register(C8SECTPFE_IB_BUFF_END(4)), + dump_register(C8SECTPFE_IB_READ_PNT(4)), + dump_register(C8SECTPFE_IB_WRT_PNT(4)), + dump_register(C8SECTPFE_IB_PRI_THRLD(4)), + dump_register(C8SECTPFE_IB_STAT(4)), + dump_register(C8SECTPFE_IB_MASK(4)), + dump_register(C8SECTPFE_IB_SYS(4)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(5)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(5)), + dump_register(C8SECTPFE_IB_PID_SET(5)), + dump_register(C8SECTPFE_IB_PKT_LEN(5)), + dump_register(C8SECTPFE_IB_BUFF_STRT(5)), + dump_register(C8SECTPFE_IB_BUFF_END(5)), + dump_register(C8SECTPFE_IB_READ_PNT(5)), + dump_register(C8SECTPFE_IB_WRT_PNT(5)), + dump_register(C8SECTPFE_IB_PRI_THRLD(5)), + dump_register(C8SECTPFE_IB_STAT(5)), + dump_register(C8SECTPFE_IB_MASK(5)), + dump_register(C8SECTPFE_IB_SYS(5)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(6)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(6)), + dump_register(C8SECTPFE_IB_PID_SET(6)), + dump_register(C8SECTPFE_IB_PKT_LEN(6)), + dump_register(C8SECTPFE_IB_BUFF_STRT(6)), + dump_register(C8SECTPFE_IB_BUFF_END(6)), + dump_register(C8SECTPFE_IB_READ_PNT(6)), + dump_register(C8SECTPFE_IB_WRT_PNT(6)), + dump_register(C8SECTPFE_IB_PRI_THRLD(6)), + dump_register(C8SECTPFE_IB_STAT(6)), + dump_register(C8SECTPFE_IB_MASK(6)), + dump_register(C8SECTPFE_IB_SYS(6)), + + dump_register(DMA_CPU_ID), + dump_register(DMA_CPU_VCR), + dump_register(DMA_CPU_RUN), + dump_register(DMA_CPU_PC), + + dump_register(DMA_PER_TPn_DREQ_MASK), + dump_register(DMA_PER_TPn_DACK_SET), + dump_register(DMA_PER_TPn_DREQ), + dump_register(DMA_PER_TPn_DACK), + dump_register(DMA_PER_DREQ_MODE), + dump_register(DMA_PER_STBUS_SYNC), + dump_register(DMA_PER_STBUS_ACCESS), + dump_register(DMA_PER_STBUS_ADDRESS), + dump_register(DMA_PER_IDLE_INT), + dump_register(DMA_PER_PRIORITY), + dump_register(DMA_PER_MAX_OPCODE), + dump_register(DMA_PER_MAX_CHUNK), + dump_register(DMA_PER_PAGE_SIZE), + dump_register(DMA_PER_MBOX_STATUS), + dump_register(DMA_PER_MBOX_SET), + dump_register(DMA_PER_MBOX_CLEAR), + dump_register(DMA_PER_MBOX_MASK), + dump_register(DMA_PER_INJECT_PKT_SRC), + dump_register(DMA_PER_INJECT_PKT_DEST), + dump_register(DMA_PER_INJECT_PKT_ADDR), + dump_register(DMA_PER_INJECT_PKT), + dump_register(DMA_PER_PAT_PTR_INIT), + dump_register(DMA_PER_PAT_PTR), + dump_register(DMA_PER_SLEEP_MASK), + dump_register(DMA_PER_SLEEP_COUNTER), + + dump_register(DMA_FIRMWARE_VERSION), + dump_register(DMA_PTRREC_BASE), + dump_register(DMA_PTRREC_INPUT_OFFSET), + dump_register(DMA_ERRREC_BASE), + + dump_register(DMA_ERROR_RECORD(0)), + dump_register(DMA_ERROR_RECORD(1)), + dump_register(DMA_ERROR_RECORD(2)), + dump_register(DMA_ERROR_RECORD(3)), + dump_register(DMA_ERROR_RECORD(4)), + dump_register(DMA_ERROR_RECORD(5)), + dump_register(DMA_ERROR_RECORD(6)), + dump_register(DMA_ERROR_RECORD(7)), + dump_register(DMA_ERROR_RECORD(8)), + dump_register(DMA_ERROR_RECORD(9)), + dump_register(DMA_ERROR_RECORD(10)), + dump_register(DMA_ERROR_RECORD(11)), + dump_register(DMA_ERROR_RECORD(12)), + dump_register(DMA_ERROR_RECORD(13)), + dump_register(DMA_ERROR_RECORD(14)), + dump_register(DMA_ERROR_RECORD(15)), + dump_register(DMA_ERROR_RECORD(16)), + dump_register(DMA_ERROR_RECORD(17)), + dump_register(DMA_ERROR_RECORD(18)), + dump_register(DMA_ERROR_RECORD(19)), + dump_register(DMA_ERROR_RECORD(20)), + dump_register(DMA_ERROR_RECORD(21)), + dump_register(DMA_ERROR_RECORD(22)), + + dump_register(DMA_IDLE_REQ), + dump_register(DMA_FIRMWARE_CONFIG), + + dump_register(PIDF_BASE(0)), + dump_register(PIDF_BASE(1)), + dump_register(PIDF_BASE(2)), + dump_register(PIDF_BASE(3)), + dump_register(PIDF_BASE(4)), + dump_register(PIDF_BASE(5)), + dump_register(PIDF_BASE(6)), + dump_register(PIDF_BASE(7)), + dump_register(PIDF_BASE(8)), + dump_register(PIDF_BASE(9)), + dump_register(PIDF_BASE(10)), + dump_register(PIDF_BASE(11)), + dump_register(PIDF_BASE(12)), + dump_register(PIDF_BASE(13)), + dump_register(PIDF_BASE(14)), + dump_register(PIDF_BASE(15)), + dump_register(PIDF_BASE(16)), + dump_register(PIDF_BASE(17)), + dump_register(PIDF_BASE(18)), + dump_register(PIDF_BASE(19)), + dump_register(PIDF_BASE(20)), + dump_register(PIDF_BASE(21)), + dump_register(PIDF_BASE(22)), + dump_register(PIDF_LEAK_ENABLE), + dump_register(PIDF_LEAK_STATUS), + dump_register(PIDF_LEAK_COUNT_RESET), + dump_register(PIDF_LEAK_COUNTER), +}; + +void c8sectpfe_debugfs_init(struct c8sectpfei *fei) +{ + struct dentry *root; + struct dentry *file; + int ret; + + root = debugfs_create_dir("c8sectpfe", NULL); + if (!root) + return; + + fei->root = root; + + fei->regset = devm_kzalloc(fei->dev, sizeof(*fei->regset), GFP_KERNEL); + if (!fei->regset) { + dev_err(fei->dev, "no more memory for debugfs\n"); + goto err; + } + + fei->regset->regs = fei_sys_regs; + fei->regset->nregs = ARRAY_SIZE(fei_sys_regs); + fei->regset->base = fei->io; + + file = debugfs_create_regset32("registers", S_IRUGO, root, + fei->regset); + if (!file) { + dev_err(fei->dev, + "%s not able to create 'registers' debugfs\n"); + goto err; + } + + return; + +err: + debugfs_remove_recursive(root); + return; +} + +void c8sectpfe_debugfs_exit(struct c8sectpfei *fei) +{ + if (fei->root) + debugfs_remove_recursive(fei->root); + + fei->root = NULL; +} diff --git a/drivers/media/tsin/c8sectpfe/c8sectpfe-debugfs.h b/drivers/media/tsin/c8sectpfe/c8sectpfe-debugfs.h new file mode 100644 index 000000000000..8af1ac1378c8 --- /dev/null +++ b/drivers/media/tsin/c8sectpfe/c8sectpfe-debugfs.h @@ -0,0 +1,26 @@ +/** + * c8sectpfe-debugfs.h - C8SECTPFE STi DVB driver debugfs header + * + * Copyright (c) STMicroelectronics 2015 + * + * Authors: Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __C8SECTPFE_DEBUG_H +#define __C8SECTPFE_DEBUG_H + +#include "c8sectpfe-core.h" + +void c8sectpfe_debugfs_init(struct c8sectpfei *); +void c8sectpfe_debugfs_exit(struct c8sectpfei *); + +#endif /* __C8SECTPFE_DEBUG_H */ diff --git a/drivers/media/tsin/c8sectpfe/c8sectpfe-dvb.c b/drivers/media/tsin/c8sectpfe/c8sectpfe-dvb.c new file mode 100644 index 000000000000..b16a2dc81d59 --- /dev/null +++ b/drivers/media/tsin/c8sectpfe/c8sectpfe-dvb.c @@ -0,0 +1,309 @@ +/* + * c8sectpfe-dvb.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author Peter Griffin <peter.griffin@linaro.org> + * + * 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 <linux/completion.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/version.h> + +#include <dt-bindings/media/c8sectpfe.h> + +#include "c8sectpfe-common.h" +#include "c8sectpfe-core.h" +#include "c8sectpfe-dvb.h" + +#include "dvb-pll.h" +#include "lnbh24.h" +#include "stv0367.h" +#include "stv0367_priv.h" +#include "stv6110x.h" +#include "stv090x.h" +#include "tda18212.h" + +static inline const char *dvb_card_str(unsigned int c) +{ + switch (c) { + case STV0367_PLL_BOARD_NIMA: return "STV0367_PLL_BOARD_NIMA"; + case STV0367_PLL_BOARD_NIMB: return "STV0367_PLL_BOARD_NIMB"; + case STV0367_TDA18212_NIMA_1: return "STV0367_TDA18212_NIMA_1"; + case STV0367_TDA18212_NIMA_2: return "STV0367_TDA18212_NIMA_2"; + case STV0367_TDA18212_NIMB_1: return "STV0367_TDA18212_NIMB_1"; + case STV0367_TDA18212_NIMB_2: return "STV0367_TDA18212_NIMB_2"; + case STV0903_6110_LNB24_NIMA: return "STV0903_6110_LNB24_NIMA"; + case STV0903_6110_LNB24_NIMB: return "STV0903_6110_LNB24_NIMB"; + default: return "unknown dvb frontend card"; + } +} + +static struct stv090x_config stv090x_config = { + .device = STV0903, + .demod_mode = STV090x_SINGLE, + .clk_mode = STV090x_CLK_EXT, + .xtal = 16000000, + .address = 0x68, + + .ts1_mode = STV090x_TSMODE_SERIAL_CONTINUOUS, + .ts2_mode = STV090x_TSMODE_SERIAL_CONTINUOUS, + + .repeater_level = STV090x_RPTLEVEL_64, + + .tuner_init = NULL, + .tuner_set_mode = NULL, + .tuner_set_frequency = NULL, + .tuner_get_frequency = NULL, + .tuner_set_bandwidth = NULL, + .tuner_get_bandwidth = NULL, + .tuner_set_bbgain = NULL, + .tuner_get_bbgain = NULL, + .tuner_set_refclk = NULL, + .tuner_get_status = NULL, +}; + +static struct stv6110x_config stv6110x_config = { + .addr = 0x60, + .refclk = 16000000, +}; + +#define NIMA 0 +#define NIMB 1 + +struct stv0367_config stv0367_pll_config[] = { + { + .demod_address = 0x1c, + .xtal = 27000000, + .if_khz = 36166, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, { + .demod_address = 0x1d, + .xtal = 27000000, + .if_khz = 36166, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, +}; + +struct stv0367_config stv0367_tda18212_config[] = { + { + .demod_address = 0x1c, + .xtal = 16000000, + .if_khz = 4500, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, { + .demod_address = 0x1d, + .xtal = 16000000, + .if_khz = 4500, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, { + .demod_address = 0x1e, + .xtal = 16000000, + .if_khz = 4500, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, +}; + +static struct tda18212_config tda18212_conf = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0) + .i2c_address = 0x60, /* (0xc0 >> 1),*/ +#endif + .if_dvbt_6 = 4150, + .if_dvbt_7 = 4150, + .if_dvbt_8 = 4500, + .if_dvbc = 5000, +}; + +int c8sectpfe_frontend_attach(struct dvb_frontend **fe, + struct c8sectpfe *c8sectpfe, + struct channel_info *tsin, int chan_num) +{ +#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 17, 0) + struct tda18212_config *tda18212; +#endif + struct stv6110x_devctl *fe2; + + BUG_ON(!tsin); + + switch (tsin->dvb_card) { + + case STV0367_PLL_BOARD_NIMA: + case STV0367_PLL_BOARD_NIMB: + if (tsin->dvb_card == STV0367_PLL_BOARD_NIMA) + *fe = dvb_attach(stv0367ter_attach, + &stv0367_pll_config[NIMA], tsin->i2c_adapter); + else + *fe = dvb_attach(stv0367ter_attach, + &stv0367_pll_config[NIMB], tsin->i2c_adapter); + + if (!*fe) { + dev_err(c8sectpfe->device, + "%s: stv0367ter_attach failed for NIM card %s\n" + , __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + }; + + /* + * init the demod so that i2c gate_ctrl + * to the tuner works correctly + */ + (*fe)->ops.init(*fe); + + if (!dvb_attach(dvb_pll_attach, *fe, 0x60, + tsin->i2c_adapter, DVB_PLL_THOMSON_DTT7546X)) { + + dev_err(c8sectpfe->device, + "%s: DVB_PLL_THOMSON_DTT7546X attach failed\n\t" + "for NIM card %s\n", + __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + } + break; + + case STV0367_TDA18212_NIMA_1: + case STV0367_TDA18212_NIMA_2: + case STV0367_TDA18212_NIMB_1: + case STV0367_TDA18212_NIMB_2: + if (tsin->dvb_card == STV0367_TDA18212_NIMA_1) + *fe = dvb_attach(stv0367ter_attach, + &stv0367_tda18212_config[0], tsin->i2c_adapter); + else if (tsin->dvb_card == STV0367_TDA18212_NIMB_1) + *fe = dvb_attach(stv0367ter_attach, + &stv0367_tda18212_config[1], tsin->i2c_adapter); + else + *fe = dvb_attach(stv0367ter_attach, + &stv0367_tda18212_config[2], tsin->i2c_adapter); + + if (!*fe) { + dev_err(c8sectpfe->device, + "%s: stv0367ter_attach failed for NIM card %s\n" + , __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + }; + + /* + * init the demod so that i2c gate_ctrl + * to the tuner works correctly + */ + (*fe)->ops.init(*fe); + +#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 17, 0) + /* Allocate the tda18212 structure */ + tda18212 = devm_kzalloc(c8sectpfe->device, + sizeof(struct tda18212_config), + GFP_KERNEL); + if (!tda18212) { + dev_err(c8sectpfe->device, + "%s: devm_kzalloc failed\n", __func__); + return -ENOMEM; + } + + memcpy(tda18212, &tda18212_conf, + sizeof(struct tda18212_config)); + + struct i2c_client *client; + struct i2c_board_info tda18212_info = { + .type = "tda18212", + .addr = 0x60, + .platform_data = tda18212, + }; + + tda18212->fe = (*fe); + + /* attach tuner */ + request_module("tda18212"); + client = i2c_new_device(tsin->i2c_adapter, &tda18212_info); + if (!client || !client->dev.driver) { + dvb_frontend_detach(*fe); + return -ENODEV; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + dvb_frontend_detach(*fe); + return -ENODEV; + } + + tsin->i2c_client = client; +#else + /* for older sti 3.10 kernel */ + if (!dvb_attach(tda18212_attach, *fe, tsin->i2c_adapter, + &tda18212_conf)) { + dev_err(c8sectpfe->device, + "%s: tda18212_attach failed for NIM card %s\n", + __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + } +#endif + break; + + case STV0903_6110_LNB24_NIMA: + *fe = dvb_attach(stv090x_attach, &stv090x_config, + tsin->i2c_adapter, STV090x_DEMODULATOR_0); + if (!*fe) { + dev_err(c8sectpfe->device, "%s: stv090x_attach failed\n\t" + "for NIM card %s\n", + __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + } + + fe2 = dvb_attach(stv6110x_attach, *fe, + &stv6110x_config, tsin->i2c_adapter); + if (!fe2) { + dev_err(c8sectpfe->device, + "%s: stv6110x_attach failed for NIM card %s\n" + , __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + }; + + stv090x_config.tuner_init = fe2->tuner_init; + stv090x_config.tuner_set_mode = fe2->tuner_set_mode; + stv090x_config.tuner_set_frequency = fe2->tuner_set_frequency; + stv090x_config.tuner_get_frequency = fe2->tuner_get_frequency; + stv090x_config.tuner_set_bandwidth = fe2->tuner_set_bandwidth; + stv090x_config.tuner_get_bandwidth = fe2->tuner_get_bandwidth; + stv090x_config.tuner_set_bbgain = fe2->tuner_set_bbgain; + stv090x_config.tuner_get_bbgain = fe2->tuner_get_bbgain; + stv090x_config.tuner_set_refclk = fe2->tuner_set_refclk; + stv090x_config.tuner_get_status = fe2->tuner_get_status; + + dvb_attach(lnbh24_attach, *fe, tsin->i2c_adapter, 0, 0, 0x9); + break; + + default: + dev_err(c8sectpfe->device, + "%s: DVB frontend card %s not yet supported\n", + __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + break; + } + + (*fe)->id = chan_num; + + dev_info(c8sectpfe->device, + "DVB frontend card %s successfully attached", + dvb_card_str(tsin->dvb_card)); + return 0; +} diff --git a/drivers/media/tsin/c8sectpfe/c8sectpfe-dvb.h b/drivers/media/tsin/c8sectpfe/c8sectpfe-dvb.h new file mode 100644 index 000000000000..2573a8c12741 --- /dev/null +++ b/drivers/media/tsin/c8sectpfe/c8sectpfe-dvb.h @@ -0,0 +1,20 @@ +/* + * c8sectpfe-common.h - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author:Peter Bennett <peter.bennett@st.com> + * + * 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. + */ +#ifndef _C8SECTPFE_DVB_H_ +#define _C8SECTPFE_DVB_H_ + +int c8sectpfe_frontend_attach(struct dvb_frontend **fe, + struct c8sectpfe *c8sectpfe, struct channel_info *tsin, + int chan_num); + +#endif diff --git a/include/dt-bindings/media/c8sectpfe.h b/include/dt-bindings/media/c8sectpfe.h new file mode 100644 index 000000000000..d332774f90ed --- /dev/null +++ b/include/dt-bindings/media/c8sectpfe.h @@ -0,0 +1,14 @@ +#ifndef __DT_C8SECTPFE_H +#define __DT_C8SECTPFE_H + +#define STV0367_PLL_BOARD_NIMA 0 +#define STV0367_PLL_BOARD_NIMB 1 +#define STV0367_TDA18212_NIMA_1 2 +#define STV0367_TDA18212_NIMA_2 3 +#define STV0367_TDA18212_NIMB_1 4 +#define STV0367_TDA18212_NIMB_2 5 + +#define STV0903_6110_LNB24_NIMA 6 +#define STV0903_6110_LNB24_NIMB 7 + +#endif /* __DT_C8SECTPFE_H */ |