Commit 67a8dbbc4e04cd256987b189352472a59aff73be
Committed by
Mauro Carvalho Chehab
1 parent
595a93a47a
Exists in
master
and in
4 other branches
[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>
Showing 7 changed files with 386 additions and 9 deletions Side-by-side Diff
drivers/media/video/Makefile
drivers/media/video/marvell-ccic/Kconfig
... | ... | @@ -6,4 +6,15 @@ |
6 | 6 | This is a video4linux2 driver for the Marvell 88ALP01 integrated |
7 | 7 | CMOS camera controller. This is the controller found on first- |
8 | 8 | generation OLPC systems. |
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. |
drivers/media/video/marvell-ccic/Makefile
drivers/media/video/marvell-ccic/mcam-core.c
... | ... | @@ -167,7 +167,7 @@ |
167 | 167 | |
168 | 168 | |
169 | 169 | /* |
170 | - * Debugging and related. FIXME these are broken | |
170 | + * Debugging and related. | |
171 | 171 | */ |
172 | 172 | #define cam_err(cam, fmt, arg...) \ |
173 | 173 | dev_err((cam)->dev, fmt, ##arg); |
... | ... | @@ -202,7 +202,8 @@ |
202 | 202 | mcam_reg_clear_bit(cam, REG_CTRL1, C1_TWOBUFS); |
203 | 203 | } else |
204 | 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 | 209 | static void mcam_ctlr_image(struct mcam_camera *cam) |
209 | 210 | |
... | ... | @@ -358,8 +359,8 @@ |
358 | 359 | unsigned long flags; |
359 | 360 | |
360 | 361 | spin_lock_irqsave(&cam->dev_lock, flags); |
361 | - mcam_reg_clear_bit(cam, REG_CTRL1, C1_PWRDWN); | |
362 | 362 | cam->plat_power_up(cam); |
363 | + mcam_reg_clear_bit(cam, REG_CTRL1, C1_PWRDWN); | |
363 | 364 | spin_unlock_irqrestore(&cam->dev_lock, flags); |
364 | 365 | msleep(5); /* Just to be sure */ |
365 | 366 | } |
366 | 367 | |
... | ... | @@ -369,8 +370,13 @@ |
369 | 370 | unsigned long flags; |
370 | 371 | |
371 | 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 | 378 | mcam_reg_set_bit(cam, REG_CTRL1, C1_PWRDWN); |
379 | + cam->plat_power_down(cam); | |
374 | 380 | spin_unlock_irqrestore(&cam->dev_lock, flags); |
375 | 381 | } |
376 | 382 | |
377 | 383 | |
378 | 384 | |
... | ... | @@ -1622,14 +1628,20 @@ |
1622 | 1628 | |
1623 | 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 | 1638 | cam_warn(cam, "Removing a device with users!\n"); |
1639 | + mcam_ctlr_power_down(cam); | |
1640 | + } | |
1641 | + mcam_free_dma_bufs(cam); | |
1627 | 1642 | if (cam->n_sbufs > 0) |
1628 | 1643 | /* What if they are still mapped? Shouldn't be, but... */ |
1629 | 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 | 1645 | video_unregister_device(&cam->vdev); |
1634 | 1646 | v4l2_device_unregister(&cam->v4l2_dev); |
1635 | 1647 | } |
drivers/media/video/marvell-ccic/mmp-driver.c
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); |
include/media/mmp-camera.h
include/media/v4l2-chip-ident.h
... | ... | @@ -185,8 +185,9 @@ |
185 | 185 | /* module wm8775: just ident 8775 */ |
186 | 186 | V4L2_IDENT_WM8775 = 8775, |
187 | 187 | |
188 | - /* module cafe_ccic, just ident 8801 */ | |
188 | + /* Marvell controllers starting at 8801 */ | |
189 | 189 | V4L2_IDENT_CAFE = 8801, |
190 | + V4L2_IDENT_ARMADA610 = 8802, | |
190 | 191 | |
191 | 192 | /* AKM AK8813/AK8814 */ |
192 | 193 | V4L2_IDENT_AK8813 = 8813, |