| // 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; |
| } |