diff options
Diffstat (limited to 'drivers/media/dvb/siano/smsdvb.c')
-rw-r--r-- | drivers/media/dvb/siano/smsdvb.c | 962 |
1 files changed, 962 insertions, 0 deletions
diff --git a/drivers/media/dvb/siano/smsdvb.c b/drivers/media/dvb/siano/smsdvb.c new file mode 100644 index 00000000000..37c594f8278 --- /dev/null +++ b/drivers/media/dvb/siano/smsdvb.c | |||
@@ -0,0 +1,962 @@ | |||
1 | /**************************************************************** | ||
2 | |||
3 | Siano Mobile Silicon, Inc. | ||
4 | MDTV receiver kernel modules. | ||
5 | Copyright (C) 2006-2008, Uri Shkolnik | ||
6 | |||
7 | This program is free software: you can redistribute it and/or modify | ||
8 | it under the terms of the GNU General Public License as published by | ||
9 | the Free Software Foundation, either version 2 of the License, or | ||
10 | (at your option) any later version. | ||
11 | |||
12 | This program is distributed in the hope that it will be useful, | ||
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | GNU General Public License for more details. | ||
16 | |||
17 | You should have received a copy of the GNU General Public License | ||
18 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
19 | |||
20 | ****************************************************************/ | ||
21 | |||
22 | #include <linux/module.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/init.h> | ||
25 | |||
26 | #include "dmxdev.h" | ||
27 | #include "dvbdev.h" | ||
28 | #include "dvb_demux.h" | ||
29 | #include "dvb_frontend.h" | ||
30 | |||
31 | #include "smscoreapi.h" | ||
32 | #include "smsendian.h" | ||
33 | #include "sms-cards.h" | ||
34 | |||
35 | DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); | ||
36 | |||
37 | struct smsdvb_client_t { | ||
38 | struct list_head entry; | ||
39 | |||
40 | struct smscore_device_t *coredev; | ||
41 | struct smscore_client_t *smsclient; | ||
42 | |||
43 | struct dvb_adapter adapter; | ||
44 | struct dvb_demux demux; | ||
45 | struct dmxdev dmxdev; | ||
46 | struct dvb_frontend frontend; | ||
47 | |||
48 | fe_status_t fe_status; | ||
49 | |||
50 | struct completion tune_done; | ||
51 | |||
52 | /* todo: save freq/band instead whole struct */ | ||
53 | struct dvb_frontend_parameters fe_params; | ||
54 | |||
55 | struct SMSHOSTLIB_STATISTICS_DVB_S sms_stat_dvb; | ||
56 | int event_fe_state; | ||
57 | int event_unc_state; | ||
58 | }; | ||
59 | |||
60 | static struct list_head g_smsdvb_clients; | ||
61 | static struct mutex g_smsdvb_clientslock; | ||
62 | |||
63 | static int sms_dbg; | ||
64 | module_param_named(debug, sms_dbg, int, 0644); | ||
65 | MODULE_PARM_DESC(debug, "set debug level (info=1, adv=2 (or-able))"); | ||
66 | |||
67 | /* Events that may come from DVB v3 adapter */ | ||
68 | static void sms_board_dvb3_event(struct smsdvb_client_t *client, | ||
69 | enum SMS_DVB3_EVENTS event) { | ||
70 | |||
71 | struct smscore_device_t *coredev = client->coredev; | ||
72 | switch (event) { | ||
73 | case DVB3_EVENT_INIT: | ||
74 | sms_debug("DVB3_EVENT_INIT"); | ||
75 | sms_board_event(coredev, BOARD_EVENT_BIND); | ||
76 | break; | ||
77 | case DVB3_EVENT_SLEEP: | ||
78 | sms_debug("DVB3_EVENT_SLEEP"); | ||
79 | sms_board_event(coredev, BOARD_EVENT_POWER_SUSPEND); | ||
80 | break; | ||
81 | case DVB3_EVENT_HOTPLUG: | ||
82 | sms_debug("DVB3_EVENT_HOTPLUG"); | ||
83 | sms_board_event(coredev, BOARD_EVENT_POWER_INIT); | ||
84 | break; | ||
85 | case DVB3_EVENT_FE_LOCK: | ||
86 | if (client->event_fe_state != DVB3_EVENT_FE_LOCK) { | ||
87 | client->event_fe_state = DVB3_EVENT_FE_LOCK; | ||
88 | sms_debug("DVB3_EVENT_FE_LOCK"); | ||
89 | sms_board_event(coredev, BOARD_EVENT_FE_LOCK); | ||
90 | } | ||
91 | break; | ||
92 | case DVB3_EVENT_FE_UNLOCK: | ||
93 | if (client->event_fe_state != DVB3_EVENT_FE_UNLOCK) { | ||
94 | client->event_fe_state = DVB3_EVENT_FE_UNLOCK; | ||
95 | sms_debug("DVB3_EVENT_FE_UNLOCK"); | ||
96 | sms_board_event(coredev, BOARD_EVENT_FE_UNLOCK); | ||
97 | } | ||
98 | break; | ||
99 | case DVB3_EVENT_UNC_OK: | ||
100 | if (client->event_unc_state != DVB3_EVENT_UNC_OK) { | ||
101 | client->event_unc_state = DVB3_EVENT_UNC_OK; | ||
102 | sms_debug("DVB3_EVENT_UNC_OK"); | ||
103 | sms_board_event(coredev, BOARD_EVENT_MULTIPLEX_OK); | ||
104 | } | ||
105 | break; | ||
106 | case DVB3_EVENT_UNC_ERR: | ||
107 | if (client->event_unc_state != DVB3_EVENT_UNC_ERR) { | ||
108 | client->event_unc_state = DVB3_EVENT_UNC_ERR; | ||
109 | sms_debug("DVB3_EVENT_UNC_ERR"); | ||
110 | sms_board_event(coredev, BOARD_EVENT_MULTIPLEX_ERRORS); | ||
111 | } | ||
112 | break; | ||
113 | |||
114 | default: | ||
115 | sms_err("Unknown dvb3 api event"); | ||
116 | break; | ||
117 | } | ||
118 | } | ||
119 | |||
120 | |||
121 | static void smsdvb_update_dvb_stats(struct RECEPTION_STATISTICS_S *pReceptionData, | ||
122 | struct SMSHOSTLIB_STATISTICS_ST *p) | ||
123 | { | ||
124 | if (sms_dbg & 2) { | ||
125 | printk(KERN_DEBUG "Reserved = %d", p->Reserved); | ||
126 | printk(KERN_DEBUG "IsRfLocked = %d", p->IsRfLocked); | ||
127 | printk(KERN_DEBUG "IsDemodLocked = %d", p->IsDemodLocked); | ||
128 | printk(KERN_DEBUG "IsExternalLNAOn = %d", p->IsExternalLNAOn); | ||
129 | printk(KERN_DEBUG "SNR = %d", p->SNR); | ||
130 | printk(KERN_DEBUG "BER = %d", p->BER); | ||
131 | printk(KERN_DEBUG "FIB_CRC = %d", p->FIB_CRC); | ||
132 | printk(KERN_DEBUG "TS_PER = %d", p->TS_PER); | ||
133 | printk(KERN_DEBUG "MFER = %d", p->MFER); | ||
134 | printk(KERN_DEBUG "RSSI = %d", p->RSSI); | ||
135 | printk(KERN_DEBUG "InBandPwr = %d", p->InBandPwr); | ||
136 | printk(KERN_DEBUG "CarrierOffset = %d", p->CarrierOffset); | ||
137 | printk(KERN_DEBUG "Frequency = %d", p->Frequency); | ||
138 | printk(KERN_DEBUG "Bandwidth = %d", p->Bandwidth); | ||
139 | printk(KERN_DEBUG "TransmissionMode = %d", p->TransmissionMode); | ||
140 | printk(KERN_DEBUG "ModemState = %d", p->ModemState); | ||
141 | printk(KERN_DEBUG "GuardInterval = %d", p->GuardInterval); | ||
142 | printk(KERN_DEBUG "CodeRate = %d", p->CodeRate); | ||
143 | printk(KERN_DEBUG "LPCodeRate = %d", p->LPCodeRate); | ||
144 | printk(KERN_DEBUG "Hierarchy = %d", p->Hierarchy); | ||
145 | printk(KERN_DEBUG "Constellation = %d", p->Constellation); | ||
146 | printk(KERN_DEBUG "BurstSize = %d", p->BurstSize); | ||
147 | printk(KERN_DEBUG "BurstDuration = %d", p->BurstDuration); | ||
148 | printk(KERN_DEBUG "BurstCycleTime = %d", p->BurstCycleTime); | ||
149 | printk(KERN_DEBUG "CalculatedBurstCycleTime = %d", p->CalculatedBurstCycleTime); | ||
150 | printk(KERN_DEBUG "NumOfRows = %d", p->NumOfRows); | ||
151 | printk(KERN_DEBUG "NumOfPaddCols = %d", p->NumOfPaddCols); | ||
152 | printk(KERN_DEBUG "NumOfPunctCols = %d", p->NumOfPunctCols); | ||
153 | printk(KERN_DEBUG "ErrorTSPackets = %d", p->ErrorTSPackets); | ||
154 | printk(KERN_DEBUG "TotalTSPackets = %d", p->TotalTSPackets); | ||
155 | printk(KERN_DEBUG "NumOfValidMpeTlbs = %d", p->NumOfValidMpeTlbs); | ||
156 | printk(KERN_DEBUG "NumOfInvalidMpeTlbs = %d", p->NumOfInvalidMpeTlbs); | ||
157 | printk(KERN_DEBUG "NumOfCorrectedMpeTlbs = %d", p->NumOfCorrectedMpeTlbs); | ||
158 | printk(KERN_DEBUG "BERErrorCount = %d", p->BERErrorCount); | ||
159 | printk(KERN_DEBUG "BERBitCount = %d", p->BERBitCount); | ||
160 | printk(KERN_DEBUG "SmsToHostTxErrors = %d", p->SmsToHostTxErrors); | ||
161 | printk(KERN_DEBUG "PreBER = %d", p->PreBER); | ||
162 | printk(KERN_DEBUG "CellId = %d", p->CellId); | ||
163 | printk(KERN_DEBUG "DvbhSrvIndHP = %d", p->DvbhSrvIndHP); | ||
164 | printk(KERN_DEBUG "DvbhSrvIndLP = %d", p->DvbhSrvIndLP); | ||
165 | printk(KERN_DEBUG "NumMPEReceived = %d", p->NumMPEReceived); | ||
166 | } | ||
167 | |||
168 | pReceptionData->IsDemodLocked = p->IsDemodLocked; | ||
169 | |||
170 | pReceptionData->SNR = p->SNR; | ||
171 | pReceptionData->BER = p->BER; | ||
172 | pReceptionData->BERErrorCount = p->BERErrorCount; | ||
173 | pReceptionData->InBandPwr = p->InBandPwr; | ||
174 | pReceptionData->ErrorTSPackets = p->ErrorTSPackets; | ||
175 | }; | ||
176 | |||
177 | |||
178 | static void smsdvb_update_isdbt_stats(struct RECEPTION_STATISTICS_S *pReceptionData, | ||
179 | struct SMSHOSTLIB_STATISTICS_ISDBT_ST *p) | ||
180 | { | ||
181 | int i; | ||
182 | |||
183 | if (sms_dbg & 2) { | ||
184 | printk(KERN_DEBUG "IsRfLocked = %d", p->IsRfLocked); | ||
185 | printk(KERN_DEBUG "IsDemodLocked = %d", p->IsDemodLocked); | ||
186 | printk(KERN_DEBUG "IsExternalLNAOn = %d", p->IsExternalLNAOn); | ||
187 | printk(KERN_DEBUG "SNR = %d", p->SNR); | ||
188 | printk(KERN_DEBUG "RSSI = %d", p->RSSI); | ||
189 | printk(KERN_DEBUG "InBandPwr = %d", p->InBandPwr); | ||
190 | printk(KERN_DEBUG "CarrierOffset = %d", p->CarrierOffset); | ||
191 | printk(KERN_DEBUG "Frequency = %d", p->Frequency); | ||
192 | printk(KERN_DEBUG "Bandwidth = %d", p->Bandwidth); | ||
193 | printk(KERN_DEBUG "TransmissionMode = %d", p->TransmissionMode); | ||
194 | printk(KERN_DEBUG "ModemState = %d", p->ModemState); | ||
195 | printk(KERN_DEBUG "GuardInterval = %d", p->GuardInterval); | ||
196 | printk(KERN_DEBUG "SystemType = %d", p->SystemType); | ||
197 | printk(KERN_DEBUG "PartialReception = %d", p->PartialReception); | ||
198 | printk(KERN_DEBUG "NumOfLayers = %d", p->NumOfLayers); | ||
199 | printk(KERN_DEBUG "SmsToHostTxErrors = %d", p->SmsToHostTxErrors); | ||
200 | |||
201 | for (i = 0; i < 3; i++) { | ||
202 | printk(KERN_DEBUG "%d: CodeRate = %d", i, p->LayerInfo[i].CodeRate); | ||
203 | printk(KERN_DEBUG "%d: Constellation = %d", i, p->LayerInfo[i].Constellation); | ||
204 | printk(KERN_DEBUG "%d: BER = %d", i, p->LayerInfo[i].BER); | ||
205 | printk(KERN_DEBUG "%d: BERErrorCount = %d", i, p->LayerInfo[i].BERErrorCount); | ||
206 | printk(KERN_DEBUG "%d: BERBitCount = %d", i, p->LayerInfo[i].BERBitCount); | ||
207 | printk(KERN_DEBUG "%d: PreBER = %d", i, p->LayerInfo[i].PreBER); | ||
208 | printk(KERN_DEBUG "%d: TS_PER = %d", i, p->LayerInfo[i].TS_PER); | ||
209 | printk(KERN_DEBUG "%d: ErrorTSPackets = %d", i, p->LayerInfo[i].ErrorTSPackets); | ||
210 | printk(KERN_DEBUG "%d: TotalTSPackets = %d", i, p->LayerInfo[i].TotalTSPackets); | ||
211 | printk(KERN_DEBUG "%d: TILdepthI = %d", i, p->LayerInfo[i].TILdepthI); | ||
212 | printk(KERN_DEBUG "%d: NumberOfSegments = %d", i, p->LayerInfo[i].NumberOfSegments); | ||
213 | printk(KERN_DEBUG "%d: TMCCErrors = %d", i, p->LayerInfo[i].TMCCErrors); | ||
214 | } | ||
215 | } | ||
216 | |||
217 | pReceptionData->IsDemodLocked = p->IsDemodLocked; | ||
218 | |||
219 | pReceptionData->SNR = p->SNR; | ||
220 | pReceptionData->InBandPwr = p->InBandPwr; | ||
221 | |||
222 | pReceptionData->ErrorTSPackets = 0; | ||
223 | pReceptionData->BER = 0; | ||
224 | pReceptionData->BERErrorCount = 0; | ||
225 | for (i = 0; i < 3; i++) { | ||
226 | pReceptionData->BER += p->LayerInfo[i].BER; | ||
227 | pReceptionData->BERErrorCount += p->LayerInfo[i].BERErrorCount; | ||
228 | pReceptionData->ErrorTSPackets += p->LayerInfo[i].ErrorTSPackets; | ||
229 | } | ||
230 | } | ||
231 | |||
232 | static int smsdvb_onresponse(void *context, struct smscore_buffer_t *cb) | ||
233 | { | ||
234 | struct smsdvb_client_t *client = (struct smsdvb_client_t *) context; | ||
235 | struct SmsMsgHdr_ST *phdr = (struct SmsMsgHdr_ST *) (((u8 *) cb->p) | ||
236 | + cb->offset); | ||
237 | u32 *pMsgData = (u32 *) phdr + 1; | ||
238 | /*u32 MsgDataLen = phdr->msgLength - sizeof(struct SmsMsgHdr_ST);*/ | ||
239 | bool is_status_update = false; | ||
240 | |||
241 | smsendian_handle_rx_message((struct SmsMsgData_ST *) phdr); | ||
242 | |||
243 | switch (phdr->msgType) { | ||
244 | case MSG_SMS_DVBT_BDA_DATA: | ||
245 | dvb_dmx_swfilter(&client->demux, (u8 *)(phdr + 1), | ||
246 | cb->size - sizeof(struct SmsMsgHdr_ST)); | ||
247 | break; | ||
248 | |||
249 | case MSG_SMS_RF_TUNE_RES: | ||
250 | case MSG_SMS_ISDBT_TUNE_RES: | ||
251 | complete(&client->tune_done); | ||
252 | break; | ||
253 | |||
254 | case MSG_SMS_SIGNAL_DETECTED_IND: | ||
255 | sms_info("MSG_SMS_SIGNAL_DETECTED_IND"); | ||
256 | client->sms_stat_dvb.TransmissionData.IsDemodLocked = true; | ||
257 | is_status_update = true; | ||
258 | break; | ||
259 | |||
260 | case MSG_SMS_NO_SIGNAL_IND: | ||
261 | sms_info("MSG_SMS_NO_SIGNAL_IND"); | ||
262 | client->sms_stat_dvb.TransmissionData.IsDemodLocked = false; | ||
263 | is_status_update = true; | ||
264 | break; | ||
265 | |||
266 | case MSG_SMS_TRANSMISSION_IND: { | ||
267 | sms_info("MSG_SMS_TRANSMISSION_IND"); | ||
268 | |||
269 | pMsgData++; | ||
270 | memcpy(&client->sms_stat_dvb.TransmissionData, pMsgData, | ||
271 | sizeof(struct TRANSMISSION_STATISTICS_S)); | ||
272 | |||
273 | /* Mo need to correct guard interval | ||
274 | * (as opposed to old statistics message). | ||
275 | */ | ||
276 | CORRECT_STAT_BANDWIDTH(client->sms_stat_dvb.TransmissionData); | ||
277 | CORRECT_STAT_TRANSMISSON_MODE( | ||
278 | client->sms_stat_dvb.TransmissionData); | ||
279 | is_status_update = true; | ||
280 | break; | ||
281 | } | ||
282 | case MSG_SMS_HO_PER_SLICES_IND: { | ||
283 | struct RECEPTION_STATISTICS_S *pReceptionData = | ||
284 | &client->sms_stat_dvb.ReceptionData; | ||
285 | struct SRVM_SIGNAL_STATUS_S SignalStatusData; | ||
286 | |||
287 | /*sms_info("MSG_SMS_HO_PER_SLICES_IND");*/ | ||
288 | pMsgData++; | ||
289 | SignalStatusData.result = pMsgData[0]; | ||
290 | SignalStatusData.snr = pMsgData[1]; | ||
291 | SignalStatusData.inBandPower = (s32) pMsgData[2]; | ||
292 | SignalStatusData.tsPackets = pMsgData[3]; | ||
293 | SignalStatusData.etsPackets = pMsgData[4]; | ||
294 | SignalStatusData.constellation = pMsgData[5]; | ||
295 | SignalStatusData.hpCode = pMsgData[6]; | ||
296 | SignalStatusData.tpsSrvIndLP = pMsgData[7] & 0x03; | ||
297 | SignalStatusData.tpsSrvIndHP = pMsgData[8] & 0x03; | ||
298 | SignalStatusData.cellId = pMsgData[9] & 0xFFFF; | ||
299 | SignalStatusData.reason = pMsgData[10]; | ||
300 | SignalStatusData.requestId = pMsgData[11]; | ||
301 | pReceptionData->IsRfLocked = pMsgData[16]; | ||
302 | pReceptionData->IsDemodLocked = pMsgData[17]; | ||
303 | pReceptionData->ModemState = pMsgData[12]; | ||
304 | pReceptionData->SNR = pMsgData[1]; | ||
305 | pReceptionData->BER = pMsgData[13]; | ||
306 | pReceptionData->RSSI = pMsgData[14]; | ||
307 | CORRECT_STAT_RSSI(client->sms_stat_dvb.ReceptionData); | ||
308 | |||
309 | pReceptionData->InBandPwr = (s32) pMsgData[2]; | ||
310 | pReceptionData->CarrierOffset = (s32) pMsgData[15]; | ||
311 | pReceptionData->TotalTSPackets = pMsgData[3]; | ||
312 | pReceptionData->ErrorTSPackets = pMsgData[4]; | ||
313 | |||
314 | /* TS PER */ | ||
315 | if ((SignalStatusData.tsPackets + SignalStatusData.etsPackets) | ||
316 | > 0) { | ||
317 | pReceptionData->TS_PER = (SignalStatusData.etsPackets | ||
318 | * 100) / (SignalStatusData.tsPackets | ||
319 | + SignalStatusData.etsPackets); | ||
320 | } else { | ||
321 | pReceptionData->TS_PER = 0; | ||
322 | } | ||
323 | |||
324 | pReceptionData->BERBitCount = pMsgData[18]; | ||
325 | pReceptionData->BERErrorCount = pMsgData[19]; | ||
326 | |||
327 | pReceptionData->MRC_SNR = pMsgData[20]; | ||
328 | pReceptionData->MRC_InBandPwr = pMsgData[21]; | ||
329 | pReceptionData->MRC_RSSI = pMsgData[22]; | ||
330 | |||
331 | is_status_update = true; | ||
332 | break; | ||
333 | } | ||
334 | case MSG_SMS_GET_STATISTICS_RES: { | ||
335 | union { | ||
336 | struct SMSHOSTLIB_STATISTICS_ISDBT_ST isdbt; | ||
337 | struct SmsMsgStatisticsInfo_ST dvb; | ||
338 | } *p = (void *) (phdr + 1); | ||
339 | struct RECEPTION_STATISTICS_S *pReceptionData = | ||
340 | &client->sms_stat_dvb.ReceptionData; | ||
341 | |||
342 | sms_info("MSG_SMS_GET_STATISTICS_RES"); | ||
343 | |||
344 | is_status_update = true; | ||
345 | |||
346 | switch (smscore_get_device_mode(client->coredev)) { | ||
347 | case DEVICE_MODE_ISDBT: | ||
348 | case DEVICE_MODE_ISDBT_BDA: | ||
349 | smsdvb_update_isdbt_stats(pReceptionData, &p->isdbt); | ||
350 | break; | ||
351 | default: | ||
352 | smsdvb_update_dvb_stats(pReceptionData, &p->dvb.Stat); | ||
353 | } | ||
354 | if (!pReceptionData->IsDemodLocked) { | ||
355 | pReceptionData->SNR = 0; | ||
356 | pReceptionData->BER = 0; | ||
357 | pReceptionData->BERErrorCount = 0; | ||
358 | pReceptionData->InBandPwr = 0; | ||
359 | pReceptionData->ErrorTSPackets = 0; | ||
360 | } | ||
361 | |||
362 | complete(&client->tune_done); | ||
363 | break; | ||
364 | } | ||
365 | default: | ||
366 | sms_info("Unhandled message %d", phdr->msgType); | ||
367 | |||
368 | } | ||
369 | smscore_putbuffer(client->coredev, cb); | ||
370 | |||
371 | if (is_status_update) { | ||
372 | if (client->sms_stat_dvb.ReceptionData.IsDemodLocked) { | ||
373 | client->fe_status = FE_HAS_SIGNAL | FE_HAS_CARRIER | ||
374 | | FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; | ||
375 | sms_board_dvb3_event(client, DVB3_EVENT_FE_LOCK); | ||
376 | if (client->sms_stat_dvb.ReceptionData.ErrorTSPackets | ||
377 | == 0) | ||
378 | sms_board_dvb3_event(client, DVB3_EVENT_UNC_OK); | ||
379 | else | ||
380 | sms_board_dvb3_event(client, | ||
381 | DVB3_EVENT_UNC_ERR); | ||
382 | |||
383 | } else { | ||
384 | if (client->sms_stat_dvb.ReceptionData.IsRfLocked) | ||
385 | client->fe_status = FE_HAS_SIGNAL | FE_HAS_CARRIER; | ||
386 | else | ||
387 | client->fe_status = 0; | ||
388 | sms_board_dvb3_event(client, DVB3_EVENT_FE_UNLOCK); | ||
389 | } | ||
390 | } | ||
391 | |||
392 | return 0; | ||
393 | } | ||
394 | |||
395 | static void smsdvb_unregister_client(struct smsdvb_client_t *client) | ||
396 | { | ||
397 | /* must be called under clientslock */ | ||
398 | |||
399 | list_del(&client->entry); | ||
400 | |||
401 | smscore_unregister_client(client->smsclient); | ||
402 | dvb_unregister_frontend(&client->frontend); | ||
403 | dvb_dmxdev_release(&client->dmxdev); | ||
404 | dvb_dmx_release(&client->demux); | ||
405 | dvb_unregister_adapter(&client->adapter); | ||
406 | kfree(client); | ||
407 | } | ||
408 | |||
409 | static void smsdvb_onremove(void *context) | ||
410 | { | ||
411 | kmutex_lock(&g_smsdvb_clientslock); | ||
412 | |||
413 | smsdvb_unregister_client((struct smsdvb_client_t *) context); | ||
414 | |||
415 | kmutex_unlock(&g_smsdvb_clientslock); | ||
416 | } | ||
417 | |||
418 | static int smsdvb_start_feed(struct dvb_demux_feed *feed) | ||
419 | { | ||
420 | struct smsdvb_client_t *client = | ||
421 | container_of(feed->demux, struct smsdvb_client_t, demux); | ||
422 | struct SmsMsgData_ST PidMsg; | ||
423 | |||
424 | sms_debug("add pid %d(%x)", | ||
425 | feed->pid, feed->pid); | ||
426 | |||
427 | PidMsg.xMsgHeader.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; | ||
428 | PidMsg.xMsgHeader.msgDstId = HIF_TASK; | ||
429 | PidMsg.xMsgHeader.msgFlags = 0; | ||
430 | PidMsg.xMsgHeader.msgType = MSG_SMS_ADD_PID_FILTER_REQ; | ||
431 | PidMsg.xMsgHeader.msgLength = sizeof(PidMsg); | ||
432 | PidMsg.msgData[0] = feed->pid; | ||
433 | |||
434 | smsendian_handle_tx_message((struct SmsMsgHdr_ST *)&PidMsg); | ||
435 | return smsclient_sendrequest(client->smsclient, | ||
436 | &PidMsg, sizeof(PidMsg)); | ||
437 | } | ||
438 | |||
439 | static int smsdvb_stop_feed(struct dvb_demux_feed *feed) | ||
440 | { | ||
441 | struct smsdvb_client_t *client = | ||
442 | container_of(feed->demux, struct smsdvb_client_t, demux); | ||
443 | struct SmsMsgData_ST PidMsg; | ||
444 | |||
445 | sms_debug("remove pid %d(%x)", | ||
446 | feed->pid, feed->pid); | ||
447 | |||
448 | PidMsg.xMsgHeader.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; | ||
449 | PidMsg.xMsgHeader.msgDstId = HIF_TASK; | ||
450 | PidMsg.xMsgHeader.msgFlags = 0; | ||
451 | PidMsg.xMsgHeader.msgType = MSG_SMS_REMOVE_PID_FILTER_REQ; | ||
452 | PidMsg.xMsgHeader.msgLength = sizeof(PidMsg); | ||
453 | PidMsg.msgData[0] = feed->pid; | ||
454 | |||
455 | smsendian_handle_tx_message((struct SmsMsgHdr_ST *)&PidMsg); | ||
456 | return smsclient_sendrequest(client->smsclient, | ||
457 | &PidMsg, sizeof(PidMsg)); | ||
458 | } | ||
459 | |||
460 | static int smsdvb_sendrequest_and_wait(struct smsdvb_client_t *client, | ||
461 | void *buffer, size_t size, | ||
462 | struct completion *completion) | ||
463 | { | ||
464 | int rc; | ||
465 | |||
466 | smsendian_handle_tx_message((struct SmsMsgHdr_ST *)buffer); | ||
467 | rc = smsclient_sendrequest(client->smsclient, buffer, size); | ||
468 | if (rc < 0) | ||
469 | return rc; | ||
470 | |||
471 | return wait_for_completion_timeout(completion, | ||
472 | msecs_to_jiffies(2000)) ? | ||
473 | 0 : -ETIME; | ||
474 | } | ||
475 | |||
476 | static int smsdvb_send_statistics_request(struct smsdvb_client_t *client) | ||
477 | { | ||
478 | int rc; | ||
479 | struct SmsMsgHdr_ST Msg = { MSG_SMS_GET_STATISTICS_REQ, | ||
480 | DVBT_BDA_CONTROL_MSG_ID, | ||
481 | HIF_TASK, | ||
482 | sizeof(struct SmsMsgHdr_ST), 0 }; | ||
483 | |||
484 | rc = smsdvb_sendrequest_and_wait(client, &Msg, sizeof(Msg), | ||
485 | &client->tune_done); | ||
486 | |||
487 | return rc; | ||
488 | } | ||
489 | |||
490 | static inline int led_feedback(struct smsdvb_client_t *client) | ||
491 | { | ||
492 | if (client->fe_status & FE_HAS_LOCK) | ||
493 | return sms_board_led_feedback(client->coredev, | ||
494 | (client->sms_stat_dvb.ReceptionData.BER | ||
495 | == 0) ? SMS_LED_HI : SMS_LED_LO); | ||
496 | else | ||
497 | return sms_board_led_feedback(client->coredev, SMS_LED_OFF); | ||
498 | } | ||
499 | |||
500 | static int smsdvb_read_status(struct dvb_frontend *fe, fe_status_t *stat) | ||
501 | { | ||
502 | int rc; | ||
503 | struct smsdvb_client_t *client; | ||
504 | client = container_of(fe, struct smsdvb_client_t, frontend); | ||
505 | |||
506 | rc = smsdvb_send_statistics_request(client); | ||
507 | |||
508 | *stat = client->fe_status; | ||
509 | |||
510 | led_feedback(client); | ||
511 | |||
512 | return rc; | ||
513 | } | ||
514 | |||
515 | static int smsdvb_read_ber(struct dvb_frontend *fe, u32 *ber) | ||
516 | { | ||
517 | int rc; | ||
518 | struct smsdvb_client_t *client; | ||
519 | client = container_of(fe, struct smsdvb_client_t, frontend); | ||
520 | |||
521 | rc = smsdvb_send_statistics_request(client); | ||
522 | |||
523 | *ber = client->sms_stat_dvb.ReceptionData.BER; | ||
524 | |||
525 | led_feedback(client); | ||
526 | |||
527 | return rc; | ||
528 | } | ||
529 | |||
530 | static int smsdvb_read_signal_strength(struct dvb_frontend *fe, u16 *strength) | ||
531 | { | ||
532 | int rc; | ||
533 | |||
534 | struct smsdvb_client_t *client; | ||
535 | client = container_of(fe, struct smsdvb_client_t, frontend); | ||
536 | |||
537 | rc = smsdvb_send_statistics_request(client); | ||
538 | |||
539 | if (client->sms_stat_dvb.ReceptionData.InBandPwr < -95) | ||
540 | *strength = 0; | ||
541 | else if (client->sms_stat_dvb.ReceptionData.InBandPwr > -29) | ||
542 | *strength = 100; | ||
543 | else | ||
544 | *strength = | ||
545 | (client->sms_stat_dvb.ReceptionData.InBandPwr | ||
546 | + 95) * 3 / 2; | ||
547 | |||
548 | led_feedback(client); | ||
549 | |||
550 | return rc; | ||
551 | } | ||
552 | |||
553 | static int smsdvb_read_snr(struct dvb_frontend *fe, u16 *snr) | ||
554 | { | ||
555 | int rc; | ||
556 | struct smsdvb_client_t *client; | ||
557 | client = container_of(fe, struct smsdvb_client_t, frontend); | ||
558 | |||
559 | rc = smsdvb_send_statistics_request(client); | ||
560 | |||
561 | *snr = client->sms_stat_dvb.ReceptionData.SNR; | ||
562 | |||
563 | led_feedback(client); | ||
564 | |||
565 | return rc; | ||
566 | } | ||
567 | |||
568 | static int smsdvb_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) | ||
569 | { | ||
570 | int rc; | ||
571 | struct smsdvb_client_t *client; | ||
572 | client = container_of(fe, struct smsdvb_client_t, frontend); | ||
573 | |||
574 | rc = smsdvb_send_statistics_request(client); | ||
575 | |||
576 | *ucblocks = client->sms_stat_dvb.ReceptionData.ErrorTSPackets; | ||
577 | |||
578 | led_feedback(client); | ||
579 | |||
580 | return rc; | ||
581 | } | ||
582 | |||
583 | static int smsdvb_get_tune_settings(struct dvb_frontend *fe, | ||
584 | struct dvb_frontend_tune_settings *tune) | ||
585 | { | ||
586 | sms_debug(""); | ||
587 | |||
588 | tune->min_delay_ms = 400; | ||
589 | tune->step_size = 250000; | ||
590 | tune->max_drift = 0; | ||
591 | return 0; | ||
592 | } | ||
593 | |||
594 | static int smsdvb_dvbt_set_frontend(struct dvb_frontend *fe, | ||
595 | struct dvb_frontend_parameters *p) | ||
596 | { | ||
597 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; | ||
598 | struct smsdvb_client_t *client = | ||
599 | container_of(fe, struct smsdvb_client_t, frontend); | ||
600 | |||
601 | struct { | ||
602 | struct SmsMsgHdr_ST Msg; | ||
603 | u32 Data[3]; | ||
604 | } Msg; | ||
605 | |||
606 | int ret; | ||
607 | |||
608 | client->fe_status = FE_HAS_SIGNAL; | ||
609 | client->event_fe_state = -1; | ||
610 | client->event_unc_state = -1; | ||
611 | fe->dtv_property_cache.delivery_system = SYS_DVBT; | ||
612 | |||
613 | Msg.Msg.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; | ||
614 | Msg.Msg.msgDstId = HIF_TASK; | ||
615 | Msg.Msg.msgFlags = 0; | ||
616 | Msg.Msg.msgType = MSG_SMS_RF_TUNE_REQ; | ||
617 | Msg.Msg.msgLength = sizeof(Msg); | ||
618 | Msg.Data[0] = c->frequency; | ||
619 | Msg.Data[2] = 12000000; | ||
620 | |||
621 | sms_info("%s: freq %d band %d", __func__, c->frequency, | ||
622 | c->bandwidth_hz); | ||
623 | |||
624 | switch (c->bandwidth_hz / 1000000) { | ||
625 | case 8: | ||
626 | Msg.Data[1] = BW_8_MHZ; | ||
627 | break; | ||
628 | case 7: | ||
629 | Msg.Data[1] = BW_7_MHZ; | ||
630 | break; | ||
631 | case 6: | ||
632 | Msg.Data[1] = BW_6_MHZ; | ||
633 | break; | ||
634 | case 0: | ||
635 | return -EOPNOTSUPP; | ||
636 | default: | ||
637 | return -EINVAL; | ||
638 | } | ||
639 | /* Disable LNA, if any. An error is returned if no LNA is present */ | ||
640 | ret = sms_board_lna_control(client->coredev, 0); | ||
641 | if (ret == 0) { | ||
642 | fe_status_t status; | ||
643 | |||
644 | /* tune with LNA off at first */ | ||
645 | ret = smsdvb_sendrequest_and_wait(client, &Msg, sizeof(Msg), | ||
646 | &client->tune_done); | ||
647 | |||
648 | smsdvb_read_status(fe, &status); | ||
649 | |||
650 | if (status & FE_HAS_LOCK) | ||
651 | return ret; | ||
652 | |||
653 | /* previous tune didn't lock - enable LNA and tune again */ | ||
654 | sms_board_lna_control(client->coredev, 1); | ||
655 | } | ||
656 | |||
657 | return smsdvb_sendrequest_and_wait(client, &Msg, sizeof(Msg), | ||
658 | &client->tune_done); | ||
659 | } | ||
660 | |||
661 | static int smsdvb_isdbt_set_frontend(struct dvb_frontend *fe, | ||
662 | struct dvb_frontend_parameters *p) | ||
663 | { | ||
664 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; | ||
665 | struct smsdvb_client_t *client = | ||
666 | container_of(fe, struct smsdvb_client_t, frontend); | ||
667 | |||
668 | struct { | ||
669 | struct SmsMsgHdr_ST Msg; | ||
670 | u32 Data[4]; | ||
671 | } Msg; | ||
672 | |||
673 | fe->dtv_property_cache.delivery_system = SYS_ISDBT; | ||
674 | |||
675 | Msg.Msg.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; | ||
676 | Msg.Msg.msgDstId = HIF_TASK; | ||
677 | Msg.Msg.msgFlags = 0; | ||
678 | Msg.Msg.msgType = MSG_SMS_ISDBT_TUNE_REQ; | ||
679 | Msg.Msg.msgLength = sizeof(Msg); | ||
680 | |||
681 | if (c->isdbt_sb_segment_idx == -1) | ||
682 | c->isdbt_sb_segment_idx = 0; | ||
683 | |||
684 | switch (c->isdbt_sb_segment_count) { | ||
685 | case 3: | ||
686 | Msg.Data[1] = BW_ISDBT_3SEG; | ||
687 | break; | ||
688 | case 1: | ||
689 | Msg.Data[1] = BW_ISDBT_1SEG; | ||
690 | break; | ||
691 | case 0: /* AUTO */ | ||
692 | switch (c->bandwidth_hz / 1000000) { | ||
693 | case 8: | ||
694 | case 7: | ||
695 | c->isdbt_sb_segment_count = 3; | ||
696 | Msg.Data[1] = BW_ISDBT_3SEG; | ||
697 | break; | ||
698 | case 6: | ||
699 | c->isdbt_sb_segment_count = 1; | ||
700 | Msg.Data[1] = BW_ISDBT_1SEG; | ||
701 | break; | ||
702 | default: /* Assumes 6 MHZ bw */ | ||
703 | c->isdbt_sb_segment_count = 1; | ||
704 | c->bandwidth_hz = 6000; | ||
705 | Msg.Data[1] = BW_ISDBT_1SEG; | ||
706 | break; | ||
707 | } | ||
708 | break; | ||
709 | default: | ||
710 | sms_info("Segment count %d not supported", c->isdbt_sb_segment_count); | ||
711 | return -EINVAL; | ||
712 | } | ||
713 | |||
714 | Msg.Data[0] = c->frequency; | ||
715 | Msg.Data[2] = 12000000; | ||
716 | Msg.Data[3] = c->isdbt_sb_segment_idx; | ||
717 | |||
718 | sms_info("%s: freq %d segwidth %d segindex %d\n", __func__, | ||
719 | c->frequency, c->isdbt_sb_segment_count, | ||
720 | c->isdbt_sb_segment_idx); | ||
721 | |||
722 | return smsdvb_sendrequest_and_wait(client, &Msg, sizeof(Msg), | ||
723 | &client->tune_done); | ||
724 | } | ||
725 | |||
726 | static int smsdvb_set_frontend(struct dvb_frontend *fe, | ||
727 | struct dvb_frontend_parameters *fep) | ||
728 | { | ||
729 | struct smsdvb_client_t *client = | ||
730 | container_of(fe, struct smsdvb_client_t, frontend); | ||
731 | struct smscore_device_t *coredev = client->coredev; | ||
732 | |||
733 | switch (smscore_get_device_mode(coredev)) { | ||
734 | case DEVICE_MODE_DVBT: | ||
735 | case DEVICE_MODE_DVBT_BDA: | ||
736 | return smsdvb_dvbt_set_frontend(fe, fep); | ||
737 | case DEVICE_MODE_ISDBT: | ||
738 | case DEVICE_MODE_ISDBT_BDA: | ||
739 | return smsdvb_isdbt_set_frontend(fe, fep); | ||
740 | default: | ||
741 | return -EINVAL; | ||
742 | } | ||
743 | } | ||
744 | |||
745 | static int smsdvb_get_frontend(struct dvb_frontend *fe, | ||
746 | struct dvb_frontend_parameters *fep) | ||
747 | { | ||
748 | struct smsdvb_client_t *client = | ||
749 | container_of(fe, struct smsdvb_client_t, frontend); | ||
750 | |||
751 | sms_debug(""); | ||
752 | |||
753 | /* todo: */ | ||
754 | memcpy(fep, &client->fe_params, | ||
755 | sizeof(struct dvb_frontend_parameters)); | ||
756 | |||
757 | return 0; | ||
758 | } | ||
759 | |||
760 | static int smsdvb_init(struct dvb_frontend *fe) | ||
761 | { | ||
762 | struct smsdvb_client_t *client = | ||
763 | container_of(fe, struct smsdvb_client_t, frontend); | ||
764 | |||
765 | sms_board_power(client->coredev, 1); | ||
766 | |||
767 | sms_board_dvb3_event(client, DVB3_EVENT_INIT); | ||
768 | return 0; | ||
769 | } | ||
770 | |||
771 | static int smsdvb_sleep(struct dvb_frontend *fe) | ||
772 | { | ||
773 | struct smsdvb_client_t *client = | ||
774 | container_of(fe, struct smsdvb_client_t, frontend); | ||
775 | |||
776 | sms_board_led_feedback(client->coredev, SMS_LED_OFF); | ||
777 | sms_board_power(client->coredev, 0); | ||
778 | |||
779 | sms_board_dvb3_event(client, DVB3_EVENT_SLEEP); | ||
780 | |||
781 | return 0; | ||
782 | } | ||
783 | |||
784 | static void smsdvb_release(struct dvb_frontend *fe) | ||
785 | { | ||
786 | /* do nothing */ | ||
787 | } | ||
788 | |||
789 | static struct dvb_frontend_ops smsdvb_fe_ops = { | ||
790 | .info = { | ||
791 | .name = "Siano Mobile Digital MDTV Receiver", | ||
792 | .type = FE_OFDM, | ||
793 | .frequency_min = 44250000, | ||
794 | .frequency_max = 867250000, | ||
795 | .frequency_stepsize = 250000, | ||
796 | .caps = FE_CAN_INVERSION_AUTO | | ||
797 | FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | | ||
798 | FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | | ||
799 | FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | | ||
800 | FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO | | ||
801 | FE_CAN_GUARD_INTERVAL_AUTO | | ||
802 | FE_CAN_RECOVER | | ||
803 | FE_CAN_HIERARCHY_AUTO, | ||
804 | }, | ||
805 | |||
806 | .release = smsdvb_release, | ||
807 | |||
808 | .set_frontend = smsdvb_set_frontend, | ||
809 | .get_frontend = smsdvb_get_frontend, | ||
810 | .get_tune_settings = smsdvb_get_tune_settings, | ||
811 | |||
812 | .read_status = smsdvb_read_status, | ||
813 | .read_ber = smsdvb_read_ber, | ||
814 | .read_signal_strength = smsdvb_read_signal_strength, | ||
815 | .read_snr = smsdvb_read_snr, | ||
816 | .read_ucblocks = smsdvb_read_ucblocks, | ||
817 | |||
818 | .init = smsdvb_init, | ||
819 | .sleep = smsdvb_sleep, | ||
820 | }; | ||
821 | |||
822 | static int smsdvb_hotplug(struct smscore_device_t *coredev, | ||
823 | struct device *device, int arrival) | ||
824 | { | ||
825 | struct smsclient_params_t params; | ||
826 | struct smsdvb_client_t *client; | ||
827 | int rc; | ||
828 | |||
829 | /* device removal handled by onremove callback */ | ||
830 | if (!arrival) | ||
831 | return 0; | ||
832 | client = kzalloc(sizeof(struct smsdvb_client_t), GFP_KERNEL); | ||
833 | if (!client) { | ||
834 | sms_err("kmalloc() failed"); | ||
835 | return -ENOMEM; | ||
836 | } | ||
837 | |||
838 | /* register dvb adapter */ | ||
839 | rc = dvb_register_adapter(&client->adapter, | ||
840 | sms_get_board( | ||
841 | smscore_get_board_id(coredev))->name, | ||
842 | THIS_MODULE, device, adapter_nr); | ||
843 | if (rc < 0) { | ||
844 | sms_err("dvb_register_adapter() failed %d", rc); | ||
845 | goto adapter_error; | ||
846 | } | ||
847 | |||
848 | /* init dvb demux */ | ||
849 | client->demux.dmx.capabilities = DMX_TS_FILTERING; | ||
850 | client->demux.filternum = 32; /* todo: nova ??? */ | ||
851 | client->demux.feednum = 32; | ||
852 | client->demux.start_feed = smsdvb_start_feed; | ||
853 | client->demux.stop_feed = smsdvb_stop_feed; | ||
854 | |||
855 | rc = dvb_dmx_init(&client->demux); | ||
856 | if (rc < 0) { | ||
857 | sms_err("dvb_dmx_init failed %d", rc); | ||
858 | goto dvbdmx_error; | ||
859 | } | ||
860 | |||
861 | /* init dmxdev */ | ||
862 | client->dmxdev.filternum = 32; | ||
863 | client->dmxdev.demux = &client->demux.dmx; | ||
864 | client->dmxdev.capabilities = 0; | ||
865 | |||
866 | rc = dvb_dmxdev_init(&client->dmxdev, &client->adapter); | ||
867 | if (rc < 0) { | ||
868 | sms_err("dvb_dmxdev_init failed %d", rc); | ||
869 | goto dmxdev_error; | ||
870 | } | ||
871 | |||
872 | /* init and register frontend */ | ||
873 | memcpy(&client->frontend.ops, &smsdvb_fe_ops, | ||
874 | sizeof(struct dvb_frontend_ops)); | ||
875 | |||
876 | rc = dvb_register_frontend(&client->adapter, &client->frontend); | ||
877 | if (rc < 0) { | ||
878 | sms_err("frontend registration failed %d", rc); | ||
879 | goto frontend_error; | ||
880 | } | ||
881 | |||
882 | params.initial_id = 1; | ||
883 | params.data_type = MSG_SMS_DVBT_BDA_DATA; | ||
884 | params.onresponse_handler = smsdvb_onresponse; | ||
885 | params.onremove_handler = smsdvb_onremove; | ||
886 | params.context = client; | ||
887 | |||
888 | rc = smscore_register_client(coredev, ¶ms, &client->smsclient); | ||
889 | if (rc < 0) { | ||
890 | sms_err("smscore_register_client() failed %d", rc); | ||
891 | goto client_error; | ||
892 | } | ||
893 | |||
894 | client->coredev = coredev; | ||
895 | |||
896 | init_completion(&client->tune_done); | ||
897 | |||
898 | kmutex_lock(&g_smsdvb_clientslock); | ||
899 | |||
900 | list_add(&client->entry, &g_smsdvb_clients); | ||
901 | |||
902 | kmutex_unlock(&g_smsdvb_clientslock); | ||
903 | |||
904 | client->event_fe_state = -1; | ||
905 | client->event_unc_state = -1; | ||
906 | sms_board_dvb3_event(client, DVB3_EVENT_HOTPLUG); | ||
907 | |||
908 | sms_info("success"); | ||
909 | sms_board_setup(coredev); | ||
910 | |||
911 | return 0; | ||
912 | |||
913 | client_error: | ||
914 | dvb_unregister_frontend(&client->frontend); | ||
915 | |||
916 | frontend_error: | ||
917 | dvb_dmxdev_release(&client->dmxdev); | ||
918 | |||
919 | dmxdev_error: | ||
920 | dvb_dmx_release(&client->demux); | ||
921 | |||
922 | dvbdmx_error: | ||
923 | dvb_unregister_adapter(&client->adapter); | ||
924 | |||
925 | adapter_error: | ||
926 | kfree(client); | ||
927 | return rc; | ||
928 | } | ||
929 | |||
930 | static int __init smsdvb_module_init(void) | ||
931 | { | ||
932 | int rc; | ||
933 | |||
934 | INIT_LIST_HEAD(&g_smsdvb_clients); | ||
935 | kmutex_init(&g_smsdvb_clientslock); | ||
936 | |||
937 | rc = smscore_register_hotplug(smsdvb_hotplug); | ||
938 | |||
939 | sms_debug(""); | ||
940 | |||
941 | return rc; | ||
942 | } | ||
943 | |||
944 | static void __exit smsdvb_module_exit(void) | ||
945 | { | ||
946 | smscore_unregister_hotplug(smsdvb_hotplug); | ||
947 | |||
948 | kmutex_lock(&g_smsdvb_clientslock); | ||
949 | |||
950 | while (!list_empty(&g_smsdvb_clients)) | ||
951 | smsdvb_unregister_client( | ||
952 | (struct smsdvb_client_t *) g_smsdvb_clients.next); | ||
953 | |||
954 | kmutex_unlock(&g_smsdvb_clientslock); | ||
955 | } | ||
956 | |||
957 | module_init(smsdvb_module_init); | ||
958 | module_exit(smsdvb_module_exit); | ||
959 | |||
960 | MODULE_DESCRIPTION("SMS DVB subsystem adaptation module"); | ||
961 | MODULE_AUTHOR("Siano Mobile Silicon, Inc. (uris@siano-ms.com)"); | ||
962 | MODULE_LICENSE("GPL"); | ||