diff options
author | Guennadi Liakhovetski <g.liakhovetski@gmx.de> | 2010-07-26 10:12:43 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2010-08-02 15:43:39 -0400 |
commit | 077e2c10c9cb618d571bf16475db696610bdb24a (patch) | |
tree | e4954cf7e9dd91e7f773bab801fc80f1410de515 /drivers/media | |
parent | 52d268a36246ee4156cc719036522616bb4d73fa (diff) |
V4L/DVB: V4L2: soc-camera: add a MIPI CSI-2 driver for SH-Mobile platforms
Some SH-Mobile SoCs implement a MIPI CSI-2 controller, that can interface to
several video clients and send data to the CEU or to the Image Signal
Processor. This patch implements a v4l2-subdevice driver for CSI-2 to be used
within the soc-camera framework, implementing the second subdevice in addition
to the actual video clients.
Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/video/Kconfig | 6 | ||||
-rw-r--r-- | drivers/media/video/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/video/sh_mobile_csi2.c | 354 |
3 files changed, 361 insertions, 0 deletions
diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index c627f776c1e9..0f1ac401dedd 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig | |||
@@ -876,6 +876,12 @@ config VIDEO_PXA27x | |||
876 | ---help--- | 876 | ---help--- |
877 | This is a v4l2 driver for the PXA27x Quick Capture Interface | 877 | This is a v4l2 driver for the PXA27x Quick Capture Interface |
878 | 878 | ||
879 | config VIDEO_SH_MOBILE_CSI2 | ||
880 | tristate "SuperH Mobile MIPI CSI-2 Interface driver" | ||
881 | depends on VIDEO_DEV && SOC_CAMERA && HAVE_CLK | ||
882 | ---help--- | ||
883 | This is a v4l2 driver for the SuperH MIPI CSI-2 Interface | ||
884 | |||
879 | config VIDEO_SH_MOBILE_CEU | 885 | config VIDEO_SH_MOBILE_CEU |
880 | tristate "SuperH Mobile CEU Interface driver" | 886 | tristate "SuperH Mobile CEU Interface driver" |
881 | depends on VIDEO_DEV && SOC_CAMERA && HAS_DMA && HAVE_CLK | 887 | depends on VIDEO_DEV && SOC_CAMERA && HAS_DMA && HAVE_CLK |
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index eba259fbf7ef..88478630e854 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile | |||
@@ -160,6 +160,7 @@ obj-$(CONFIG_SOC_CAMERA_PLATFORM) += soc_camera_platform.o | |||
160 | obj-$(CONFIG_VIDEO_MX1) += mx1_camera.o | 160 | obj-$(CONFIG_VIDEO_MX1) += mx1_camera.o |
161 | obj-$(CONFIG_VIDEO_MX3) += mx3_camera.o | 161 | obj-$(CONFIG_VIDEO_MX3) += mx3_camera.o |
162 | obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o | 162 | obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o |
163 | obj-$(CONFIG_VIDEO_SH_MOBILE_CSI2) += sh_mobile_csi2.o | ||
163 | obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o | 164 | obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o |
164 | 165 | ||
165 | obj-$(CONFIG_ARCH_DAVINCI) += davinci/ | 166 | obj-$(CONFIG_ARCH_DAVINCI) += davinci/ |
diff --git a/drivers/media/video/sh_mobile_csi2.c b/drivers/media/video/sh_mobile_csi2.c new file mode 100644 index 000000000000..84a646819318 --- /dev/null +++ b/drivers/media/video/sh_mobile_csi2.c | |||
@@ -0,0 +1,354 @@ | |||
1 | /* | ||
2 | * Driver for the SH-Mobile MIPI CSI-2 unit | ||
3 | * | ||
4 | * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | |||
11 | #include <linux/delay.h> | ||
12 | #include <linux/i2c.h> | ||
13 | #include <linux/io.h> | ||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/pm_runtime.h> | ||
16 | #include <linux/slab.h> | ||
17 | #include <linux/videodev2.h> | ||
18 | |||
19 | #include <media/sh_mobile_csi2.h> | ||
20 | #include <media/soc_camera.h> | ||
21 | #include <media/v4l2-common.h> | ||
22 | #include <media/v4l2-dev.h> | ||
23 | #include <media/v4l2-device.h> | ||
24 | #include <media/v4l2-mediabus.h> | ||
25 | #include <media/v4l2-subdev.h> | ||
26 | |||
27 | #define SH_CSI2_TREF 0x00 | ||
28 | #define SH_CSI2_SRST 0x04 | ||
29 | #define SH_CSI2_PHYCNT 0x08 | ||
30 | #define SH_CSI2_CHKSUM 0x0C | ||
31 | #define SH_CSI2_VCDT 0x10 | ||
32 | |||
33 | struct sh_csi2 { | ||
34 | struct v4l2_subdev subdev; | ||
35 | struct list_head list; | ||
36 | struct notifier_block notifier; | ||
37 | unsigned int irq; | ||
38 | void __iomem *base; | ||
39 | struct platform_device *pdev; | ||
40 | struct sh_csi2_client_config *client; | ||
41 | }; | ||
42 | |||
43 | static int sh_csi2_try_fmt(struct v4l2_subdev *sd, | ||
44 | struct v4l2_mbus_framefmt *mf) | ||
45 | { | ||
46 | struct sh_csi2 *priv = container_of(sd, struct sh_csi2, subdev); | ||
47 | struct sh_csi2_pdata *pdata = priv->pdev->dev.platform_data; | ||
48 | |||
49 | if (mf->width > 8188) | ||
50 | mf->width = 8188; | ||
51 | else if (mf->width & 1) | ||
52 | mf->width &= ~1; | ||
53 | |||
54 | switch (pdata->type) { | ||
55 | case SH_CSI2C: | ||
56 | switch (mf->code) { | ||
57 | case V4L2_MBUS_FMT_UYVY8_2X8: /* YUV422 */ | ||
58 | case V4L2_MBUS_FMT_YUYV8_1_5X8: /* YUV420 */ | ||
59 | case V4L2_MBUS_FMT_GREY8_1X8: /* RAW8 */ | ||
60 | case V4L2_MBUS_FMT_SBGGR8_1X8: | ||
61 | case V4L2_MBUS_FMT_SGRBG8_1X8: | ||
62 | break; | ||
63 | default: | ||
64 | /* All MIPI CSI-2 devices must support one of primary formats */ | ||
65 | mf->code = V4L2_MBUS_FMT_YUYV8_2X8; | ||
66 | } | ||
67 | break; | ||
68 | case SH_CSI2I: | ||
69 | switch (mf->code) { | ||
70 | case V4L2_MBUS_FMT_GREY8_1X8: /* RAW8 */ | ||
71 | case V4L2_MBUS_FMT_SBGGR8_1X8: | ||
72 | case V4L2_MBUS_FMT_SGRBG8_1X8: | ||
73 | case V4L2_MBUS_FMT_SBGGR10_1X10: /* RAW10 */ | ||
74 | case V4L2_MBUS_FMT_SBGGR12_1X12: /* RAW12 */ | ||
75 | break; | ||
76 | default: | ||
77 | /* All MIPI CSI-2 devices must support one of primary formats */ | ||
78 | mf->code = V4L2_MBUS_FMT_SBGGR8_1X8; | ||
79 | } | ||
80 | break; | ||
81 | } | ||
82 | |||
83 | return 0; | ||
84 | } | ||
85 | |||
86 | /* | ||
87 | * We have done our best in try_fmt to try and tell the sensor, which formats | ||
88 | * we support. If now the configuration is unsuitable for us we can only | ||
89 | * error out. | ||
90 | */ | ||
91 | static int sh_csi2_s_fmt(struct v4l2_subdev *sd, | ||
92 | struct v4l2_mbus_framefmt *mf) | ||
93 | { | ||
94 | struct sh_csi2 *priv = container_of(sd, struct sh_csi2, subdev); | ||
95 | u32 tmp = (priv->client->channel & 3) << 8; | ||
96 | |||
97 | dev_dbg(sd->v4l2_dev->dev, "%s(%u)\n", __func__, mf->code); | ||
98 | if (mf->width > 8188 || mf->width & 1) | ||
99 | return -EINVAL; | ||
100 | |||
101 | switch (mf->code) { | ||
102 | case V4L2_MBUS_FMT_UYVY8_2X8: | ||
103 | tmp |= 0x1e; /* YUV422 8 bit */ | ||
104 | break; | ||
105 | case V4L2_MBUS_FMT_YUYV8_1_5X8: | ||
106 | tmp |= 0x18; /* YUV420 8 bit */ | ||
107 | break; | ||
108 | case V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE: | ||
109 | tmp |= 0x21; /* RGB555 */ | ||
110 | break; | ||
111 | case V4L2_MBUS_FMT_RGB565_2X8_BE: | ||
112 | tmp |= 0x22; /* RGB565 */ | ||
113 | break; | ||
114 | case V4L2_MBUS_FMT_GREY8_1X8: | ||
115 | case V4L2_MBUS_FMT_SBGGR8_1X8: | ||
116 | case V4L2_MBUS_FMT_SGRBG8_1X8: | ||
117 | tmp |= 0x2a; /* RAW8 */ | ||
118 | break; | ||
119 | default: | ||
120 | return -EINVAL; | ||
121 | } | ||
122 | |||
123 | iowrite32(tmp, priv->base + SH_CSI2_VCDT); | ||
124 | |||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | static struct v4l2_subdev_video_ops sh_csi2_subdev_video_ops = { | ||
129 | .s_mbus_fmt = sh_csi2_s_fmt, | ||
130 | .try_mbus_fmt = sh_csi2_try_fmt, | ||
131 | }; | ||
132 | |||
133 | static struct v4l2_subdev_core_ops sh_csi2_subdev_core_ops; | ||
134 | |||
135 | static struct v4l2_subdev_ops sh_csi2_subdev_ops = { | ||
136 | .core = &sh_csi2_subdev_core_ops, | ||
137 | .video = &sh_csi2_subdev_video_ops, | ||
138 | }; | ||
139 | |||
140 | static void sh_csi2_hwinit(struct sh_csi2 *priv) | ||
141 | { | ||
142 | struct sh_csi2_pdata *pdata = priv->pdev->dev.platform_data; | ||
143 | __u32 tmp = 0x10; /* Enable MIPI CSI clock lane */ | ||
144 | |||
145 | /* Reflect registers immediately */ | ||
146 | iowrite32(0x00000001, priv->base + SH_CSI2_TREF); | ||
147 | /* reset CSI2 harware */ | ||
148 | iowrite32(0x00000001, priv->base + SH_CSI2_SRST); | ||
149 | udelay(5); | ||
150 | iowrite32(0x00000000, priv->base + SH_CSI2_SRST); | ||
151 | |||
152 | if (priv->client->lanes & 3) | ||
153 | tmp |= priv->client->lanes & 3; | ||
154 | else | ||
155 | /* Default - both lanes */ | ||
156 | tmp |= 3; | ||
157 | |||
158 | if (priv->client->phy == SH_CSI2_PHY_MAIN) | ||
159 | tmp |= 0x8000; | ||
160 | |||
161 | iowrite32(tmp, priv->base + SH_CSI2_PHYCNT); | ||
162 | |||
163 | tmp = 0; | ||
164 | if (pdata->flags & SH_CSI2_ECC) | ||
165 | tmp |= 2; | ||
166 | if (pdata->flags & SH_CSI2_CRC) | ||
167 | tmp |= 1; | ||
168 | iowrite32(tmp, priv->base + SH_CSI2_CHKSUM); | ||
169 | } | ||
170 | |||
171 | static int sh_csi2_set_bus_param(struct soc_camera_device *icd, | ||
172 | unsigned long flags) | ||
173 | { | ||
174 | return 0; | ||
175 | } | ||
176 | |||
177 | static unsigned long sh_csi2_query_bus_param(struct soc_camera_device *icd) | ||
178 | { | ||
179 | struct soc_camera_link *icl = to_soc_camera_link(icd); | ||
180 | const unsigned long flags = SOCAM_PCLK_SAMPLE_RISING | | ||
181 | SOCAM_HSYNC_ACTIVE_HIGH | SOCAM_VSYNC_ACTIVE_HIGH | | ||
182 | SOCAM_MASTER | SOCAM_DATAWIDTH_8 | SOCAM_DATA_ACTIVE_HIGH; | ||
183 | |||
184 | return soc_camera_apply_sensor_flags(icl, flags); | ||
185 | } | ||
186 | |||
187 | static int sh_csi2_notify(struct notifier_block *nb, | ||
188 | unsigned long action, void *data) | ||
189 | { | ||
190 | struct device *dev = data; | ||
191 | struct soc_camera_device *icd = to_soc_camera_dev(dev); | ||
192 | struct v4l2_device *v4l2_dev = dev_get_drvdata(dev->parent); | ||
193 | struct sh_csi2 *priv = | ||
194 | container_of(nb, struct sh_csi2, notifier); | ||
195 | struct sh_csi2_pdata *pdata = priv->pdev->dev.platform_data; | ||
196 | int ret, i; | ||
197 | |||
198 | for (i = 0; i < pdata->num_clients; i++) | ||
199 | if (&pdata->clients[i].pdev->dev == icd->pdev) | ||
200 | break; | ||
201 | |||
202 | dev_dbg(dev, "%s(%p): action = %lu, found #%d\n", __func__, dev, action, i); | ||
203 | |||
204 | if (i == pdata->num_clients) | ||
205 | return NOTIFY_DONE; | ||
206 | |||
207 | switch (action) { | ||
208 | case BUS_NOTIFY_BOUND_DRIVER: | ||
209 | snprintf(priv->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s%s", | ||
210 | dev_name(v4l2_dev->dev), ".mipi-csi"); | ||
211 | ret = v4l2_device_register_subdev(v4l2_dev, &priv->subdev); | ||
212 | dev_dbg(dev, "%s(%p): ret(register_subdev) = %d\n", __func__, priv, ret); | ||
213 | if (ret < 0) | ||
214 | return NOTIFY_DONE; | ||
215 | |||
216 | priv->client = pdata->clients + i; | ||
217 | |||
218 | icd->ops->set_bus_param = sh_csi2_set_bus_param; | ||
219 | icd->ops->query_bus_param = sh_csi2_query_bus_param; | ||
220 | |||
221 | pm_runtime_get_sync(v4l2_get_subdevdata(&priv->subdev)); | ||
222 | |||
223 | sh_csi2_hwinit(priv); | ||
224 | break; | ||
225 | case BUS_NOTIFY_UNBIND_DRIVER: | ||
226 | priv->client = NULL; | ||
227 | |||
228 | /* Driver is about to be unbound */ | ||
229 | icd->ops->set_bus_param = NULL; | ||
230 | icd->ops->query_bus_param = NULL; | ||
231 | |||
232 | v4l2_device_unregister_subdev(&priv->subdev); | ||
233 | |||
234 | pm_runtime_put(v4l2_get_subdevdata(&priv->subdev)); | ||
235 | break; | ||
236 | } | ||
237 | |||
238 | return NOTIFY_OK; | ||
239 | } | ||
240 | |||
241 | static __devinit int sh_csi2_probe(struct platform_device *pdev) | ||
242 | { | ||
243 | struct resource *res; | ||
244 | unsigned int irq; | ||
245 | int ret; | ||
246 | struct sh_csi2 *priv; | ||
247 | /* Platform data specify the PHY, lanes, ECC, CRC */ | ||
248 | struct sh_csi2_pdata *pdata = pdev->dev.platform_data; | ||
249 | |||
250 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
251 | /* Interrupt unused so far */ | ||
252 | irq = platform_get_irq(pdev, 0); | ||
253 | |||
254 | if (!res || (int)irq <= 0 || !pdata) { | ||
255 | dev_err(&pdev->dev, "Not enough CSI2 platform resources.\n"); | ||
256 | return -ENODEV; | ||
257 | } | ||
258 | |||
259 | /* TODO: Add support for CSI2I. Careful: different register layout! */ | ||
260 | if (pdata->type != SH_CSI2C) { | ||
261 | dev_err(&pdev->dev, "Only CSI2C supported ATM.\n"); | ||
262 | return -EINVAL; | ||
263 | } | ||
264 | |||
265 | priv = kzalloc(sizeof(struct sh_csi2), GFP_KERNEL); | ||
266 | if (!priv) | ||
267 | return -ENOMEM; | ||
268 | |||
269 | priv->irq = irq; | ||
270 | priv->notifier.notifier_call = sh_csi2_notify; | ||
271 | |||
272 | /* We MUST attach after the MIPI sensor */ | ||
273 | ret = bus_register_notifier(&soc_camera_bus_type, &priv->notifier); | ||
274 | if (ret < 0) { | ||
275 | dev_err(&pdev->dev, "CSI2 cannot register notifier\n"); | ||
276 | goto ernotify; | ||
277 | } | ||
278 | |||
279 | if (!request_mem_region(res->start, resource_size(res), pdev->name)) { | ||
280 | dev_err(&pdev->dev, "CSI2 register region already claimed\n"); | ||
281 | ret = -EBUSY; | ||
282 | goto ereqreg; | ||
283 | } | ||
284 | |||
285 | priv->base = ioremap(res->start, resource_size(res)); | ||
286 | if (!priv->base) { | ||
287 | ret = -ENXIO; | ||
288 | dev_err(&pdev->dev, "Unable to ioremap CSI2 registers.\n"); | ||
289 | goto eremap; | ||
290 | } | ||
291 | |||
292 | priv->pdev = pdev; | ||
293 | |||
294 | v4l2_subdev_init(&priv->subdev, &sh_csi2_subdev_ops); | ||
295 | v4l2_set_subdevdata(&priv->subdev, &pdev->dev); | ||
296 | |||
297 | platform_set_drvdata(pdev, priv); | ||
298 | |||
299 | pm_runtime_enable(&pdev->dev); | ||
300 | |||
301 | dev_dbg(&pdev->dev, "CSI2 probed.\n"); | ||
302 | |||
303 | return 0; | ||
304 | |||
305 | eremap: | ||
306 | release_mem_region(res->start, resource_size(res)); | ||
307 | ereqreg: | ||
308 | bus_unregister_notifier(&soc_camera_bus_type, &priv->notifier); | ||
309 | ernotify: | ||
310 | kfree(priv); | ||
311 | |||
312 | return ret; | ||
313 | } | ||
314 | |||
315 | static __devexit int sh_csi2_remove(struct platform_device *pdev) | ||
316 | { | ||
317 | struct sh_csi2 *priv = platform_get_drvdata(pdev); | ||
318 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
319 | |||
320 | bus_unregister_notifier(&soc_camera_bus_type, &priv->notifier); | ||
321 | pm_runtime_disable(&pdev->dev); | ||
322 | iounmap(priv->base); | ||
323 | release_mem_region(res->start, resource_size(res)); | ||
324 | platform_set_drvdata(pdev, NULL); | ||
325 | kfree(priv); | ||
326 | |||
327 | return 0; | ||
328 | } | ||
329 | |||
330 | static struct platform_driver __refdata sh_csi2_pdrv = { | ||
331 | .remove = __devexit_p(sh_csi2_remove), | ||
332 | .driver = { | ||
333 | .name = "sh-mobile-csi2", | ||
334 | .owner = THIS_MODULE, | ||
335 | }, | ||
336 | }; | ||
337 | |||
338 | static int __init sh_csi2_init(void) | ||
339 | { | ||
340 | return platform_driver_probe(&sh_csi2_pdrv, sh_csi2_probe); | ||
341 | } | ||
342 | |||
343 | static void __exit sh_csi2_exit(void) | ||
344 | { | ||
345 | platform_driver_unregister(&sh_csi2_pdrv); | ||
346 | } | ||
347 | |||
348 | module_init(sh_csi2_init); | ||
349 | module_exit(sh_csi2_exit); | ||
350 | |||
351 | MODULE_DESCRIPTION("SH-Mobile MIPI CSI-2 driver"); | ||
352 | MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); | ||
353 | MODULE_LICENSE("GPL v2"); | ||
354 | MODULE_ALIAS("platform:sh-mobile-csi2"); | ||