Commit | Line | Data |
---|---|---|
67a8dbbc JC |
1 | /* |
2 | * Support for the camera device found on Marvell MMP processors; known | |
3 | * to work with the Armada 610 as used in the OLPC 1.75 system. | |
4 | * | |
5 | * Copyright 2011 Jonathan Corbet <corbet@lwn.net> | |
6 | * | |
7 | * This file may be distributed under the terms of the GNU General | |
8 | * Public License, version 2. | |
9 | */ | |
10 | ||
11 | #include <linux/init.h> | |
12 | #include <linux/kernel.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/i2c.h> | |
15 | #include <linux/i2c-gpio.h> | |
16 | #include <linux/interrupt.h> | |
17 | #include <linux/spinlock.h> | |
18 | #include <linux/slab.h> | |
19 | #include <linux/videodev2.h> | |
20 | #include <media/v4l2-device.h> | |
eb4b0ec7 | 21 | #include <linux/platform_data/media/mmp-camera.h> |
67a8dbbc JC |
22 | #include <linux/device.h> |
23 | #include <linux/platform_device.h> | |
24 | #include <linux/gpio.h> | |
25 | #include <linux/io.h> | |
26 | #include <linux/delay.h> | |
27 | #include <linux/list.h> | |
bb0a896e | 28 | #include <linux/pm.h> |
05fed816 | 29 | #include <linux/clk.h> |
67a8dbbc JC |
30 | |
31 | #include "mcam-core.h" | |
32 | ||
e27412f5 | 33 | MODULE_ALIAS("platform:mmp-camera"); |
67a8dbbc JC |
34 | MODULE_AUTHOR("Jonathan Corbet <corbet@lwn.net>"); |
35 | MODULE_LICENSE("GPL"); | |
36 | ||
0e394f44 LY |
37 | static char *mcam_clks[] = {"CCICAXICLK", "CCICFUNCLK", "CCICPHYCLK"}; |
38 | ||
67a8dbbc JC |
39 | struct mmp_camera { |
40 | void *power_regs; | |
41 | struct platform_device *pdev; | |
42 | struct mcam_camera mcam; | |
43 | struct list_head devlist; | |
05fed816 | 44 | struct clk *mipi_clk; |
67a8dbbc JC |
45 | int irq; |
46 | }; | |
47 | ||
48 | static inline struct mmp_camera *mcam_to_cam(struct mcam_camera *mcam) | |
49 | { | |
50 | return container_of(mcam, struct mmp_camera, mcam); | |
51 | } | |
52 | ||
53 | /* | |
54 | * A silly little infrastructure so we can keep track of our devices. | |
55 | * Chances are that we will never have more than one of them, but | |
56 | * the Armada 610 *does* have two controllers... | |
57 | */ | |
58 | ||
59 | static LIST_HEAD(mmpcam_devices); | |
60 | static struct mutex mmpcam_devices_lock; | |
61 | ||
62 | static void mmpcam_add_device(struct mmp_camera *cam) | |
63 | { | |
64 | mutex_lock(&mmpcam_devices_lock); | |
65 | list_add(&cam->devlist, &mmpcam_devices); | |
66 | mutex_unlock(&mmpcam_devices_lock); | |
67 | } | |
68 | ||
69 | static void mmpcam_remove_device(struct mmp_camera *cam) | |
70 | { | |
71 | mutex_lock(&mmpcam_devices_lock); | |
72 | list_del(&cam->devlist); | |
73 | mutex_unlock(&mmpcam_devices_lock); | |
74 | } | |
75 | ||
76 | /* | |
77 | * Platform dev remove passes us a platform_device, and there's | |
78 | * no handy unused drvdata to stash a backpointer in. So just | |
79 | * dig it out of our list. | |
80 | */ | |
81 | static struct mmp_camera *mmpcam_find_device(struct platform_device *pdev) | |
82 | { | |
83 | struct mmp_camera *cam; | |
84 | ||
85 | mutex_lock(&mmpcam_devices_lock); | |
86 | list_for_each_entry(cam, &mmpcam_devices, devlist) { | |
87 | if (cam->pdev == pdev) { | |
88 | mutex_unlock(&mmpcam_devices_lock); | |
89 | return cam; | |
90 | } | |
91 | } | |
92 | mutex_unlock(&mmpcam_devices_lock); | |
93 | return NULL; | |
94 | } | |
95 | ||
96 | ||
97 | ||
98 | ||
99 | /* | |
100 | * Power-related registers; this almost certainly belongs | |
101 | * somewhere else. | |
102 | * | |
103 | * ARMADA 610 register manual, sec 7.2.1, p1842. | |
104 | */ | |
105 | #define CPU_SUBSYS_PMU_BASE 0xd4282800 | |
106 | #define REG_CCIC_DCGCR 0x28 /* CCIC dyn clock gate ctrl reg */ | |
107 | #define REG_CCIC_CRCR 0x50 /* CCIC clk reset ctrl reg */ | |
7c269f45 | 108 | #define REG_CCIC2_CRCR 0xf4 /* CCIC2 clk reset ctrl reg */ |
67a8dbbc | 109 | |
0e394f44 LY |
110 | static void mcam_clk_enable(struct mcam_camera *mcam) |
111 | { | |
112 | unsigned int i; | |
113 | ||
114 | for (i = 0; i < NR_MCAM_CLK; i++) { | |
115 | if (!IS_ERR(mcam->clk[i])) | |
116 | clk_prepare_enable(mcam->clk[i]); | |
117 | } | |
118 | } | |
119 | ||
120 | static void mcam_clk_disable(struct mcam_camera *mcam) | |
121 | { | |
122 | int i; | |
123 | ||
124 | for (i = NR_MCAM_CLK - 1; i >= 0; i--) { | |
125 | if (!IS_ERR(mcam->clk[i])) | |
126 | clk_disable_unprepare(mcam->clk[i]); | |
127 | } | |
128 | } | |
129 | ||
67a8dbbc JC |
130 | /* |
131 | * Power control. | |
132 | */ | |
4a0abfaa JC |
133 | static void mmpcam_power_up_ctlr(struct mmp_camera *cam) |
134 | { | |
135 | iowrite32(0x3f, cam->power_regs + REG_CCIC_DCGCR); | |
136 | iowrite32(0x3805b, cam->power_regs + REG_CCIC_CRCR); | |
137 | mdelay(1); | |
138 | } | |
139 | ||
05fed816 | 140 | static int mmpcam_power_up(struct mcam_camera *mcam) |
67a8dbbc JC |
141 | { |
142 | struct mmp_camera *cam = mcam_to_cam(mcam); | |
143 | struct mmp_camera_platform_data *pdata; | |
05fed816 | 144 | |
67a8dbbc JC |
145 | /* |
146 | * Turn on power and clocks to the controller. | |
147 | */ | |
4a0abfaa | 148 | mmpcam_power_up_ctlr(cam); |
67a8dbbc JC |
149 | /* |
150 | * Provide power to the sensor. | |
151 | */ | |
152 | mcam_reg_write(mcam, REG_CLKCTRL, 0x60000002); | |
153 | pdata = cam->pdev->dev.platform_data; | |
154 | gpio_set_value(pdata->sensor_power_gpio, 1); | |
155 | mdelay(5); | |
156 | mcam_reg_clear_bit(mcam, REG_CTRL1, 0x10000000); | |
157 | gpio_set_value(pdata->sensor_reset_gpio, 0); /* reset is active low */ | |
158 | mdelay(5); | |
159 | gpio_set_value(pdata->sensor_reset_gpio, 1); /* reset is active low */ | |
160 | mdelay(5); | |
0e394f44 LY |
161 | |
162 | mcam_clk_enable(mcam); | |
163 | ||
05fed816 | 164 | return 0; |
67a8dbbc JC |
165 | } |
166 | ||
167 | static void mmpcam_power_down(struct mcam_camera *mcam) | |
168 | { | |
169 | struct mmp_camera *cam = mcam_to_cam(mcam); | |
170 | struct mmp_camera_platform_data *pdata; | |
171 | /* | |
172 | * Turn off clocks and set reset lines | |
173 | */ | |
174 | iowrite32(0, cam->power_regs + REG_CCIC_DCGCR); | |
175 | iowrite32(0, cam->power_regs + REG_CCIC_CRCR); | |
176 | /* | |
177 | * Shut down the sensor. | |
178 | */ | |
179 | pdata = cam->pdev->dev.platform_data; | |
180 | gpio_set_value(pdata->sensor_power_gpio, 0); | |
181 | gpio_set_value(pdata->sensor_reset_gpio, 0); | |
05fed816 | 182 | |
0e394f44 | 183 | mcam_clk_disable(mcam); |
67a8dbbc JC |
184 | } |
185 | ||
7c269f45 LY |
186 | void mcam_ctlr_reset(struct mcam_camera *mcam) |
187 | { | |
188 | unsigned long val; | |
189 | struct mmp_camera *cam = mcam_to_cam(mcam); | |
190 | ||
191 | if (mcam->ccic_id) { | |
192 | /* | |
193 | * Using CCIC2 | |
194 | */ | |
195 | val = ioread32(cam->power_regs + REG_CCIC2_CRCR); | |
196 | iowrite32(val & ~0x2, cam->power_regs + REG_CCIC2_CRCR); | |
197 | iowrite32(val | 0x2, cam->power_regs + REG_CCIC2_CRCR); | |
198 | } else { | |
199 | /* | |
200 | * Using CCIC1 | |
201 | */ | |
202 | val = ioread32(cam->power_regs + REG_CCIC_CRCR); | |
203 | iowrite32(val & ~0x2, cam->power_regs + REG_CCIC_CRCR); | |
204 | iowrite32(val | 0x2, cam->power_regs + REG_CCIC_CRCR); | |
205 | } | |
206 | } | |
207 | ||
05fed816 LY |
208 | /* |
209 | * calc the dphy register values | |
210 | * There are three dphy registers being used. | |
211 | * dphy[0] - CSI2_DPHY3 | |
212 | * dphy[1] - CSI2_DPHY5 | |
213 | * dphy[2] - CSI2_DPHY6 | |
214 | * CSI2_DPHY3 and CSI2_DPHY6 can be set with a default value | |
215 | * or be calculated dynamically | |
216 | */ | |
217 | void mmpcam_calc_dphy(struct mcam_camera *mcam) | |
218 | { | |
219 | struct mmp_camera *cam = mcam_to_cam(mcam); | |
220 | struct mmp_camera_platform_data *pdata = cam->pdev->dev.platform_data; | |
221 | struct device *dev = &cam->pdev->dev; | |
222 | unsigned long tx_clk_esc; | |
223 | ||
224 | /* | |
225 | * If CSI2_DPHY3 is calculated dynamically, | |
226 | * pdata->lane_clk should be already set | |
227 | * either in the board driver statically | |
228 | * or in the sensor driver dynamically. | |
229 | */ | |
230 | /* | |
231 | * dphy[0] - CSI2_DPHY3: | |
232 | * bit 0 ~ bit 7: HS Term Enable. | |
233 | * defines the time that the DPHY | |
234 | * wait before enabling the data | |
235 | * lane termination after detecting | |
236 | * that the sensor has driven the data | |
237 | * lanes to the LP00 bridge state. | |
238 | * The value is calculated by: | |
239 | * (Max T(D_TERM_EN)/Period(DDR)) - 1 | |
240 | * bit 8 ~ bit 15: HS_SETTLE | |
241 | * Time interval during which the HS | |
242 | * receiver shall ignore any Data Lane | |
243 | * HS transistions. | |
244 | * The vaule has been calibrated on | |
245 | * different boards. It seems to work well. | |
246 | * | |
247 | * More detail please refer | |
248 | * MIPI Alliance Spectification for D-PHY | |
249 | * document for explanation of HS-SETTLE | |
250 | * and D-TERM-EN. | |
251 | */ | |
252 | switch (pdata->dphy3_algo) { | |
253 | case DPHY3_ALGO_PXA910: | |
254 | /* | |
255 | * Calculate CSI2_DPHY3 algo for PXA910 | |
256 | */ | |
257 | pdata->dphy[0] = | |
258 | (((1 + (pdata->lane_clk * 80) / 1000) & 0xff) << 8) | |
259 | | (1 + pdata->lane_clk * 35 / 1000); | |
260 | break; | |
261 | case DPHY3_ALGO_PXA2128: | |
262 | /* | |
263 | * Calculate CSI2_DPHY3 algo for PXA2128 | |
264 | */ | |
265 | pdata->dphy[0] = | |
266 | (((2 + (pdata->lane_clk * 110) / 1000) & 0xff) << 8) | |
267 | | (1 + pdata->lane_clk * 35 / 1000); | |
268 | break; | |
269 | default: | |
270 | /* | |
271 | * Use default CSI2_DPHY3 value for PXA688/PXA988 | |
272 | */ | |
273 | dev_dbg(dev, "camera: use the default CSI2_DPHY3 value\n"); | |
274 | } | |
275 | ||
276 | /* | |
277 | * mipi_clk will never be changed, it is a fixed value on MMP | |
278 | */ | |
279 | if (IS_ERR(cam->mipi_clk)) | |
280 | return; | |
281 | ||
282 | /* get the escape clk, this is hard coded */ | |
326f5a3f | 283 | clk_prepare_enable(cam->mipi_clk); |
05fed816 | 284 | tx_clk_esc = (clk_get_rate(cam->mipi_clk) / 1000000) / 12; |
326f5a3f | 285 | clk_disable_unprepare(cam->mipi_clk); |
05fed816 LY |
286 | /* |
287 | * dphy[2] - CSI2_DPHY6: | |
288 | * bit 0 ~ bit 7: CK Term Enable | |
289 | * Time for the Clock Lane receiver to enable the HS line | |
290 | * termination. The value is calculated similarly with | |
291 | * HS Term Enable | |
292 | * bit 8 ~ bit 15: CK Settle | |
293 | * Time interval during which the HS receiver shall ignore | |
294 | * any Clock Lane HS transitions. | |
295 | * The value is calibrated on the boards. | |
296 | */ | |
297 | pdata->dphy[2] = | |
298 | ((((534 * tx_clk_esc) / 2000 - 1) & 0xff) << 8) | |
299 | | (((38 * tx_clk_esc) / 1000 - 1) & 0xff); | |
300 | ||
301 | dev_dbg(dev, "camera: DPHY sets: dphy3=0x%x, dphy5=0x%x, dphy6=0x%x\n", | |
302 | pdata->dphy[0], pdata->dphy[1], pdata->dphy[2]); | |
303 | } | |
67a8dbbc JC |
304 | |
305 | static irqreturn_t mmpcam_irq(int irq, void *data) | |
306 | { | |
307 | struct mcam_camera *mcam = data; | |
308 | unsigned int irqs, handled; | |
309 | ||
310 | spin_lock(&mcam->dev_lock); | |
311 | irqs = mcam_reg_read(mcam, REG_IRQSTAT); | |
312 | handled = mccic_irq(mcam, irqs); | |
313 | spin_unlock(&mcam->dev_lock); | |
314 | return IRQ_RETVAL(handled); | |
315 | } | |
316 | ||
0e394f44 LY |
317 | static void mcam_init_clk(struct mcam_camera *mcam) |
318 | { | |
319 | unsigned int i; | |
320 | ||
321 | for (i = 0; i < NR_MCAM_CLK; i++) { | |
322 | if (mcam_clks[i] != NULL) { | |
323 | /* Some clks are not necessary on some boards | |
324 | * We still try to run even it fails getting clk | |
325 | */ | |
326 | mcam->clk[i] = devm_clk_get(mcam->dev, mcam_clks[i]); | |
327 | if (IS_ERR(mcam->clk[i])) | |
328 | dev_warn(mcam->dev, "Could not get clk: %s\n", | |
329 | mcam_clks[i]); | |
330 | } | |
331 | } | |
332 | } | |
67a8dbbc JC |
333 | |
334 | static int mmpcam_probe(struct platform_device *pdev) | |
335 | { | |
336 | struct mmp_camera *cam; | |
337 | struct mcam_camera *mcam; | |
338 | struct resource *res; | |
339 | struct mmp_camera_platform_data *pdata; | |
340 | int ret; | |
341 | ||
05fed816 LY |
342 | pdata = pdev->dev.platform_data; |
343 | if (!pdata) | |
344 | return -ENODEV; | |
345 | ||
578e99d6 | 346 | cam = devm_kzalloc(&pdev->dev, sizeof(*cam), GFP_KERNEL); |
67a8dbbc JC |
347 | if (cam == NULL) |
348 | return -ENOMEM; | |
349 | cam->pdev = pdev; | |
350 | INIT_LIST_HEAD(&cam->devlist); | |
351 | ||
352 | mcam = &cam->mcam; | |
67a8dbbc JC |
353 | mcam->plat_power_up = mmpcam_power_up; |
354 | mcam->plat_power_down = mmpcam_power_down; | |
7c269f45 | 355 | mcam->ctlr_reset = mcam_ctlr_reset; |
05fed816 | 356 | mcam->calc_dphy = mmpcam_calc_dphy; |
67a8dbbc JC |
357 | mcam->dev = &pdev->dev; |
358 | mcam->use_smbus = 0; | |
7c269f45 | 359 | mcam->ccic_id = pdev->id; |
05fed816 LY |
360 | mcam->mclk_min = pdata->mclk_min; |
361 | mcam->mclk_src = pdata->mclk_src; | |
362 | mcam->mclk_div = pdata->mclk_div; | |
363 | mcam->bus_type = pdata->bus_type; | |
364 | mcam->dphy = pdata->dphy; | |
326f5a3f LY |
365 | if (mcam->bus_type == V4L2_MBUS_CSI2) { |
366 | cam->mipi_clk = devm_clk_get(mcam->dev, "mipi"); | |
367 | if ((IS_ERR(cam->mipi_clk) && mcam->dphy[2] == 0)) | |
368 | return PTR_ERR(cam->mipi_clk); | |
369 | } | |
05fed816 LY |
370 | mcam->mipi_enabled = false; |
371 | mcam->lane = pdata->lane; | |
7486af1a | 372 | mcam->chip_id = MCAM_ARMADA610; |
28720944 | 373 | mcam->buffer_mode = B_DMA_sg; |
b7b68393 | 374 | strlcpy(mcam->bus_info, "platform:mmp-camera", sizeof(mcam->bus_info)); |
67a8dbbc JC |
375 | spin_lock_init(&mcam->dev_lock); |
376 | /* | |
377 | * Get our I/O memory. | |
378 | */ | |
379 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
578e99d6 LY |
380 | mcam->regs = devm_ioremap_resource(&pdev->dev, res); |
381 | if (IS_ERR(mcam->regs)) | |
382 | return PTR_ERR(mcam->regs); | |
4e032f3f | 383 | mcam->regs_size = resource_size(res); |
67a8dbbc JC |
384 | /* |
385 | * Power/clock memory is elsewhere; get it too. Perhaps this | |
386 | * should really be managed outside of this driver? | |
387 | */ | |
388 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1); | |
578e99d6 LY |
389 | cam->power_regs = devm_ioremap_resource(&pdev->dev, res); |
390 | if (IS_ERR(cam->power_regs)) | |
391 | return PTR_ERR(cam->power_regs); | |
67a8dbbc JC |
392 | /* |
393 | * Find the i2c adapter. This assumes, of course, that the | |
394 | * i2c bus is already up and functioning. | |
395 | */ | |
67a8dbbc JC |
396 | mcam->i2c_adapter = platform_get_drvdata(pdata->i2c_device); |
397 | if (mcam->i2c_adapter == NULL) { | |
67a8dbbc | 398 | dev_err(&pdev->dev, "No i2c adapter\n"); |
578e99d6 | 399 | return -ENODEV; |
67a8dbbc JC |
400 | } |
401 | /* | |
402 | * Sensor GPIO pins. | |
403 | */ | |
578e99d6 LY |
404 | ret = devm_gpio_request(&pdev->dev, pdata->sensor_power_gpio, |
405 | "cam-power"); | |
67a8dbbc JC |
406 | if (ret) { |
407 | dev_err(&pdev->dev, "Can't get sensor power gpio %d", | |
408 | pdata->sensor_power_gpio); | |
578e99d6 | 409 | return ret; |
67a8dbbc JC |
410 | } |
411 | gpio_direction_output(pdata->sensor_power_gpio, 0); | |
578e99d6 LY |
412 | ret = devm_gpio_request(&pdev->dev, pdata->sensor_reset_gpio, |
413 | "cam-reset"); | |
67a8dbbc JC |
414 | if (ret) { |
415 | dev_err(&pdev->dev, "Can't get sensor reset gpio %d", | |
416 | pdata->sensor_reset_gpio); | |
578e99d6 | 417 | return ret; |
67a8dbbc JC |
418 | } |
419 | gpio_direction_output(pdata->sensor_reset_gpio, 0); | |
0e394f44 LY |
420 | |
421 | mcam_init_clk(mcam); | |
422 | ||
67a8dbbc JC |
423 | /* |
424 | * Power the device up and hand it off to the core. | |
425 | */ | |
05fed816 | 426 | ret = mmpcam_power_up(mcam); |
67a8dbbc | 427 | if (ret) |
326f5a3f | 428 | return ret; |
05fed816 LY |
429 | ret = mccic_register(mcam); |
430 | if (ret) | |
578e99d6 | 431 | goto out_power_down; |
67a8dbbc JC |
432 | /* |
433 | * Finally, set up our IRQ now that the core is ready to | |
434 | * deal with it. | |
435 | */ | |
436 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
437 | if (res == NULL) { | |
438 | ret = -ENODEV; | |
439 | goto out_unregister; | |
440 | } | |
441 | cam->irq = res->start; | |
578e99d6 LY |
442 | ret = devm_request_irq(&pdev->dev, cam->irq, mmpcam_irq, IRQF_SHARED, |
443 | "mmp-camera", mcam); | |
67a8dbbc JC |
444 | if (ret == 0) { |
445 | mmpcam_add_device(cam); | |
446 | return 0; | |
447 | } | |
448 | ||
449 | out_unregister: | |
450 | mccic_shutdown(mcam); | |
578e99d6 | 451 | out_power_down: |
221a8248 | 452 | mmpcam_power_down(mcam); |
67a8dbbc JC |
453 | return ret; |
454 | } | |
455 | ||
456 | ||
457 | static int mmpcam_remove(struct mmp_camera *cam) | |
458 | { | |
459 | struct mcam_camera *mcam = &cam->mcam; | |
67a8dbbc JC |
460 | |
461 | mmpcam_remove_device(cam); | |
67a8dbbc JC |
462 | mccic_shutdown(mcam); |
463 | mmpcam_power_down(mcam); | |
67a8dbbc JC |
464 | return 0; |
465 | } | |
466 | ||
467 | static int mmpcam_platform_remove(struct platform_device *pdev) | |
468 | { | |
469 | struct mmp_camera *cam = mmpcam_find_device(pdev); | |
470 | ||
471 | if (cam == NULL) | |
472 | return -ENODEV; | |
473 | return mmpcam_remove(cam); | |
474 | } | |
475 | ||
bb0a896e JC |
476 | /* |
477 | * Suspend/resume support. | |
478 | */ | |
479 | #ifdef CONFIG_PM | |
480 | ||
481 | static int mmpcam_suspend(struct platform_device *pdev, pm_message_t state) | |
482 | { | |
483 | struct mmp_camera *cam = mmpcam_find_device(pdev); | |
484 | ||
485 | if (state.event != PM_EVENT_SUSPEND) | |
486 | return 0; | |
487 | mccic_suspend(&cam->mcam); | |
488 | return 0; | |
489 | } | |
490 | ||
491 | static int mmpcam_resume(struct platform_device *pdev) | |
492 | { | |
493 | struct mmp_camera *cam = mmpcam_find_device(pdev); | |
494 | ||
495 | /* | |
496 | * Power up unconditionally just in case the core tries to | |
497 | * touch a register even if nothing was active before; trust | |
498 | * me, it's better this way. | |
499 | */ | |
4a0abfaa | 500 | mmpcam_power_up_ctlr(cam); |
bb0a896e JC |
501 | return mccic_resume(&cam->mcam); |
502 | } | |
503 | ||
504 | #endif | |
505 | ||
67a8dbbc JC |
506 | |
507 | static struct platform_driver mmpcam_driver = { | |
508 | .probe = mmpcam_probe, | |
509 | .remove = mmpcam_platform_remove, | |
bb0a896e JC |
510 | #ifdef CONFIG_PM |
511 | .suspend = mmpcam_suspend, | |
512 | .resume = mmpcam_resume, | |
513 | #endif | |
67a8dbbc JC |
514 | .driver = { |
515 | .name = "mmp-camera", | |
67a8dbbc JC |
516 | } |
517 | }; | |
518 | ||
519 | ||
520 | static int __init mmpcam_init_module(void) | |
521 | { | |
522 | mutex_init(&mmpcam_devices_lock); | |
523 | return platform_driver_register(&mmpcam_driver); | |
524 | } | |
525 | ||
526 | static void __exit mmpcam_exit_module(void) | |
527 | { | |
528 | platform_driver_unregister(&mmpcam_driver); | |
529 | /* | |
530 | * platform_driver_unregister() should have emptied the list | |
531 | */ | |
532 | if (!list_empty(&mmpcam_devices)) | |
533 | printk(KERN_ERR "mmp_camera leaving devices behind\n"); | |
534 | } | |
535 | ||
536 | module_init(mmpcam_init_module); | |
537 | module_exit(mmpcam_exit_module); |