| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * c8sectpfe-common.c - C8SECTPFE STi DVB driver | |
| * | |
| * Copyright (c) STMicroelectronics 2015 | |
| * | |
| * Author: Peter Griffin <peter.griffin@linaro.org> | |
| * | |
| */ | |
| #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 <media/dmxdev.h> | |
| #include <media/dvbdev.h> | |
| #include <media/dvb_demux.h> | |
| #include <media/dvb_frontend.h> | |
| #include <media/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].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) { | |
| if (tsin->frontend) { | |
| dvb_unregister_frontend(tsin->frontend); | |
| dvb_frontend_detach(tsin->frontend); | |
| } | |
| i2c_put_adapter(tsin->i2c_adapter); | |
| if (tsin->i2c_client) { | |
| 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; | |
| } |