diff options
author | Jonathan Corbet <corbet@lwn.net> | 2011-06-11 13:46:43 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2011-07-27 16:53:00 -0400 |
commit | abfa3df36c01a32b081fb448750181af76eb9d55 (patch) | |
tree | 65a28797a78898f5feb073068161cd708288a7ca /drivers/media/video/marvell-ccic/cafe-driver.c | |
parent | f8fc729870ee82662ae6e3a713d59b2fbf3b04c6 (diff) |
[media] marvell-cam: Separate out the Marvell camera core
There will eventually be multiple users of the core camera controller, so
separate it from the bus/platform/i2c stuff. I've tried to do the minimal
set of changes to get the driver functioning in this configuration; I did
clean up a bunch of old checkpatch gripes in the process. This driver
works like the old one did on OLPC XO 1 systems.
Cc: Daniel Drake <dsd@laptop.org>
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/cafe-driver.c')
-rw-r--r-- | drivers/media/video/marvell-ccic/cafe-driver.c | 570 |
1 files changed, 570 insertions, 0 deletions
diff --git a/drivers/media/video/marvell-ccic/cafe-driver.c b/drivers/media/video/marvell-ccic/cafe-driver.c new file mode 100644 index 000000000000..3f38f2a0643f --- /dev/null +++ b/drivers/media/video/marvell-ccic/cafe-driver.c | |||
@@ -0,0 +1,570 @@ | |||
1 | /* | ||
2 | * A driver for the CMOS camera controller in the Marvell 88ALP01 "cafe" | ||
3 | * multifunction chip. Currently works with the Omnivision OV7670 | ||
4 | * sensor. | ||
5 | * | ||
6 | * The data sheet for this device can be found at: | ||
7 | * http://www.marvell.com/products/pc_connectivity/88alp01/ | ||
8 | * | ||
9 | * Copyright 2006-11 One Laptop Per Child Association, Inc. | ||
10 | * Copyright 2006-11 Jonathan Corbet <corbet@lwn.net> | ||
11 | * | ||
12 | * Written by Jonathan Corbet, corbet@lwn.net. | ||
13 | * | ||
14 | * v4l2_device/v4l2_subdev conversion by: | ||
15 | * Copyright (C) 2009 Hans Verkuil <hverkuil@xs4all.nl> | ||
16 | * | ||
17 | * Note: this conversion is untested! Please contact the linux-media | ||
18 | * mailinglist if you can test this, together with the test results. | ||
19 | * | ||
20 | * This file may be distributed under the terms of the GNU General | ||
21 | * Public License, version 2. | ||
22 | */ | ||
23 | #include <linux/kernel.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/init.h> | ||
26 | #include <linux/pci.h> | ||
27 | #include <linux/i2c.h> | ||
28 | #include <linux/interrupt.h> | ||
29 | #include <linux/spinlock.h> | ||
30 | #include <linux/slab.h> | ||
31 | #include <linux/videodev2.h> | ||
32 | #include <media/v4l2-device.h> | ||
33 | #include <media/v4l2-chip-ident.h> | ||
34 | #include <linux/device.h> | ||
35 | #include <linux/wait.h> | ||
36 | #include <linux/delay.h> | ||
37 | #include <linux/io.h> | ||
38 | |||
39 | #include "mcam-core.h" | ||
40 | |||
41 | #define CAFE_VERSION 0x000002 | ||
42 | |||
43 | |||
44 | /* | ||
45 | * Parameters. | ||
46 | */ | ||
47 | MODULE_AUTHOR("Jonathan Corbet <corbet@lwn.net>"); | ||
48 | MODULE_DESCRIPTION("Marvell 88ALP01 CMOS Camera Controller driver"); | ||
49 | MODULE_LICENSE("GPL"); | ||
50 | MODULE_SUPPORTED_DEVICE("Video"); | ||
51 | |||
52 | |||
53 | |||
54 | |||
55 | struct cafe_camera { | ||
56 | int registered; /* Fully initialized? */ | ||
57 | struct mcam_camera mcam; | ||
58 | struct pci_dev *pdev; | ||
59 | wait_queue_head_t smbus_wait; /* Waiting on i2c events */ | ||
60 | }; | ||
61 | |||
62 | /* | ||
63 | * Debugging and related. | ||
64 | */ | ||
65 | #define cam_err(cam, fmt, arg...) \ | ||
66 | dev_err(&(cam)->pdev->dev, fmt, ##arg); | ||
67 | #define cam_warn(cam, fmt, arg...) \ | ||
68 | dev_warn(&(cam)->pdev->dev, fmt, ##arg); | ||
69 | |||
70 | /* -------------------------------------------------------------------- */ | ||
71 | /* | ||
72 | * The I2C/SMBUS interface to the camera itself starts here. The | ||
73 | * controller handles SMBUS itself, presenting a relatively simple register | ||
74 | * interface; all we have to do is to tell it where to route the data. | ||
75 | */ | ||
76 | #define CAFE_SMBUS_TIMEOUT (HZ) /* generous */ | ||
77 | |||
78 | static inline struct cafe_camera *to_cam(struct v4l2_device *dev) | ||
79 | { | ||
80 | struct mcam_camera *m = container_of(dev, struct mcam_camera, v4l2_dev); | ||
81 | return container_of(m, struct cafe_camera, mcam); | ||
82 | } | ||
83 | |||
84 | |||
85 | static int cafe_smbus_write_done(struct mcam_camera *mcam) | ||
86 | { | ||
87 | unsigned long flags; | ||
88 | int c1; | ||
89 | |||
90 | /* | ||
91 | * We must delay after the interrupt, or the controller gets confused | ||
92 | * and never does give us good status. Fortunately, we don't do this | ||
93 | * often. | ||
94 | */ | ||
95 | udelay(20); | ||
96 | spin_lock_irqsave(&mcam->dev_lock, flags); | ||
97 | c1 = mcam_reg_read(mcam, REG_TWSIC1); | ||
98 | spin_unlock_irqrestore(&mcam->dev_lock, flags); | ||
99 | return (c1 & (TWSIC1_WSTAT|TWSIC1_ERROR)) != TWSIC1_WSTAT; | ||
100 | } | ||
101 | |||
102 | static int cafe_smbus_write_data(struct cafe_camera *cam, | ||
103 | u16 addr, u8 command, u8 value) | ||
104 | { | ||
105 | unsigned int rval; | ||
106 | unsigned long flags; | ||
107 | struct mcam_camera *mcam = &cam->mcam; | ||
108 | |||
109 | spin_lock_irqsave(&mcam->dev_lock, flags); | ||
110 | rval = TWSIC0_EN | ((addr << TWSIC0_SID_SHIFT) & TWSIC0_SID); | ||
111 | rval |= TWSIC0_OVMAGIC; /* Make OV sensors work */ | ||
112 | /* | ||
113 | * Marvell sez set clkdiv to all 1's for now. | ||
114 | */ | ||
115 | rval |= TWSIC0_CLKDIV; | ||
116 | mcam_reg_write(mcam, REG_TWSIC0, rval); | ||
117 | (void) mcam_reg_read(mcam, REG_TWSIC1); /* force write */ | ||
118 | rval = value | ((command << TWSIC1_ADDR_SHIFT) & TWSIC1_ADDR); | ||
119 | mcam_reg_write(mcam, REG_TWSIC1, rval); | ||
120 | spin_unlock_irqrestore(&mcam->dev_lock, flags); | ||
121 | |||
122 | /* Unfortunately, reading TWSIC1 too soon after sending a command | ||
123 | * causes the device to die. | ||
124 | * Use a busy-wait because we often send a large quantity of small | ||
125 | * commands at-once; using msleep() would cause a lot of context | ||
126 | * switches which take longer than 2ms, resulting in a noticeable | ||
127 | * boot-time and capture-start delays. | ||
128 | */ | ||
129 | mdelay(2); | ||
130 | |||
131 | /* | ||
132 | * Another sad fact is that sometimes, commands silently complete but | ||
133 | * cafe_smbus_write_done() never becomes aware of this. | ||
134 | * This happens at random and appears to possible occur with any | ||
135 | * command. | ||
136 | * We don't understand why this is. We work around this issue | ||
137 | * with the timeout in the wait below, assuming that all commands | ||
138 | * complete within the timeout. | ||
139 | */ | ||
140 | wait_event_timeout(cam->smbus_wait, cafe_smbus_write_done(mcam), | ||
141 | CAFE_SMBUS_TIMEOUT); | ||
142 | |||
143 | spin_lock_irqsave(&mcam->dev_lock, flags); | ||
144 | rval = mcam_reg_read(mcam, REG_TWSIC1); | ||
145 | spin_unlock_irqrestore(&mcam->dev_lock, flags); | ||
146 | |||
147 | if (rval & TWSIC1_WSTAT) { | ||
148 | cam_err(cam, "SMBUS write (%02x/%02x/%02x) timed out\n", addr, | ||
149 | command, value); | ||
150 | return -EIO; | ||
151 | } | ||
152 | if (rval & TWSIC1_ERROR) { | ||
153 | cam_err(cam, "SMBUS write (%02x/%02x/%02x) error\n", addr, | ||
154 | command, value); | ||
155 | return -EIO; | ||
156 | } | ||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | |||
161 | |||
162 | static int cafe_smbus_read_done(struct mcam_camera *mcam) | ||
163 | { | ||
164 | unsigned long flags; | ||
165 | int c1; | ||
166 | |||
167 | /* | ||
168 | * We must delay after the interrupt, or the controller gets confused | ||
169 | * and never does give us good status. Fortunately, we don't do this | ||
170 | * often. | ||
171 | */ | ||
172 | udelay(20); | ||
173 | spin_lock_irqsave(&mcam->dev_lock, flags); | ||
174 | c1 = mcam_reg_read(mcam, REG_TWSIC1); | ||
175 | spin_unlock_irqrestore(&mcam->dev_lock, flags); | ||
176 | return c1 & (TWSIC1_RVALID|TWSIC1_ERROR); | ||
177 | } | ||
178 | |||
179 | |||
180 | |||
181 | static int cafe_smbus_read_data(struct cafe_camera *cam, | ||
182 | u16 addr, u8 command, u8 *value) | ||
183 | { | ||
184 | unsigned int rval; | ||
185 | unsigned long flags; | ||
186 | struct mcam_camera *mcam = &cam->mcam; | ||
187 | |||
188 | spin_lock_irqsave(&mcam->dev_lock, flags); | ||
189 | rval = TWSIC0_EN | ((addr << TWSIC0_SID_SHIFT) & TWSIC0_SID); | ||
190 | rval |= TWSIC0_OVMAGIC; /* Make OV sensors work */ | ||
191 | /* | ||
192 | * Marvel sez set clkdiv to all 1's for now. | ||
193 | */ | ||
194 | rval |= TWSIC0_CLKDIV; | ||
195 | mcam_reg_write(mcam, REG_TWSIC0, rval); | ||
196 | (void) mcam_reg_read(mcam, REG_TWSIC1); /* force write */ | ||
197 | rval = TWSIC1_READ | ((command << TWSIC1_ADDR_SHIFT) & TWSIC1_ADDR); | ||
198 | mcam_reg_write(mcam, REG_TWSIC1, rval); | ||
199 | spin_unlock_irqrestore(&mcam->dev_lock, flags); | ||
200 | |||
201 | wait_event_timeout(cam->smbus_wait, | ||
202 | cafe_smbus_read_done(mcam), CAFE_SMBUS_TIMEOUT); | ||
203 | spin_lock_irqsave(&mcam->dev_lock, flags); | ||
204 | rval = mcam_reg_read(mcam, REG_TWSIC1); | ||
205 | spin_unlock_irqrestore(&mcam->dev_lock, flags); | ||
206 | |||
207 | if (rval & TWSIC1_ERROR) { | ||
208 | cam_err(cam, "SMBUS read (%02x/%02x) error\n", addr, command); | ||
209 | return -EIO; | ||
210 | } | ||
211 | if (!(rval & TWSIC1_RVALID)) { | ||
212 | cam_err(cam, "SMBUS read (%02x/%02x) timed out\n", addr, | ||
213 | command); | ||
214 | return -EIO; | ||
215 | } | ||
216 | *value = rval & 0xff; | ||
217 | return 0; | ||
218 | } | ||
219 | |||
220 | /* | ||
221 | * Perform a transfer over SMBUS. This thing is called under | ||
222 | * the i2c bus lock, so we shouldn't race with ourselves... | ||
223 | */ | ||
224 | static int cafe_smbus_xfer(struct i2c_adapter *adapter, u16 addr, | ||
225 | unsigned short flags, char rw, u8 command, | ||
226 | int size, union i2c_smbus_data *data) | ||
227 | { | ||
228 | struct cafe_camera *cam = i2c_get_adapdata(adapter); | ||
229 | int ret = -EINVAL; | ||
230 | |||
231 | /* | ||
232 | * This interface would appear to only do byte data ops. OK | ||
233 | * it can do word too, but the cam chip has no use for that. | ||
234 | */ | ||
235 | if (size != I2C_SMBUS_BYTE_DATA) { | ||
236 | cam_err(cam, "funky xfer size %d\n", size); | ||
237 | return -EINVAL; | ||
238 | } | ||
239 | |||
240 | if (rw == I2C_SMBUS_WRITE) | ||
241 | ret = cafe_smbus_write_data(cam, addr, command, data->byte); | ||
242 | else if (rw == I2C_SMBUS_READ) | ||
243 | ret = cafe_smbus_read_data(cam, addr, command, &data->byte); | ||
244 | return ret; | ||
245 | } | ||
246 | |||
247 | |||
248 | static void cafe_smbus_enable_irq(struct cafe_camera *cam) | ||
249 | { | ||
250 | unsigned long flags; | ||
251 | |||
252 | spin_lock_irqsave(&cam->mcam.dev_lock, flags); | ||
253 | mcam_reg_set_bit(&cam->mcam, REG_IRQMASK, TWSIIRQS); | ||
254 | spin_unlock_irqrestore(&cam->mcam.dev_lock, flags); | ||
255 | } | ||
256 | |||
257 | static u32 cafe_smbus_func(struct i2c_adapter *adapter) | ||
258 | { | ||
259 | return I2C_FUNC_SMBUS_READ_BYTE_DATA | | ||
260 | I2C_FUNC_SMBUS_WRITE_BYTE_DATA; | ||
261 | } | ||
262 | |||
263 | static struct i2c_algorithm cafe_smbus_algo = { | ||
264 | .smbus_xfer = cafe_smbus_xfer, | ||
265 | .functionality = cafe_smbus_func | ||
266 | }; | ||
267 | |||
268 | static int cafe_smbus_setup(struct cafe_camera *cam) | ||
269 | { | ||
270 | struct i2c_adapter *adap = &cam->mcam.i2c_adapter; | ||
271 | int ret; | ||
272 | |||
273 | cafe_smbus_enable_irq(cam); | ||
274 | adap->owner = THIS_MODULE; | ||
275 | adap->algo = &cafe_smbus_algo; | ||
276 | strcpy(adap->name, "cafe_ccic"); | ||
277 | adap->dev.parent = &cam->pdev->dev; | ||
278 | i2c_set_adapdata(adap, cam); | ||
279 | ret = i2c_add_adapter(adap); | ||
280 | if (ret) | ||
281 | printk(KERN_ERR "Unable to register cafe i2c adapter\n"); | ||
282 | return ret; | ||
283 | } | ||
284 | |||
285 | static void cafe_smbus_shutdown(struct cafe_camera *cam) | ||
286 | { | ||
287 | i2c_del_adapter(&cam->mcam.i2c_adapter); | ||
288 | } | ||
289 | |||
290 | |||
291 | /* | ||
292 | * Controller-level stuff | ||
293 | */ | ||
294 | |||
295 | static void cafe_ctlr_init(struct mcam_camera *mcam) | ||
296 | { | ||
297 | unsigned long flags; | ||
298 | |||
299 | spin_lock_irqsave(&mcam->dev_lock, flags); | ||
300 | /* | ||
301 | * Added magic to bring up the hardware on the B-Test board | ||
302 | */ | ||
303 | mcam_reg_write(mcam, 0x3038, 0x8); | ||
304 | mcam_reg_write(mcam, 0x315c, 0x80008); | ||
305 | /* | ||
306 | * Go through the dance needed to wake the device up. | ||
307 | * Note that these registers are global and shared | ||
308 | * with the NAND and SD devices. Interaction between the | ||
309 | * three still needs to be examined. | ||
310 | */ | ||
311 | mcam_reg_write(mcam, REG_GL_CSR, GCSR_SRS|GCSR_MRS); /* Needed? */ | ||
312 | mcam_reg_write(mcam, REG_GL_CSR, GCSR_SRC|GCSR_MRC); | ||
313 | mcam_reg_write(mcam, REG_GL_CSR, GCSR_SRC|GCSR_MRS); | ||
314 | /* | ||
315 | * Here we must wait a bit for the controller to come around. | ||
316 | */ | ||
317 | spin_unlock_irqrestore(&mcam->dev_lock, flags); | ||
318 | msleep(5); | ||
319 | spin_lock_irqsave(&mcam->dev_lock, flags); | ||
320 | |||
321 | mcam_reg_write(mcam, REG_GL_CSR, GCSR_CCIC_EN|GCSR_SRC|GCSR_MRC); | ||
322 | mcam_reg_set_bit(mcam, REG_GL_IMASK, GIMSK_CCIC_EN); | ||
323 | /* | ||
324 | * Mask all interrupts. | ||
325 | */ | ||
326 | mcam_reg_write(mcam, REG_IRQMASK, 0); | ||
327 | spin_unlock_irqrestore(&mcam->dev_lock, flags); | ||
328 | } | ||
329 | |||
330 | |||
331 | static void cafe_ctlr_power_up(struct mcam_camera *mcam) | ||
332 | { | ||
333 | /* | ||
334 | * Part one of the sensor dance: turn the global | ||
335 | * GPIO signal on. | ||
336 | */ | ||
337 | mcam_reg_write(mcam, REG_GL_FCR, GFCR_GPIO_ON); | ||
338 | mcam_reg_write(mcam, REG_GL_GPIOR, GGPIO_OUT|GGPIO_VAL); | ||
339 | /* | ||
340 | * Put the sensor into operational mode (assumes OLPC-style | ||
341 | * wiring). Control 0 is reset - set to 1 to operate. | ||
342 | * Control 1 is power down, set to 0 to operate. | ||
343 | */ | ||
344 | mcam_reg_write(mcam, REG_GPR, GPR_C1EN|GPR_C0EN); /* pwr up, reset */ | ||
345 | mcam_reg_write(mcam, REG_GPR, GPR_C1EN|GPR_C0EN|GPR_C0); | ||
346 | } | ||
347 | |||
348 | static void cafe_ctlr_power_down(struct mcam_camera *mcam) | ||
349 | { | ||
350 | mcam_reg_write(mcam, REG_GPR, GPR_C1EN|GPR_C0EN|GPR_C1); | ||
351 | mcam_reg_write(mcam, REG_GL_FCR, GFCR_GPIO_ON); | ||
352 | mcam_reg_write(mcam, REG_GL_GPIOR, GGPIO_OUT); | ||
353 | } | ||
354 | |||
355 | |||
356 | |||
357 | /* | ||
358 | * The platform interrupt handler. | ||
359 | */ | ||
360 | static irqreturn_t cafe_irq(int irq, void *data) | ||
361 | { | ||
362 | struct cafe_camera *cam = data; | ||
363 | struct mcam_camera *mcam = &cam->mcam; | ||
364 | unsigned int irqs, handled; | ||
365 | |||
366 | spin_lock(&mcam->dev_lock); | ||
367 | irqs = mcam_reg_read(mcam, REG_IRQSTAT); | ||
368 | handled = cam->registered && mccic_irq(mcam, irqs); | ||
369 | if (irqs & TWSIIRQS) { | ||
370 | mcam_reg_write(mcam, REG_IRQSTAT, TWSIIRQS); | ||
371 | wake_up(&cam->smbus_wait); | ||
372 | handled = 1; | ||
373 | } | ||
374 | spin_unlock(&mcam->dev_lock); | ||
375 | return IRQ_RETVAL(handled); | ||
376 | } | ||
377 | |||
378 | |||
379 | /* -------------------------------------------------------------------------- */ | ||
380 | /* | ||
381 | * PCI interface stuff. | ||
382 | */ | ||
383 | |||
384 | static int cafe_pci_probe(struct pci_dev *pdev, | ||
385 | const struct pci_device_id *id) | ||
386 | { | ||
387 | int ret; | ||
388 | struct cafe_camera *cam; | ||
389 | struct mcam_camera *mcam; | ||
390 | |||
391 | /* | ||
392 | * Start putting together one of our big camera structures. | ||
393 | */ | ||
394 | ret = -ENOMEM; | ||
395 | cam = kzalloc(sizeof(struct cafe_camera), GFP_KERNEL); | ||
396 | if (cam == NULL) | ||
397 | goto out; | ||
398 | cam->pdev = pdev; | ||
399 | mcam = &cam->mcam; | ||
400 | mcam->chip_id = V4L2_IDENT_CAFE; | ||
401 | spin_lock_init(&mcam->dev_lock); | ||
402 | init_waitqueue_head(&cam->smbus_wait); | ||
403 | mcam->plat_power_up = cafe_ctlr_power_up; | ||
404 | mcam->plat_power_down = cafe_ctlr_power_down; | ||
405 | mcam->dev = &pdev->dev; | ||
406 | /* | ||
407 | * Get set up on the PCI bus. | ||
408 | */ | ||
409 | ret = pci_enable_device(pdev); | ||
410 | if (ret) | ||
411 | goto out_free; | ||
412 | pci_set_master(pdev); | ||
413 | |||
414 | ret = -EIO; | ||
415 | mcam->regs = pci_iomap(pdev, 0, 0); | ||
416 | if (!mcam->regs) { | ||
417 | printk(KERN_ERR "Unable to ioremap cafe-ccic regs\n"); | ||
418 | goto out_disable; | ||
419 | } | ||
420 | ret = request_irq(pdev->irq, cafe_irq, IRQF_SHARED, "cafe-ccic", cam); | ||
421 | if (ret) | ||
422 | goto out_iounmap; | ||
423 | |||
424 | /* | ||
425 | * Initialize the controller and leave it powered up. It will | ||
426 | * stay that way until the sensor driver shows up. | ||
427 | */ | ||
428 | cafe_ctlr_init(mcam); | ||
429 | cafe_ctlr_power_up(mcam); | ||
430 | /* | ||
431 | * Set up I2C/SMBUS communications. We have to drop the mutex here | ||
432 | * because the sensor could attach in this call chain, leading to | ||
433 | * unsightly deadlocks. | ||
434 | */ | ||
435 | ret = cafe_smbus_setup(cam); | ||
436 | if (ret) | ||
437 | goto out_pdown; | ||
438 | |||
439 | ret = mccic_register(mcam); | ||
440 | if (ret == 0) { | ||
441 | cam->registered = 1; | ||
442 | return 0; | ||
443 | } | ||
444 | |||
445 | cafe_smbus_shutdown(cam); | ||
446 | out_pdown: | ||
447 | cafe_ctlr_power_down(mcam); | ||
448 | free_irq(pdev->irq, cam); | ||
449 | out_iounmap: | ||
450 | pci_iounmap(pdev, mcam->regs); | ||
451 | out_disable: | ||
452 | pci_disable_device(pdev); | ||
453 | out_free: | ||
454 | kfree(cam); | ||
455 | out: | ||
456 | return ret; | ||
457 | } | ||
458 | |||
459 | |||
460 | /* | ||
461 | * Shut down an initialized device | ||
462 | */ | ||
463 | static void cafe_shutdown(struct cafe_camera *cam) | ||
464 | { | ||
465 | mccic_shutdown(&cam->mcam); | ||
466 | cafe_smbus_shutdown(cam); | ||
467 | free_irq(cam->pdev->irq, cam); | ||
468 | pci_iounmap(cam->pdev, cam->mcam.regs); | ||
469 | } | ||
470 | |||
471 | |||
472 | static void cafe_pci_remove(struct pci_dev *pdev) | ||
473 | { | ||
474 | struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev); | ||
475 | struct cafe_camera *cam = to_cam(v4l2_dev); | ||
476 | |||
477 | if (cam == NULL) { | ||
478 | printk(KERN_WARNING "pci_remove on unknown pdev %p\n", pdev); | ||
479 | return; | ||
480 | } | ||
481 | cafe_shutdown(cam); | ||
482 | kfree(cam); | ||
483 | } | ||
484 | |||
485 | |||
486 | #ifdef CONFIG_PM | ||
487 | /* | ||
488 | * Basic power management. | ||
489 | */ | ||
490 | static int cafe_pci_suspend(struct pci_dev *pdev, pm_message_t state) | ||
491 | { | ||
492 | struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev); | ||
493 | struct cafe_camera *cam = to_cam(v4l2_dev); | ||
494 | int ret; | ||
495 | |||
496 | ret = pci_save_state(pdev); | ||
497 | if (ret) | ||
498 | return ret; | ||
499 | mccic_suspend(&cam->mcam); | ||
500 | pci_disable_device(pdev); | ||
501 | return 0; | ||
502 | } | ||
503 | |||
504 | |||
505 | static int cafe_pci_resume(struct pci_dev *pdev) | ||
506 | { | ||
507 | struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev); | ||
508 | struct cafe_camera *cam = to_cam(v4l2_dev); | ||
509 | int ret = 0; | ||
510 | |||
511 | pci_restore_state(pdev); | ||
512 | ret = pci_enable_device(pdev); | ||
513 | |||
514 | if (ret) { | ||
515 | cam_warn(cam, "Unable to re-enable device on resume!\n"); | ||
516 | return ret; | ||
517 | } | ||
518 | cafe_ctlr_init(&cam->mcam); | ||
519 | return mccic_resume(&cam->mcam); | ||
520 | } | ||
521 | |||
522 | #endif /* CONFIG_PM */ | ||
523 | |||
524 | static struct pci_device_id cafe_ids[] = { | ||
525 | { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, | ||
526 | PCI_DEVICE_ID_MARVELL_88ALP01_CCIC) }, | ||
527 | { 0, } | ||
528 | }; | ||
529 | |||
530 | MODULE_DEVICE_TABLE(pci, cafe_ids); | ||
531 | |||
532 | static struct pci_driver cafe_pci_driver = { | ||
533 | .name = "cafe1000-ccic", | ||
534 | .id_table = cafe_ids, | ||
535 | .probe = cafe_pci_probe, | ||
536 | .remove = cafe_pci_remove, | ||
537 | #ifdef CONFIG_PM | ||
538 | .suspend = cafe_pci_suspend, | ||
539 | .resume = cafe_pci_resume, | ||
540 | #endif | ||
541 | }; | ||
542 | |||
543 | |||
544 | |||
545 | |||
546 | static int __init cafe_init(void) | ||
547 | { | ||
548 | int ret; | ||
549 | |||
550 | printk(KERN_NOTICE "Marvell M88ALP01 'CAFE' Camera Controller version %d\n", | ||
551 | CAFE_VERSION); | ||
552 | ret = pci_register_driver(&cafe_pci_driver); | ||
553 | if (ret) { | ||
554 | printk(KERN_ERR "Unable to register cafe_ccic driver\n"); | ||
555 | goto out; | ||
556 | } | ||
557 | ret = 0; | ||
558 | |||
559 | out: | ||
560 | return ret; | ||
561 | } | ||
562 | |||
563 | |||
564 | static void __exit cafe_exit(void) | ||
565 | { | ||
566 | pci_unregister_driver(&cafe_pci_driver); | ||
567 | } | ||
568 | |||
569 | module_init(cafe_init); | ||
570 | module_exit(cafe_exit); | ||