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/video/marvell-ccic/mmp-driver.c | |
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/video/marvell-ccic/mmp-driver.c')
-rw-r--r-- | drivers/media/video/marvell-ccic/mmp-driver.c | 339 |
1 files changed, 339 insertions, 0 deletions
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 000000000000..ac9976f23a61 --- /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); | ||