diff options
author | Jonathan Corbet <corbet@lwn.net> | 2011-06-11 13:46:49 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2011-07-27 16:53:01 -0400 |
commit | 67a8dbbc4e04cd256987b189352472a59aff73be (patch) | |
tree | 767486631bf85293111a634737dfabb954f98950 /drivers/media | |
parent | 595a93a47a3b7dc1be84160fbd73b1406074f411 (diff) |
[media] marvell-cam: Basic working MMP camera driver
Now we have a camera working over the marvell cam controller core. It
works like the cafe driver and has all the same limitations, contiguous DMA
only being one of them. But it's a start.
Signed-off-by: Jonathan Corbet <corbet@lwn.net>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/video/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/video/marvell-ccic/Kconfig | 11 | ||||
-rw-r--r-- | drivers/media/video/marvell-ccic/Makefile | 4 | ||||
-rw-r--r-- | drivers/media/video/marvell-ccic/mcam-core.c | 28 | ||||
-rw-r--r-- | drivers/media/video/marvell-ccic/mmp-driver.c | 339 |
5 files changed, 375 insertions, 8 deletions
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 42b6a7a6482..89478f01731 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile | |||
@@ -128,6 +128,7 @@ obj-$(CONFIG_VIDEO_M32R_AR_M64278) += arv.o | |||
128 | obj-$(CONFIG_VIDEO_CX2341X) += cx2341x.o | 128 | obj-$(CONFIG_VIDEO_CX2341X) += cx2341x.o |
129 | 129 | ||
130 | obj-$(CONFIG_VIDEO_CAFE_CCIC) += marvell-ccic/ | 130 | obj-$(CONFIG_VIDEO_CAFE_CCIC) += marvell-ccic/ |
131 | obj-$(CONFIG_VIDEO_MMP_CAMERA) += marvell-ccic/ | ||
131 | 132 | ||
132 | obj-$(CONFIG_VIDEO_VIA_CAMERA) += via-camera.o | 133 | obj-$(CONFIG_VIDEO_VIA_CAMERA) += via-camera.o |
133 | 134 | ||
diff --git a/drivers/media/video/marvell-ccic/Kconfig b/drivers/media/video/marvell-ccic/Kconfig index 80136a8771e..b4f72605997 100644 --- a/drivers/media/video/marvell-ccic/Kconfig +++ b/drivers/media/video/marvell-ccic/Kconfig | |||
@@ -7,3 +7,14 @@ config VIDEO_CAFE_CCIC | |||
7 | CMOS camera controller. This is the controller found on first- | 7 | CMOS camera controller. This is the controller found on first- |
8 | generation OLPC systems. | 8 | generation OLPC systems. |
9 | 9 | ||
10 | config VIDEO_MMP_CAMERA | ||
11 | tristate "Marvell Armada 610 integrated camera controller support" | ||
12 | depends on ARCH_MMP && I2C && VIDEO_V4L2 | ||
13 | select VIDEO_OV7670 | ||
14 | select I2C_GPIO | ||
15 | ---help--- | ||
16 | This is a Video4Linux2 driver for the integrated camera | ||
17 | controller found on Marvell Armada 610 application | ||
18 | processors (and likely beyond). This is the controller found | ||
19 | in OLPC XO 1.75 systems. | ||
20 | |||
diff --git a/drivers/media/video/marvell-ccic/Makefile b/drivers/media/video/marvell-ccic/Makefile index 462b385c623..05a792c579a 100644 --- a/drivers/media/video/marvell-ccic/Makefile +++ b/drivers/media/video/marvell-ccic/Makefile | |||
@@ -1,2 +1,6 @@ | |||
1 | obj-$(CONFIG_VIDEO_CAFE_CCIC) += cafe_ccic.o | 1 | obj-$(CONFIG_VIDEO_CAFE_CCIC) += cafe_ccic.o |
2 | cafe_ccic-y := cafe-driver.o mcam-core.o | 2 | cafe_ccic-y := cafe-driver.o mcam-core.o |
3 | |||
4 | obj-$(CONFIG_VIDEO_MMP_CAMERA) += mmp_camera.o | ||
5 | mmp_camera-y := mmp-driver.o mcam-core.o | ||
6 | |||
diff --git a/drivers/media/video/marvell-ccic/mcam-core.c b/drivers/media/video/marvell-ccic/mcam-core.c index 014b70b5a9b..3e6a5e8b0cd 100644 --- a/drivers/media/video/marvell-ccic/mcam-core.c +++ b/drivers/media/video/marvell-ccic/mcam-core.c | |||
@@ -167,7 +167,7 @@ static void mcam_set_config_needed(struct mcam_camera *cam, int needed) | |||
167 | 167 | ||
168 | 168 | ||
169 | /* | 169 | /* |
170 | * Debugging and related. FIXME these are broken | 170 | * Debugging and related. |
171 | */ | 171 | */ |
172 | #define cam_err(cam, fmt, arg...) \ | 172 | #define cam_err(cam, fmt, arg...) \ |
173 | dev_err((cam)->dev, fmt, ##arg); | 173 | dev_err((cam)->dev, fmt, ##arg); |
@@ -202,7 +202,8 @@ static void mcam_ctlr_dma(struct mcam_camera *cam) | |||
202 | mcam_reg_clear_bit(cam, REG_CTRL1, C1_TWOBUFS); | 202 | mcam_reg_clear_bit(cam, REG_CTRL1, C1_TWOBUFS); |
203 | } else | 203 | } else |
204 | mcam_reg_set_bit(cam, REG_CTRL1, C1_TWOBUFS); | 204 | mcam_reg_set_bit(cam, REG_CTRL1, C1_TWOBUFS); |
205 | mcam_reg_write(cam, REG_UBAR, 0); /* 32 bits only for now */ | 205 | if (cam->chip_id == V4L2_IDENT_CAFE) |
206 | mcam_reg_write(cam, REG_UBAR, 0); /* 32 bits only */ | ||
206 | } | 207 | } |
207 | 208 | ||
208 | static void mcam_ctlr_image(struct mcam_camera *cam) | 209 | static void mcam_ctlr_image(struct mcam_camera *cam) |
@@ -358,8 +359,8 @@ static void mcam_ctlr_power_up(struct mcam_camera *cam) | |||
358 | unsigned long flags; | 359 | unsigned long flags; |
359 | 360 | ||
360 | spin_lock_irqsave(&cam->dev_lock, flags); | 361 | spin_lock_irqsave(&cam->dev_lock, flags); |
361 | mcam_reg_clear_bit(cam, REG_CTRL1, C1_PWRDWN); | ||
362 | cam->plat_power_up(cam); | 362 | cam->plat_power_up(cam); |
363 | mcam_reg_clear_bit(cam, REG_CTRL1, C1_PWRDWN); | ||
363 | spin_unlock_irqrestore(&cam->dev_lock, flags); | 364 | spin_unlock_irqrestore(&cam->dev_lock, flags); |
364 | msleep(5); /* Just to be sure */ | 365 | msleep(5); /* Just to be sure */ |
365 | } | 366 | } |
@@ -369,8 +370,13 @@ static void mcam_ctlr_power_down(struct mcam_camera *cam) | |||
369 | unsigned long flags; | 370 | unsigned long flags; |
370 | 371 | ||
371 | spin_lock_irqsave(&cam->dev_lock, flags); | 372 | spin_lock_irqsave(&cam->dev_lock, flags); |
372 | cam->plat_power_down(cam); | 373 | /* |
374 | * School of hard knocks department: be sure we do any register | ||
375 | * twiddling on the controller *before* calling the platform | ||
376 | * power down routine. | ||
377 | */ | ||
373 | mcam_reg_set_bit(cam, REG_CTRL1, C1_PWRDWN); | 378 | mcam_reg_set_bit(cam, REG_CTRL1, C1_PWRDWN); |
379 | cam->plat_power_down(cam); | ||
374 | spin_unlock_irqrestore(&cam->dev_lock, flags); | 380 | spin_unlock_irqrestore(&cam->dev_lock, flags); |
375 | } | 381 | } |
376 | 382 | ||
@@ -1622,14 +1628,20 @@ out_unregister: | |||
1622 | 1628 | ||
1623 | void mccic_shutdown(struct mcam_camera *cam) | 1629 | void mccic_shutdown(struct mcam_camera *cam) |
1624 | { | 1630 | { |
1625 | if (cam->users > 0) | 1631 | /* |
1632 | * If we have no users (and we really, really should have no | ||
1633 | * users) the device will already be powered down. Trying to | ||
1634 | * take it down again will wedge the machine, which is frowned | ||
1635 | * upon. | ||
1636 | */ | ||
1637 | if (cam->users > 0) { | ||
1626 | cam_warn(cam, "Removing a device with users!\n"); | 1638 | cam_warn(cam, "Removing a device with users!\n"); |
1639 | mcam_ctlr_power_down(cam); | ||
1640 | } | ||
1641 | mcam_free_dma_bufs(cam); | ||
1627 | if (cam->n_sbufs > 0) | 1642 | if (cam->n_sbufs > 0) |
1628 | /* What if they are still mapped? Shouldn't be, but... */ | 1643 | /* What if they are still mapped? Shouldn't be, but... */ |
1629 | mcam_free_sio_buffers(cam); | 1644 | mcam_free_sio_buffers(cam); |
1630 | mcam_ctlr_stop_dma(cam); | ||
1631 | mcam_ctlr_power_down(cam); | ||
1632 | mcam_free_dma_bufs(cam); | ||
1633 | video_unregister_device(&cam->vdev); | 1645 | video_unregister_device(&cam->vdev); |
1634 | v4l2_device_unregister(&cam->v4l2_dev); | 1646 | v4l2_device_unregister(&cam->v4l2_dev); |
1635 | } | 1647 | } |
diff --git a/drivers/media/video/marvell-ccic/mmp-driver.c b/drivers/media/video/marvell-ccic/mmp-driver.c new file mode 100644 index 00000000000..ac9976f23a6 --- /dev/null +++ b/drivers/media/video/marvell-ccic/mmp-driver.c | |||
@@ -0,0 +1,339 @@ | |||
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> | ||
21 | #include <media/v4l2-chip-ident.h> | ||
22 | #include <media/mmp-camera.h> | ||
23 | #include <linux/device.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | #include <linux/gpio.h> | ||
26 | #include <linux/io.h> | ||
27 | #include <linux/delay.h> | ||
28 | #include <linux/list.h> | ||
29 | |||
30 | #include "mcam-core.h" | ||
31 | |||
32 | MODULE_AUTHOR("Jonathan Corbet <corbet@lwn.net>"); | ||
33 | MODULE_LICENSE("GPL"); | ||
34 | |||
35 | struct mmp_camera { | ||
36 | void *power_regs; | ||
37 | struct platform_device *pdev; | ||
38 | struct mcam_camera mcam; | ||
39 | struct list_head devlist; | ||
40 | int irq; | ||
41 | }; | ||
42 | |||
43 | static inline struct mmp_camera *mcam_to_cam(struct mcam_camera *mcam) | ||
44 | { | ||
45 | return container_of(mcam, struct mmp_camera, mcam); | ||
46 | } | ||
47 | |||
48 | /* | ||
49 | * A silly little infrastructure so we can keep track of our devices. | ||
50 | * Chances are that we will never have more than one of them, but | ||
51 | * the Armada 610 *does* have two controllers... | ||
52 | */ | ||
53 | |||
54 | static LIST_HEAD(mmpcam_devices); | ||
55 | static struct mutex mmpcam_devices_lock; | ||
56 | |||
57 | static void mmpcam_add_device(struct mmp_camera *cam) | ||
58 | { | ||
59 | mutex_lock(&mmpcam_devices_lock); | ||
60 | list_add(&cam->devlist, &mmpcam_devices); | ||
61 | mutex_unlock(&mmpcam_devices_lock); | ||
62 | } | ||
63 | |||
64 | static void mmpcam_remove_device(struct mmp_camera *cam) | ||
65 | { | ||
66 | mutex_lock(&mmpcam_devices_lock); | ||
67 | list_del(&cam->devlist); | ||
68 | mutex_unlock(&mmpcam_devices_lock); | ||
69 | } | ||
70 | |||
71 | /* | ||
72 | * Platform dev remove passes us a platform_device, and there's | ||
73 | * no handy unused drvdata to stash a backpointer in. So just | ||
74 | * dig it out of our list. | ||
75 | */ | ||
76 | static struct mmp_camera *mmpcam_find_device(struct platform_device *pdev) | ||
77 | { | ||
78 | struct mmp_camera *cam; | ||
79 | |||
80 | mutex_lock(&mmpcam_devices_lock); | ||
81 | list_for_each_entry(cam, &mmpcam_devices, devlist) { | ||
82 | if (cam->pdev == pdev) { | ||
83 | mutex_unlock(&mmpcam_devices_lock); | ||
84 | return cam; | ||
85 | } | ||
86 | } | ||
87 | mutex_unlock(&mmpcam_devices_lock); | ||
88 | return NULL; | ||
89 | } | ||
90 | |||
91 | |||
92 | |||
93 | |||
94 | /* | ||
95 | * Power-related registers; this almost certainly belongs | ||
96 | * somewhere else. | ||
97 | * | ||
98 | * ARMADA 610 register manual, sec 7.2.1, p1842. | ||
99 | */ | ||
100 | #define CPU_SUBSYS_PMU_BASE 0xd4282800 | ||
101 | #define REG_CCIC_DCGCR 0x28 /* CCIC dyn clock gate ctrl reg */ | ||
102 | #define REG_CCIC_CRCR 0x50 /* CCIC clk reset ctrl reg */ | ||
103 | |||
104 | /* | ||
105 | * Power control. | ||
106 | */ | ||
107 | static void mmpcam_power_up(struct mcam_camera *mcam) | ||
108 | { | ||
109 | struct mmp_camera *cam = mcam_to_cam(mcam); | ||
110 | struct mmp_camera_platform_data *pdata; | ||
111 | /* | ||
112 | * Turn on power and clocks to the controller. | ||
113 | */ | ||
114 | iowrite32(0x3f, cam->power_regs + REG_CCIC_DCGCR); | ||
115 | iowrite32(0x3805b, cam->power_regs + REG_CCIC_CRCR); | ||
116 | mdelay(1); | ||
117 | /* | ||
118 | * Provide power to the sensor. | ||
119 | */ | ||
120 | mcam_reg_write(mcam, REG_CLKCTRL, 0x60000002); | ||
121 | pdata = cam->pdev->dev.platform_data; | ||
122 | gpio_set_value(pdata->sensor_power_gpio, 1); | ||
123 | mdelay(5); | ||
124 | mcam_reg_clear_bit(mcam, REG_CTRL1, 0x10000000); | ||
125 | gpio_set_value(pdata->sensor_reset_gpio, 0); /* reset is active low */ | ||
126 | mdelay(5); | ||
127 | gpio_set_value(pdata->sensor_reset_gpio, 1); /* reset is active low */ | ||
128 | mdelay(5); | ||
129 | } | ||
130 | |||
131 | static void mmpcam_power_down(struct mcam_camera *mcam) | ||
132 | { | ||
133 | struct mmp_camera *cam = mcam_to_cam(mcam); | ||
134 | struct mmp_camera_platform_data *pdata; | ||
135 | /* | ||
136 | * Turn off clocks and set reset lines | ||
137 | */ | ||
138 | iowrite32(0, cam->power_regs + REG_CCIC_DCGCR); | ||
139 | iowrite32(0, cam->power_regs + REG_CCIC_CRCR); | ||
140 | /* | ||
141 | * Shut down the sensor. | ||
142 | */ | ||
143 | pdata = cam->pdev->dev.platform_data; | ||
144 | gpio_set_value(pdata->sensor_power_gpio, 0); | ||
145 | gpio_set_value(pdata->sensor_reset_gpio, 0); | ||
146 | } | ||
147 | |||
148 | |||
149 | static irqreturn_t mmpcam_irq(int irq, void *data) | ||
150 | { | ||
151 | struct mcam_camera *mcam = data; | ||
152 | unsigned int irqs, handled; | ||
153 | |||
154 | spin_lock(&mcam->dev_lock); | ||
155 | irqs = mcam_reg_read(mcam, REG_IRQSTAT); | ||
156 | handled = mccic_irq(mcam, irqs); | ||
157 | spin_unlock(&mcam->dev_lock); | ||
158 | return IRQ_RETVAL(handled); | ||
159 | } | ||
160 | |||
161 | |||
162 | static int mmpcam_probe(struct platform_device *pdev) | ||
163 | { | ||
164 | struct mmp_camera *cam; | ||
165 | struct mcam_camera *mcam; | ||
166 | struct resource *res; | ||
167 | struct mmp_camera_platform_data *pdata; | ||
168 | int ret; | ||
169 | |||
170 | cam = kzalloc(sizeof(*cam), GFP_KERNEL); | ||
171 | if (cam == NULL) | ||
172 | return -ENOMEM; | ||
173 | cam->pdev = pdev; | ||
174 | INIT_LIST_HEAD(&cam->devlist); | ||
175 | |||
176 | mcam = &cam->mcam; | ||
177 | mcam->platform = MHP_Armada610; | ||
178 | mcam->plat_power_up = mmpcam_power_up; | ||
179 | mcam->plat_power_down = mmpcam_power_down; | ||
180 | mcam->dev = &pdev->dev; | ||
181 | mcam->use_smbus = 0; | ||
182 | mcam->chip_id = V4L2_IDENT_ARMADA610; | ||
183 | spin_lock_init(&mcam->dev_lock); | ||
184 | /* | ||
185 | * Get our I/O memory. | ||
186 | */ | ||
187 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
188 | if (res == NULL) { | ||
189 | dev_err(&pdev->dev, "no iomem resource!\n"); | ||
190 | ret = -ENODEV; | ||
191 | goto out_free; | ||
192 | } | ||
193 | mcam->regs = ioremap(res->start, resource_size(res)); | ||
194 | if (mcam->regs == NULL) { | ||
195 | dev_err(&pdev->dev, "MMIO ioremap fail\n"); | ||
196 | ret = -ENODEV; | ||
197 | goto out_free; | ||
198 | } | ||
199 | /* | ||
200 | * Power/clock memory is elsewhere; get it too. Perhaps this | ||
201 | * should really be managed outside of this driver? | ||
202 | */ | ||
203 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1); | ||
204 | if (res == NULL) { | ||
205 | dev_err(&pdev->dev, "no power resource!\n"); | ||
206 | ret = -ENODEV; | ||
207 | goto out_unmap1; | ||
208 | } | ||
209 | cam->power_regs = ioremap(res->start, resource_size(res)); | ||
210 | if (cam->power_regs == NULL) { | ||
211 | dev_err(&pdev->dev, "power MMIO ioremap fail\n"); | ||
212 | ret = -ENODEV; | ||
213 | goto out_unmap1; | ||
214 | } | ||
215 | /* | ||
216 | * Find the i2c adapter. This assumes, of course, that the | ||
217 | * i2c bus is already up and functioning. | ||
218 | */ | ||
219 | pdata = pdev->dev.platform_data; | ||
220 | mcam->i2c_adapter = platform_get_drvdata(pdata->i2c_device); | ||
221 | if (mcam->i2c_adapter == NULL) { | ||
222 | ret = -ENODEV; | ||
223 | dev_err(&pdev->dev, "No i2c adapter\n"); | ||
224 | goto out_unmap2; | ||
225 | } | ||
226 | /* | ||
227 | * Sensor GPIO pins. | ||
228 | */ | ||
229 | ret = gpio_request(pdata->sensor_power_gpio, "cam-power"); | ||
230 | if (ret) { | ||
231 | dev_err(&pdev->dev, "Can't get sensor power gpio %d", | ||
232 | pdata->sensor_power_gpio); | ||
233 | goto out_unmap2; | ||
234 | } | ||
235 | gpio_direction_output(pdata->sensor_power_gpio, 0); | ||
236 | ret = gpio_request(pdata->sensor_reset_gpio, "cam-reset"); | ||
237 | if (ret) { | ||
238 | dev_err(&pdev->dev, "Can't get sensor reset gpio %d", | ||
239 | pdata->sensor_reset_gpio); | ||
240 | goto out_gpio; | ||
241 | } | ||
242 | gpio_direction_output(pdata->sensor_reset_gpio, 0); | ||
243 | /* | ||
244 | * Power the device up and hand it off to the core. | ||
245 | */ | ||
246 | mmpcam_power_up(mcam); | ||
247 | ret = mccic_register(mcam); | ||
248 | if (ret) | ||
249 | goto out_gpio2; | ||
250 | /* | ||
251 | * Finally, set up our IRQ now that the core is ready to | ||
252 | * deal with it. | ||
253 | */ | ||
254 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
255 | if (res == NULL) { | ||
256 | ret = -ENODEV; | ||
257 | goto out_unregister; | ||
258 | } | ||
259 | cam->irq = res->start; | ||
260 | ret = request_irq(cam->irq, mmpcam_irq, IRQF_SHARED, | ||
261 | "mmp-camera", mcam); | ||
262 | if (ret == 0) { | ||
263 | mmpcam_add_device(cam); | ||
264 | return 0; | ||
265 | } | ||
266 | |||
267 | out_unregister: | ||
268 | mccic_shutdown(mcam); | ||
269 | mmpcam_power_down(mcam); | ||
270 | out_gpio2: | ||
271 | gpio_free(pdata->sensor_reset_gpio); | ||
272 | out_gpio: | ||
273 | gpio_free(pdata->sensor_power_gpio); | ||
274 | out_unmap2: | ||
275 | iounmap(cam->power_regs); | ||
276 | out_unmap1: | ||
277 | iounmap(mcam->regs); | ||
278 | out_free: | ||
279 | kfree(cam); | ||
280 | return ret; | ||
281 | } | ||
282 | |||
283 | |||
284 | static int mmpcam_remove(struct mmp_camera *cam) | ||
285 | { | ||
286 | struct mcam_camera *mcam = &cam->mcam; | ||
287 | struct mmp_camera_platform_data *pdata; | ||
288 | |||
289 | mmpcam_remove_device(cam); | ||
290 | free_irq(cam->irq, mcam); | ||
291 | mccic_shutdown(mcam); | ||
292 | mmpcam_power_down(mcam); | ||
293 | pdata = cam->pdev->dev.platform_data; | ||
294 | gpio_free(pdata->sensor_reset_gpio); | ||
295 | gpio_free(pdata->sensor_power_gpio); | ||
296 | iounmap(cam->power_regs); | ||
297 | iounmap(mcam->regs); | ||
298 | kfree(cam); | ||
299 | return 0; | ||
300 | } | ||
301 | |||
302 | static int mmpcam_platform_remove(struct platform_device *pdev) | ||
303 | { | ||
304 | struct mmp_camera *cam = mmpcam_find_device(pdev); | ||
305 | |||
306 | if (cam == NULL) | ||
307 | return -ENODEV; | ||
308 | return mmpcam_remove(cam); | ||
309 | } | ||
310 | |||
311 | |||
312 | static struct platform_driver mmpcam_driver = { | ||
313 | .probe = mmpcam_probe, | ||
314 | .remove = mmpcam_platform_remove, | ||
315 | .driver = { | ||
316 | .name = "mmp-camera", | ||
317 | .owner = THIS_MODULE | ||
318 | } | ||
319 | }; | ||
320 | |||
321 | |||
322 | static int __init mmpcam_init_module(void) | ||
323 | { | ||
324 | mutex_init(&mmpcam_devices_lock); | ||
325 | return platform_driver_register(&mmpcam_driver); | ||
326 | } | ||
327 | |||
328 | static void __exit mmpcam_exit_module(void) | ||
329 | { | ||
330 | platform_driver_unregister(&mmpcam_driver); | ||
331 | /* | ||
332 | * platform_driver_unregister() should have emptied the list | ||
333 | */ | ||
334 | if (!list_empty(&mmpcam_devices)) | ||
335 | printk(KERN_ERR "mmp_camera leaving devices behind\n"); | ||
336 | } | ||
337 | |||
338 | module_init(mmpcam_init_module); | ||
339 | module_exit(mmpcam_exit_module); | ||