diff options
author | Antti Palosaari <crope@iki.fi> | 2014-02-02 21:34:23 -0500 |
---|---|---|
committer | Mauro Carvalho Chehab <m.chehab@samsung.com> | 2014-03-13 09:26:50 -0400 |
commit | 93203dd6c7c436e62523b2ced7faf3aed77218ed (patch) | |
tree | 04159c27c8e80f6c99d86c50db11422835013302 | |
parent | 3d0c8fa3c5a0f9ffc4c3e8b4625ddeb875aee50b (diff) |
[media] msi001: Mirics MSi001 silicon tuner driver
That RF tuner driver is bound via SPI bus model and it implements V4L
subdev API. I split it out from MSi3101 SDR driver.
MSi3101 = MSi2500 + MSi001.
Signed-off-by: Antti Palosaari <crope@iki.fi>
Acked-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>
-rw-r--r-- | drivers/staging/media/msi3101/Kconfig | 4 | ||||
-rw-r--r-- | drivers/staging/media/msi3101/Makefile | 1 | ||||
-rw-r--r-- | drivers/staging/media/msi3101/msi001.c | 499 |
3 files changed, 504 insertions, 0 deletions
diff --git a/drivers/staging/media/msi3101/Kconfig b/drivers/staging/media/msi3101/Kconfig index 0c349c8595e4..97d5210d19c1 100644 --- a/drivers/staging/media/msi3101/Kconfig +++ b/drivers/staging/media/msi3101/Kconfig | |||
@@ -3,3 +3,7 @@ config USB_MSI3101 | |||
3 | depends on USB && VIDEO_DEV && VIDEO_V4L2 | 3 | depends on USB && VIDEO_DEV && VIDEO_V4L2 |
4 | select VIDEOBUF2_CORE | 4 | select VIDEOBUF2_CORE |
5 | select VIDEOBUF2_VMALLOC | 5 | select VIDEOBUF2_VMALLOC |
6 | |||
7 | config MEDIA_TUNER_MSI001 | ||
8 | tristate "Mirics MSi001" | ||
9 | depends on VIDEO_V4L2 && SPI | ||
diff --git a/drivers/staging/media/msi3101/Makefile b/drivers/staging/media/msi3101/Makefile index 3730654b0eb9..daf4f58d9a56 100644 --- a/drivers/staging/media/msi3101/Makefile +++ b/drivers/staging/media/msi3101/Makefile | |||
@@ -1 +1,2 @@ | |||
1 | obj-$(CONFIG_USB_MSI3101) += sdr-msi3101.o | 1 | obj-$(CONFIG_USB_MSI3101) += sdr-msi3101.o |
2 | obj-$(CONFIG_MEDIA_TUNER_MSI001) += msi001.o | ||
diff --git a/drivers/staging/media/msi3101/msi001.c b/drivers/staging/media/msi3101/msi001.c new file mode 100644 index 000000000000..25feece0a7b5 --- /dev/null +++ b/drivers/staging/media/msi3101/msi001.c | |||
@@ -0,0 +1,499 @@ | |||
1 | /* | ||
2 | * Mirics MSi001 silicon tuner driver | ||
3 | * | ||
4 | * Copyright (C) 2013 Antti Palosaari <crope@iki.fi> | ||
5 | * Copyright (C) 2014 Antti Palosaari <crope@iki.fi> | ||
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 | |||
18 | #include <linux/module.h> | ||
19 | #include <linux/gcd.h> | ||
20 | #include <media/v4l2-device.h> | ||
21 | #include <media/v4l2-ctrls.h> | ||
22 | |||
23 | static const struct v4l2_frequency_band bands[] = { | ||
24 | { | ||
25 | .type = V4L2_TUNER_RF, | ||
26 | .index = 0, | ||
27 | .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, | ||
28 | .rangelow = 49000000, | ||
29 | .rangehigh = 263000000, | ||
30 | }, { | ||
31 | .type = V4L2_TUNER_RF, | ||
32 | .index = 1, | ||
33 | .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, | ||
34 | .rangelow = 390000000, | ||
35 | .rangehigh = 960000000, | ||
36 | }, | ||
37 | }; | ||
38 | |||
39 | struct msi001 { | ||
40 | struct spi_device *spi; | ||
41 | struct v4l2_subdev sd; | ||
42 | |||
43 | /* Controls */ | ||
44 | struct v4l2_ctrl_handler hdl; | ||
45 | struct v4l2_ctrl *bandwidth_auto; | ||
46 | struct v4l2_ctrl *bandwidth; | ||
47 | struct v4l2_ctrl *lna_gain; | ||
48 | struct v4l2_ctrl *mixer_gain; | ||
49 | struct v4l2_ctrl *if_gain; | ||
50 | |||
51 | unsigned int f_tuner; | ||
52 | }; | ||
53 | |||
54 | static inline struct msi001 *sd_to_msi001(struct v4l2_subdev *sd) | ||
55 | { | ||
56 | return container_of(sd, struct msi001, sd); | ||
57 | } | ||
58 | |||
59 | static int msi001_wreg(struct msi001 *s, u32 data) | ||
60 | { | ||
61 | /* Register format: 4 bits addr + 20 bits value */ | ||
62 | return spi_write(s->spi, &data, 3); | ||
63 | }; | ||
64 | |||
65 | static int msi001_set_gain(struct msi001 *s, int lna_gain, int mixer_gain, | ||
66 | int if_gain) | ||
67 | { | ||
68 | int ret; | ||
69 | u32 reg; | ||
70 | dev_dbg(&s->spi->dev, "%s: lna=%d mixer=%d if=%d\n", __func__, | ||
71 | lna_gain, mixer_gain, if_gain); | ||
72 | |||
73 | reg = 1 << 0; | ||
74 | reg |= (59 - if_gain) << 4; | ||
75 | reg |= 0 << 10; | ||
76 | reg |= (1 - mixer_gain) << 12; | ||
77 | reg |= (1 - lna_gain) << 13; | ||
78 | reg |= 4 << 14; | ||
79 | reg |= 0 << 17; | ||
80 | ret = msi001_wreg(s, reg); | ||
81 | if (ret) | ||
82 | goto err; | ||
83 | |||
84 | return 0; | ||
85 | err: | ||
86 | dev_dbg(&s->spi->dev, "%s: failed %d\n", __func__, ret); | ||
87 | return ret; | ||
88 | }; | ||
89 | |||
90 | static int msi001_set_tuner(struct msi001 *s) | ||
91 | { | ||
92 | int ret, i; | ||
93 | unsigned int n, m, thresh, frac, vco_step, tmp, f_if1; | ||
94 | u32 reg; | ||
95 | u64 f_vco, tmp64; | ||
96 | u8 mode, filter_mode, lo_div; | ||
97 | static const struct { | ||
98 | u32 rf; | ||
99 | u8 mode; | ||
100 | u8 lo_div; | ||
101 | } band_lut[] = { | ||
102 | { 50000000, 0xe1, 16}, /* AM_MODE2, antenna 2 */ | ||
103 | {108000000, 0x42, 32}, /* VHF_MODE */ | ||
104 | {330000000, 0x44, 16}, /* B3_MODE */ | ||
105 | {960000000, 0x48, 4}, /* B45_MODE */ | ||
106 | { ~0U, 0x50, 2}, /* BL_MODE */ | ||
107 | }; | ||
108 | static const struct { | ||
109 | u32 freq; | ||
110 | u8 filter_mode; | ||
111 | } if_freq_lut[] = { | ||
112 | { 0, 0x03}, /* Zero IF */ | ||
113 | { 450000, 0x02}, /* 450 kHz IF */ | ||
114 | {1620000, 0x01}, /* 1.62 MHz IF */ | ||
115 | {2048000, 0x00}, /* 2.048 MHz IF */ | ||
116 | }; | ||
117 | static const struct { | ||
118 | u32 freq; | ||
119 | u8 val; | ||
120 | } bandwidth_lut[] = { | ||
121 | { 200000, 0x00}, /* 200 kHz */ | ||
122 | { 300000, 0x01}, /* 300 kHz */ | ||
123 | { 600000, 0x02}, /* 600 kHz */ | ||
124 | {1536000, 0x03}, /* 1.536 MHz */ | ||
125 | {5000000, 0x04}, /* 5 MHz */ | ||
126 | {6000000, 0x05}, /* 6 MHz */ | ||
127 | {7000000, 0x06}, /* 7 MHz */ | ||
128 | {8000000, 0x07}, /* 8 MHz */ | ||
129 | }; | ||
130 | |||
131 | unsigned int f_rf = s->f_tuner; | ||
132 | |||
133 | /* | ||
134 | * bandwidth (Hz) | ||
135 | * 200000, 300000, 600000, 1536000, 5000000, 6000000, 7000000, 8000000 | ||
136 | */ | ||
137 | unsigned int bandwidth; | ||
138 | |||
139 | /* | ||
140 | * intermediate frequency (Hz) | ||
141 | * 0, 450000, 1620000, 2048000 | ||
142 | */ | ||
143 | unsigned int f_if = 0; | ||
144 | #define F_REF 24000000 | ||
145 | #define R_REF 4 | ||
146 | #define F_OUT_STEP 1 | ||
147 | |||
148 | dev_dbg(&s->spi->dev, | ||
149 | "%s: f_rf=%d f_if=%d\n", | ||
150 | __func__, f_rf, f_if); | ||
151 | |||
152 | for (i = 0; i < ARRAY_SIZE(band_lut); i++) { | ||
153 | if (f_rf <= band_lut[i].rf) { | ||
154 | mode = band_lut[i].mode; | ||
155 | lo_div = band_lut[i].lo_div; | ||
156 | break; | ||
157 | } | ||
158 | } | ||
159 | |||
160 | if (i == ARRAY_SIZE(band_lut)) { | ||
161 | ret = -EINVAL; | ||
162 | goto err; | ||
163 | } | ||
164 | |||
165 | /* AM_MODE is upconverted */ | ||
166 | if ((mode >> 0) & 0x1) | ||
167 | f_if1 = 5 * F_REF; | ||
168 | else | ||
169 | f_if1 = 0; | ||
170 | |||
171 | for (i = 0; i < ARRAY_SIZE(if_freq_lut); i++) { | ||
172 | if (f_if == if_freq_lut[i].freq) { | ||
173 | filter_mode = if_freq_lut[i].filter_mode; | ||
174 | break; | ||
175 | } | ||
176 | } | ||
177 | |||
178 | if (i == ARRAY_SIZE(if_freq_lut)) { | ||
179 | ret = -EINVAL; | ||
180 | goto err; | ||
181 | } | ||
182 | |||
183 | /* filters */ | ||
184 | bandwidth = s->bandwidth->val; | ||
185 | bandwidth = clamp(bandwidth, 200000U, 8000000U); | ||
186 | |||
187 | for (i = 0; i < ARRAY_SIZE(bandwidth_lut); i++) { | ||
188 | if (bandwidth <= bandwidth_lut[i].freq) { | ||
189 | bandwidth = bandwidth_lut[i].val; | ||
190 | break; | ||
191 | } | ||
192 | } | ||
193 | |||
194 | if (i == ARRAY_SIZE(bandwidth_lut)) { | ||
195 | ret = -EINVAL; | ||
196 | goto err; | ||
197 | } | ||
198 | |||
199 | s->bandwidth->val = bandwidth_lut[i].freq; | ||
200 | |||
201 | dev_dbg(&s->spi->dev, "%s: bandwidth selected=%d\n", | ||
202 | __func__, bandwidth_lut[i].freq); | ||
203 | |||
204 | f_vco = (f_rf + f_if + f_if1) * lo_div; | ||
205 | tmp64 = f_vco; | ||
206 | m = do_div(tmp64, F_REF * R_REF); | ||
207 | n = (unsigned int) tmp64; | ||
208 | |||
209 | vco_step = F_OUT_STEP * lo_div; | ||
210 | thresh = (F_REF * R_REF) / vco_step; | ||
211 | frac = 1ul * thresh * m / (F_REF * R_REF); | ||
212 | |||
213 | /* Find out greatest common divisor and divide to smaller. */ | ||
214 | tmp = gcd(thresh, frac); | ||
215 | thresh /= tmp; | ||
216 | frac /= tmp; | ||
217 | |||
218 | /* Force divide to reg max. Resolution will be reduced. */ | ||
219 | tmp = DIV_ROUND_UP(thresh, 4095); | ||
220 | thresh = DIV_ROUND_CLOSEST(thresh, tmp); | ||
221 | frac = DIV_ROUND_CLOSEST(frac, tmp); | ||
222 | |||
223 | /* calc real RF set */ | ||
224 | tmp = 1ul * F_REF * R_REF * n; | ||
225 | tmp += 1ul * F_REF * R_REF * frac / thresh; | ||
226 | tmp /= lo_div; | ||
227 | |||
228 | dev_dbg(&s->spi->dev, | ||
229 | "%s: rf=%u:%u n=%d thresh=%d frac=%d\n", | ||
230 | __func__, f_rf, tmp, n, thresh, frac); | ||
231 | |||
232 | ret = msi001_wreg(s, 0x00000e); | ||
233 | if (ret) | ||
234 | goto err; | ||
235 | |||
236 | ret = msi001_wreg(s, 0x000003); | ||
237 | if (ret) | ||
238 | goto err; | ||
239 | |||
240 | reg = 0 << 0; | ||
241 | reg |= mode << 4; | ||
242 | reg |= filter_mode << 12; | ||
243 | reg |= bandwidth << 14; | ||
244 | reg |= 0x02 << 17; | ||
245 | reg |= 0x00 << 20; | ||
246 | ret = msi001_wreg(s, reg); | ||
247 | if (ret) | ||
248 | goto err; | ||
249 | |||
250 | reg = 5 << 0; | ||
251 | reg |= thresh << 4; | ||
252 | reg |= 1 << 19; | ||
253 | reg |= 1 << 21; | ||
254 | ret = msi001_wreg(s, reg); | ||
255 | if (ret) | ||
256 | goto err; | ||
257 | |||
258 | reg = 2 << 0; | ||
259 | reg |= frac << 4; | ||
260 | reg |= n << 16; | ||
261 | ret = msi001_wreg(s, reg); | ||
262 | if (ret) | ||
263 | goto err; | ||
264 | |||
265 | ret = msi001_set_gain(s, s->lna_gain->cur.val, s->mixer_gain->cur.val, | ||
266 | s->if_gain->cur.val); | ||
267 | if (ret) | ||
268 | goto err; | ||
269 | |||
270 | reg = 6 << 0; | ||
271 | reg |= 63 << 4; | ||
272 | reg |= 4095 << 10; | ||
273 | ret = msi001_wreg(s, reg); | ||
274 | if (ret) | ||
275 | goto err; | ||
276 | |||
277 | return 0; | ||
278 | err: | ||
279 | dev_dbg(&s->spi->dev, "%s: failed %d\n", __func__, ret); | ||
280 | return ret; | ||
281 | }; | ||
282 | |||
283 | static int msi001_s_power(struct v4l2_subdev *sd, int on) | ||
284 | { | ||
285 | struct msi001 *s = sd_to_msi001(sd); | ||
286 | int ret; | ||
287 | dev_dbg(&s->spi->dev, "%s: on=%d\n", __func__, on); | ||
288 | |||
289 | if (on) | ||
290 | ret = 0; | ||
291 | else | ||
292 | ret = msi001_wreg(s, 0x000000); | ||
293 | |||
294 | return ret; | ||
295 | } | ||
296 | |||
297 | static const struct v4l2_subdev_core_ops msi001_core_ops = { | ||
298 | .s_power = msi001_s_power, | ||
299 | }; | ||
300 | |||
301 | static int msi001_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v) | ||
302 | { | ||
303 | struct msi001 *s = sd_to_msi001(sd); | ||
304 | dev_dbg(&s->spi->dev, "%s: index=%d\n", __func__, v->index); | ||
305 | |||
306 | strlcpy(v->name, "Mirics MSi001", sizeof(v->name)); | ||
307 | v->type = V4L2_TUNER_RF; | ||
308 | v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS; | ||
309 | v->rangelow = 49000000; | ||
310 | v->rangehigh = 960000000; | ||
311 | |||
312 | return 0; | ||
313 | } | ||
314 | |||
315 | static int msi001_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *v) | ||
316 | { | ||
317 | struct msi001 *s = sd_to_msi001(sd); | ||
318 | dev_dbg(&s->spi->dev, "%s: index=%d\n", __func__, v->index); | ||
319 | return 0; | ||
320 | } | ||
321 | |||
322 | static int msi001_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) | ||
323 | { | ||
324 | struct msi001 *s = sd_to_msi001(sd); | ||
325 | dev_dbg(&s->spi->dev, "%s: tuner=%d\n", __func__, f->tuner); | ||
326 | f->frequency = s->f_tuner; | ||
327 | return 0; | ||
328 | } | ||
329 | |||
330 | static int msi001_s_frequency(struct v4l2_subdev *sd, | ||
331 | const struct v4l2_frequency *f) | ||
332 | { | ||
333 | struct msi001 *s = sd_to_msi001(sd); | ||
334 | unsigned int band; | ||
335 | dev_dbg(&s->spi->dev, "%s: tuner=%d type=%d frequency=%u\n", | ||
336 | __func__, f->tuner, f->type, f->frequency); | ||
337 | |||
338 | if (f->frequency < ((bands[0].rangehigh + bands[1].rangelow) / 2)) | ||
339 | band = 0; | ||
340 | else | ||
341 | band = 1; | ||
342 | s->f_tuner = clamp_t(unsigned int, f->frequency, | ||
343 | bands[band].rangelow, bands[band].rangehigh); | ||
344 | |||
345 | return msi001_set_tuner(s); | ||
346 | } | ||
347 | |||
348 | static int msi001_enum_freq_bands(struct v4l2_subdev *sd, | ||
349 | struct v4l2_frequency_band *band) | ||
350 | { | ||
351 | struct msi001 *s = sd_to_msi001(sd); | ||
352 | dev_dbg(&s->spi->dev, "%s: tuner=%d type=%d index=%d\n", | ||
353 | __func__, band->tuner, band->type, band->index); | ||
354 | |||
355 | if (band->index >= ARRAY_SIZE(bands)) | ||
356 | return -EINVAL; | ||
357 | |||
358 | band->capability = bands[band->index].capability; | ||
359 | band->rangelow = bands[band->index].rangelow; | ||
360 | band->rangehigh = bands[band->index].rangehigh; | ||
361 | |||
362 | return 0; | ||
363 | } | ||
364 | |||
365 | static const struct v4l2_subdev_tuner_ops msi001_tuner_ops = { | ||
366 | .g_tuner = msi001_g_tuner, | ||
367 | .s_tuner = msi001_s_tuner, | ||
368 | .g_frequency = msi001_g_frequency, | ||
369 | .s_frequency = msi001_s_frequency, | ||
370 | .enum_freq_bands = msi001_enum_freq_bands, | ||
371 | }; | ||
372 | |||
373 | static const struct v4l2_subdev_ops msi001_ops = { | ||
374 | .core = &msi001_core_ops, | ||
375 | .tuner = &msi001_tuner_ops, | ||
376 | }; | ||
377 | |||
378 | static int msi001_s_ctrl(struct v4l2_ctrl *ctrl) | ||
379 | { | ||
380 | struct msi001 *s = container_of(ctrl->handler, struct msi001, hdl); | ||
381 | |||
382 | int ret; | ||
383 | dev_dbg(&s->spi->dev, | ||
384 | "%s: id=%d name=%s val=%d min=%d max=%d step=%d\n", | ||
385 | __func__, ctrl->id, ctrl->name, ctrl->val, | ||
386 | ctrl->minimum, ctrl->maximum, ctrl->step); | ||
387 | |||
388 | switch (ctrl->id) { | ||
389 | case V4L2_CID_RF_TUNER_BANDWIDTH_AUTO: | ||
390 | case V4L2_CID_RF_TUNER_BANDWIDTH: | ||
391 | ret = msi001_set_tuner(s); | ||
392 | break; | ||
393 | case V4L2_CID_RF_TUNER_LNA_GAIN: | ||
394 | ret = msi001_set_gain(s, s->lna_gain->val, | ||
395 | s->mixer_gain->cur.val, s->if_gain->cur.val); | ||
396 | break; | ||
397 | case V4L2_CID_RF_TUNER_MIXER_GAIN: | ||
398 | ret = msi001_set_gain(s, s->lna_gain->cur.val, | ||
399 | s->mixer_gain->val, s->if_gain->cur.val); | ||
400 | break; | ||
401 | case V4L2_CID_RF_TUNER_IF_GAIN: | ||
402 | ret = msi001_set_gain(s, s->lna_gain->cur.val, | ||
403 | s->mixer_gain->cur.val, s->if_gain->val); | ||
404 | break; | ||
405 | default: | ||
406 | dev_dbg(&s->spi->dev, "%s: unkown control %d\n", | ||
407 | __func__, ctrl->id); | ||
408 | ret = -EINVAL; | ||
409 | } | ||
410 | |||
411 | return ret; | ||
412 | } | ||
413 | |||
414 | static const struct v4l2_ctrl_ops msi001_ctrl_ops = { | ||
415 | .s_ctrl = msi001_s_ctrl, | ||
416 | }; | ||
417 | |||
418 | static int msi001_probe(struct spi_device *spi) | ||
419 | { | ||
420 | struct msi001 *s; | ||
421 | int ret; | ||
422 | dev_dbg(&spi->dev, "%s:\n", __func__); | ||
423 | |||
424 | s = kzalloc(sizeof(struct msi001), GFP_KERNEL); | ||
425 | if (s == NULL) { | ||
426 | ret = -ENOMEM; | ||
427 | dev_dbg(&spi->dev, "Could not allocate memory for msi001\n"); | ||
428 | goto err_kfree; | ||
429 | } | ||
430 | |||
431 | s->spi = spi; | ||
432 | v4l2_spi_subdev_init(&s->sd, spi, &msi001_ops); | ||
433 | |||
434 | /* Register controls */ | ||
435 | v4l2_ctrl_handler_init(&s->hdl, 5); | ||
436 | s->bandwidth_auto = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops, | ||
437 | V4L2_CID_RF_TUNER_BANDWIDTH_AUTO, 0, 1, 1, 1); | ||
438 | s->bandwidth = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops, | ||
439 | V4L2_CID_RF_TUNER_BANDWIDTH, 200000, 8000000, 1, 200000); | ||
440 | v4l2_ctrl_auto_cluster(2, &s->bandwidth_auto, 0, false); | ||
441 | s->lna_gain = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops, | ||
442 | V4L2_CID_RF_TUNER_LNA_GAIN, 0, 1, 1, 1); | ||
443 | s->mixer_gain = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops, | ||
444 | V4L2_CID_RF_TUNER_MIXER_GAIN, 0, 1, 1, 1); | ||
445 | s->if_gain = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops, | ||
446 | V4L2_CID_RF_TUNER_IF_GAIN, 0, 59, 1, 0); | ||
447 | if (s->hdl.error) { | ||
448 | ret = s->hdl.error; | ||
449 | dev_err(&s->spi->dev, "Could not initialize controls\n"); | ||
450 | /* control init failed, free handler */ | ||
451 | goto err_ctrl_handler_free; | ||
452 | } | ||
453 | |||
454 | s->sd.ctrl_handler = &s->hdl; | ||
455 | return 0; | ||
456 | |||
457 | err_ctrl_handler_free: | ||
458 | v4l2_ctrl_handler_free(&s->hdl); | ||
459 | err_kfree: | ||
460 | kfree(s); | ||
461 | return ret; | ||
462 | } | ||
463 | |||
464 | static int msi001_remove(struct spi_device *spi) | ||
465 | { | ||
466 | struct v4l2_subdev *sd = spi_get_drvdata(spi); | ||
467 | struct msi001 *s = sd_to_msi001(sd); | ||
468 | dev_dbg(&spi->dev, "%s:\n", __func__); | ||
469 | |||
470 | /* | ||
471 | * Registered by v4l2_spi_new_subdev() from master driver, but we must | ||
472 | * unregister it from here. Weird. | ||
473 | */ | ||
474 | v4l2_device_unregister_subdev(&s->sd); | ||
475 | v4l2_ctrl_handler_free(&s->hdl); | ||
476 | kfree(s); | ||
477 | return 0; | ||
478 | } | ||
479 | |||
480 | static const struct spi_device_id msi001_id[] = { | ||
481 | {"msi001", 0}, | ||
482 | {} | ||
483 | }; | ||
484 | MODULE_DEVICE_TABLE(spi, msi001_id); | ||
485 | |||
486 | static struct spi_driver msi001_driver = { | ||
487 | .driver = { | ||
488 | .name = "msi001", | ||
489 | .owner = THIS_MODULE, | ||
490 | }, | ||
491 | .probe = msi001_probe, | ||
492 | .remove = msi001_remove, | ||
493 | .id_table = msi001_id, | ||
494 | }; | ||
495 | module_spi_driver(msi001_driver); | ||
496 | |||
497 | MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); | ||
498 | MODULE_DESCRIPTION("Mirics MSi001"); | ||
499 | MODULE_LICENSE("GPL"); | ||