1// SPDX-License-Identifier: GPL-2.0
2/*
3 * c8sectpfe-common.c - C8SECTPFE STi DVB driver
4 *
5 * Copyright (c) STMicroelectronics 2015
6 *
7 *   Author: Peter Griffin <peter.griffin@linaro.org>
8 *
9 */
10#include <linux/completion.h>
11#include <linux/delay.h>
12#include <linux/device.h>
13#include <linux/dvb/dmx.h>
14#include <linux/errno.h>
15#include <linux/init.h>
16#include <linux/interrupt.h>
17#include <linux/io.h>
18#include <linux/ioport.h>
19#include <linux/module.h>
20#include <linux/slab.h>
21#include <linux/time.h>
22#include <linux/wait.h>
23
24#include <media/dmxdev.h>
25#include <media/dvbdev.h>
26#include <media/dvb_demux.h>
27#include <media/dvb_frontend.h>
28#include <media/dvb_net.h>
29
30#include "c8sectpfe-common.h"
31#include "c8sectpfe-core.h"
32#include "c8sectpfe-dvb.h"
33
34static int register_dvb(struct stdemux *demux, struct dvb_adapter *adap,
35				void *start_feed, void *stop_feed,
36				struct c8sectpfei *fei)
37{
38	int result;
39
40	demux->dvb_demux.dmx.capabilities = DMX_TS_FILTERING |
41					DMX_SECTION_FILTERING |
42					DMX_MEMORY_BASED_FILTERING;
43
44	demux->dvb_demux.priv = demux;
45	demux->dvb_demux.filternum = C8SECTPFE_MAXCHANNEL;
46	demux->dvb_demux.feednum = C8SECTPFE_MAXCHANNEL;
47
48	demux->dvb_demux.start_feed = start_feed;
49	demux->dvb_demux.stop_feed = stop_feed;
50	demux->dvb_demux.write_to_decoder = NULL;
51
52	result = dvb_dmx_init(&demux->dvb_demux);
53	if (result < 0) {
54		dev_err(fei->dev, "dvb_dmx_init failed (errno = %d)\n",
55			result);
56		goto err_dmx;
57	}
58
59	demux->dmxdev.filternum = demux->dvb_demux.filternum;
60	demux->dmxdev.demux = &demux->dvb_demux.dmx;
61	demux->dmxdev.capabilities = 0;
62
63	result = dvb_dmxdev_init(&demux->dmxdev, adap);
64	if (result < 0) {
65		dev_err(fei->dev, "dvb_dmxdev_init failed (errno = %d)\n",
66			result);
67
68		goto err_dmxdev;
69	}
70
71	demux->hw_frontend.source = DMX_FRONTEND_0 + demux->tsin_index;
72
73	result = demux->dvb_demux.dmx.add_frontend(&demux->dvb_demux.dmx,
74						&demux->hw_frontend);
75	if (result < 0) {
76		dev_err(fei->dev, "add_frontend failed (errno = %d)\n", result);
77		goto err_fe_hw;
78	}
79
80	demux->mem_frontend.source = DMX_MEMORY_FE;
81	result = demux->dvb_demux.dmx.add_frontend(&demux->dvb_demux.dmx,
82						&demux->mem_frontend);
83	if (result < 0) {
84		dev_err(fei->dev, "add_frontend failed (%d)\n", result);
85		goto err_fe_mem;
86	}
87
88	result = demux->dvb_demux.dmx.connect_frontend(&demux->dvb_demux.dmx,
89							&demux->hw_frontend);
90	if (result < 0) {
91		dev_err(fei->dev, "connect_frontend (%d)\n", result);
92		goto err_fe_con;
93	}
94
95	return 0;
96
97err_fe_con:
98	demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx,
99						     &demux->mem_frontend);
100err_fe_mem:
101	demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx,
102						     &demux->hw_frontend);
103err_fe_hw:
104	dvb_dmxdev_release(&demux->dmxdev);
105err_dmxdev:
106	dvb_dmx_release(&demux->dvb_demux);
107err_dmx:
108	return result;
109
110}
111
112static void unregister_dvb(struct stdemux *demux)
113{
114
115	demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx,
116						     &demux->mem_frontend);
117
118	demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx,
119						     &demux->hw_frontend);
120
121	dvb_dmxdev_release(&demux->dmxdev);
122
123	dvb_dmx_release(&demux->dvb_demux);
124}
125
126static struct c8sectpfe *c8sectpfe_create(struct c8sectpfei *fei,
127				void *start_feed,
128				void *stop_feed)
129{
130	struct c8sectpfe *c8sectpfe;
131	int result;
132	int i, j;
133
134	short int ids[] = { -1 };
135
136	c8sectpfe = kzalloc(sizeof(struct c8sectpfe), GFP_KERNEL);
137	if (!c8sectpfe)
138		goto err1;
139
140	mutex_init(&c8sectpfe->lock);
141
142	c8sectpfe->device = fei->dev;
143
144	result = dvb_register_adapter(&c8sectpfe->adapter, "STi c8sectpfe",
145					THIS_MODULE, fei->dev, ids);
146	if (result < 0) {
147		dev_err(fei->dev, "dvb_register_adapter failed (errno = %d)\n",
148			result);
149		goto err2;
150	}
151
152	c8sectpfe->adapter.priv = fei;
153
154	for (i = 0; i < fei->tsin_count; i++) {
155
156		c8sectpfe->demux[i].tsin_index = i;
157		c8sectpfe->demux[i].c8sectpfei = fei;
158
159		result = register_dvb(&c8sectpfe->demux[i], &c8sectpfe->adapter,
160				start_feed, stop_feed, fei);
161		if (result < 0) {
162			dev_err(fei->dev,
163				"register_dvb feed=%d failed (errno = %d)\n",
164				result, i);
165
166			/* we take a all or nothing approach */
167			for (j = 0; j < i; j++)
168				unregister_dvb(&c8sectpfe->demux[j]);
169			goto err3;
170		}
171	}
172
173	c8sectpfe->num_feeds = fei->tsin_count;
174
175	return c8sectpfe;
176err3:
177	dvb_unregister_adapter(&c8sectpfe->adapter);
178err2:
179	kfree(c8sectpfe);
180err1:
181	return NULL;
182};
183
184static void c8sectpfe_delete(struct c8sectpfe *c8sectpfe)
185{
186	int i;
187
188	if (!c8sectpfe)
189		return;
190
191	for (i = 0; i < c8sectpfe->num_feeds; i++)
192		unregister_dvb(&c8sectpfe->demux[i]);
193
194	dvb_unregister_adapter(&c8sectpfe->adapter);
195
196	kfree(c8sectpfe);
197};
198
199void c8sectpfe_tuner_unregister_frontend(struct c8sectpfe *c8sectpfe,
200					struct c8sectpfei *fei)
201{
202	int n;
203	struct channel_info *tsin;
204
205	for (n = 0; n < fei->tsin_count; n++) {
206
207		tsin = fei->channel_data[n];
208
209		if (tsin) {
210			if (tsin->frontend) {
211				dvb_unregister_frontend(tsin->frontend);
212				dvb_frontend_detach(tsin->frontend);
213			}
214
215			i2c_put_adapter(tsin->i2c_adapter);
216
217			if (tsin->i2c_client) {
218				module_put(tsin->i2c_client->dev.driver->owner);
219				i2c_unregister_device(tsin->i2c_client);
220			}
221		}
222	}
223
224	c8sectpfe_delete(c8sectpfe);
225};
226
227int c8sectpfe_tuner_register_frontend(struct c8sectpfe **c8sectpfe,
228				struct c8sectpfei *fei,
229				void *start_feed,
230				void *stop_feed)
231{
232	struct channel_info *tsin;
233	struct dvb_frontend *frontend;
234	int n, res;
235
236	*c8sectpfe = c8sectpfe_create(fei, start_feed, stop_feed);
237	if (!*c8sectpfe)
238		return -ENOMEM;
239
240	for (n = 0; n < fei->tsin_count; n++) {
241		tsin = fei->channel_data[n];
242
243		res = c8sectpfe_frontend_attach(&frontend, *c8sectpfe, tsin, n);
244		if (res)
245			goto err;
246
247		res = dvb_register_frontend(&c8sectpfe[0]->adapter, frontend);
248		if (res < 0) {
249			dev_err(fei->dev, "dvb_register_frontend failed (%d)\n",
250				res);
251			goto err;
252		}
253
254		tsin->frontend = frontend;
255	}
256
257	return 0;
258
259err:
260	c8sectpfe_tuner_unregister_frontend(*c8sectpfe, fei);
261	return res;
262}
263