Commit 077e2c10c9cb618d571bf16475db696610bdb24a
Committed by
Mauro Carvalho Chehab
1 parent
52d268a362
Exists in
master
and in
39 other branches
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>
Showing 4 changed files with 407 additions and 0 deletions Side-by-side Diff
drivers/media/video/Kconfig
... | ... | @@ -876,6 +876,12 @@ |
876 | 876 | ---help--- |
877 | 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 | 885 | config VIDEO_SH_MOBILE_CEU |
880 | 886 | tristate "SuperH Mobile CEU Interface driver" |
881 | 887 | depends on VIDEO_DEV && SOC_CAMERA && HAS_DMA && HAVE_CLK |
drivers/media/video/Makefile
... | ... | @@ -160,6 +160,7 @@ |
160 | 160 | obj-$(CONFIG_VIDEO_MX1) += mx1_camera.o |
161 | 161 | obj-$(CONFIG_VIDEO_MX3) += mx3_camera.o |
162 | 162 | obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o |
163 | +obj-$(CONFIG_VIDEO_SH_MOBILE_CSI2) += sh_mobile_csi2.o | |
163 | 164 | obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o |
164 | 165 | |
165 | 166 | obj-$(CONFIG_ARCH_DAVINCI) += davinci/ |
drivers/media/video/sh_mobile_csi2.c
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"); |
include/media/sh_mobile_csi2.h
1 | +/* | |
2 | + * Driver header 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 | +#ifndef SH_MIPI_CSI | |
12 | +#define SH_MIPI_CSI | |
13 | + | |
14 | +enum sh_csi2_phy { | |
15 | + SH_CSI2_PHY_MAIN, | |
16 | + SH_CSI2_PHY_SUB, | |
17 | +}; | |
18 | + | |
19 | +enum sh_csi2_type { | |
20 | + SH_CSI2C, | |
21 | + SH_CSI2I, | |
22 | +}; | |
23 | + | |
24 | +#define SH_CSI2_CRC (1 << 0) | |
25 | +#define SH_CSI2_ECC (1 << 1) | |
26 | + | |
27 | +struct platform_device; | |
28 | + | |
29 | +struct sh_csi2_client_config { | |
30 | + enum sh_csi2_phy phy; | |
31 | + unsigned char lanes; /* bitmask[3:0] */ | |
32 | + unsigned char channel; /* 0..3 */ | |
33 | + struct platform_device *pdev; /* client platform device */ | |
34 | +}; | |
35 | + | |
36 | +struct sh_csi2_pdata { | |
37 | + enum sh_csi2_type type; | |
38 | + unsigned int flags; | |
39 | + struct sh_csi2_client_config *clients; | |
40 | + int num_clients; | |
41 | +}; | |
42 | + | |
43 | +struct device; | |
44 | +struct v4l2_device; | |
45 | + | |
46 | +#endif |