diff options
Diffstat (limited to 'drivers/media/video/sh_mobile_csi2.c')
-rw-r--r-- | drivers/media/video/sh_mobile_csi2.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/drivers/media/video/sh_mobile_csi2.c b/drivers/media/video/sh_mobile_csi2.c new file mode 100644 index 00000000000..2893a0134c7 --- /dev/null +++ b/drivers/media/video/sh_mobile_csi2.c | |||
@@ -0,0 +1,372 @@ | |||
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_ceu.h> | ||
20 | #include <media/sh_mobile_csi2.h> | ||
21 | #include <media/soc_camera.h> | ||
22 | #include <media/v4l2-common.h> | ||
23 | #include <media/v4l2-dev.h> | ||
24 | #include <media/v4l2-device.h> | ||
25 | #include <media/v4l2-mediabus.h> | ||
26 | #include <media/v4l2-subdev.h> | ||
27 | |||
28 | #define SH_CSI2_TREF 0x00 | ||
29 | #define SH_CSI2_SRST 0x04 | ||
30 | #define SH_CSI2_PHYCNT 0x08 | ||
31 | #define SH_CSI2_CHKSUM 0x0C | ||
32 | #define SH_CSI2_VCDT 0x10 | ||
33 | |||
34 | struct sh_csi2 { | ||
35 | struct v4l2_subdev subdev; | ||
36 | struct list_head list; | ||
37 | unsigned int irq; | ||
38 | void __iomem *base; | ||
39 | struct platform_device *pdev; | ||
40 | struct sh_csi2_client_config *client; | ||
41 | unsigned long (*query_bus_param)(struct soc_camera_device *); | ||
42 | int (*set_bus_param)(struct soc_camera_device *, unsigned long); | ||
43 | }; | ||
44 | |||
45 | static int sh_csi2_try_fmt(struct v4l2_subdev *sd, | ||
46 | struct v4l2_mbus_framefmt *mf) | ||
47 | { | ||
48 | struct sh_csi2 *priv = container_of(sd, struct sh_csi2, subdev); | ||
49 | struct sh_csi2_pdata *pdata = priv->pdev->dev.platform_data; | ||
50 | |||
51 | if (mf->width > 8188) | ||
52 | mf->width = 8188; | ||
53 | else if (mf->width & 1) | ||
54 | mf->width &= ~1; | ||
55 | |||
56 | switch (pdata->type) { | ||
57 | case SH_CSI2C: | ||
58 | switch (mf->code) { | ||
59 | case V4L2_MBUS_FMT_UYVY8_2X8: /* YUV422 */ | ||
60 | case V4L2_MBUS_FMT_YUYV8_1_5X8: /* YUV420 */ | ||
61 | case V4L2_MBUS_FMT_Y8_1X8: /* RAW8 */ | ||
62 | case V4L2_MBUS_FMT_SBGGR8_1X8: | ||
63 | case V4L2_MBUS_FMT_SGRBG8_1X8: | ||
64 | break; | ||
65 | default: | ||
66 | /* All MIPI CSI-2 devices must support one of primary formats */ | ||
67 | mf->code = V4L2_MBUS_FMT_YUYV8_2X8; | ||
68 | } | ||
69 | break; | ||
70 | case SH_CSI2I: | ||
71 | switch (mf->code) { | ||
72 | case V4L2_MBUS_FMT_Y8_1X8: /* RAW8 */ | ||
73 | case V4L2_MBUS_FMT_SBGGR8_1X8: | ||
74 | case V4L2_MBUS_FMT_SGRBG8_1X8: | ||
75 | case V4L2_MBUS_FMT_SBGGR10_1X10: /* RAW10 */ | ||
76 | case V4L2_MBUS_FMT_SBGGR12_1X12: /* RAW12 */ | ||
77 | break; | ||
78 | default: | ||
79 | /* All MIPI CSI-2 devices must support one of primary formats */ | ||
80 | mf->code = V4L2_MBUS_FMT_SBGGR8_1X8; | ||
81 | } | ||
82 | break; | ||
83 | } | ||
84 | |||
85 | return 0; | ||
86 | } | ||
87 | |||
88 | /* | ||
89 | * We have done our best in try_fmt to try and tell the sensor, which formats | ||
90 | * we support. If now the configuration is unsuitable for us we can only | ||
91 | * error out. | ||
92 | */ | ||
93 | static int sh_csi2_s_fmt(struct v4l2_subdev *sd, | ||
94 | struct v4l2_mbus_framefmt *mf) | ||
95 | { | ||
96 | struct sh_csi2 *priv = container_of(sd, struct sh_csi2, subdev); | ||
97 | u32 tmp = (priv->client->channel & 3) << 8; | ||
98 | |||
99 | dev_dbg(sd->v4l2_dev->dev, "%s(%u)\n", __func__, mf->code); | ||
100 | if (mf->width > 8188 || mf->width & 1) | ||
101 | return -EINVAL; | ||
102 | |||
103 | switch (mf->code) { | ||
104 | case V4L2_MBUS_FMT_UYVY8_2X8: | ||
105 | tmp |= 0x1e; /* YUV422 8 bit */ | ||
106 | break; | ||
107 | case V4L2_MBUS_FMT_YUYV8_1_5X8: | ||
108 | tmp |= 0x18; /* YUV420 8 bit */ | ||
109 | break; | ||
110 | case V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE: | ||
111 | tmp |= 0x21; /* RGB555 */ | ||
112 | break; | ||
113 | case V4L2_MBUS_FMT_RGB565_2X8_BE: | ||
114 | tmp |= 0x22; /* RGB565 */ | ||
115 | break; | ||
116 | case V4L2_MBUS_FMT_Y8_1X8: | ||
117 | case V4L2_MBUS_FMT_SBGGR8_1X8: | ||
118 | case V4L2_MBUS_FMT_SGRBG8_1X8: | ||
119 | tmp |= 0x2a; /* RAW8 */ | ||
120 | break; | ||
121 | default: | ||
122 | return -EINVAL; | ||
123 | } | ||
124 | |||
125 | iowrite32(tmp, priv->base + SH_CSI2_VCDT); | ||
126 | |||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | static struct v4l2_subdev_video_ops sh_csi2_subdev_video_ops = { | ||
131 | .s_mbus_fmt = sh_csi2_s_fmt, | ||
132 | .try_mbus_fmt = sh_csi2_try_fmt, | ||
133 | }; | ||
134 | |||
135 | static void sh_csi2_hwinit(struct sh_csi2 *priv) | ||
136 | { | ||
137 | struct sh_csi2_pdata *pdata = priv->pdev->dev.platform_data; | ||
138 | __u32 tmp = 0x10; /* Enable MIPI CSI clock lane */ | ||
139 | |||
140 | /* Reflect registers immediately */ | ||
141 | iowrite32(0x00000001, priv->base + SH_CSI2_TREF); | ||
142 | /* reset CSI2 harware */ | ||
143 | iowrite32(0x00000001, priv->base + SH_CSI2_SRST); | ||
144 | udelay(5); | ||
145 | iowrite32(0x00000000, priv->base + SH_CSI2_SRST); | ||
146 | |||
147 | if (priv->client->lanes & 3) | ||
148 | tmp |= priv->client->lanes & 3; | ||
149 | else | ||
150 | /* Default - both lanes */ | ||
151 | tmp |= 3; | ||
152 | |||
153 | if (priv->client->phy == SH_CSI2_PHY_MAIN) | ||
154 | tmp |= 0x8000; | ||
155 | |||
156 | iowrite32(tmp, priv->base + SH_CSI2_PHYCNT); | ||
157 | |||
158 | tmp = 0; | ||
159 | if (pdata->flags & SH_CSI2_ECC) | ||
160 | tmp |= 2; | ||
161 | if (pdata->flags & SH_CSI2_CRC) | ||
162 | tmp |= 1; | ||
163 | iowrite32(tmp, priv->base + SH_CSI2_CHKSUM); | ||
164 | } | ||
165 | |||
166 | static int sh_csi2_set_bus_param(struct soc_camera_device *icd, | ||
167 | unsigned long flags) | ||
168 | { | ||
169 | return 0; | ||
170 | } | ||
171 | |||
172 | static unsigned long sh_csi2_query_bus_param(struct soc_camera_device *icd) | ||
173 | { | ||
174 | struct soc_camera_link *icl = to_soc_camera_link(icd); | ||
175 | const unsigned long flags = SOCAM_PCLK_SAMPLE_RISING | | ||
176 | SOCAM_HSYNC_ACTIVE_HIGH | SOCAM_VSYNC_ACTIVE_HIGH | | ||
177 | SOCAM_MASTER | SOCAM_DATAWIDTH_8 | SOCAM_DATA_ACTIVE_HIGH; | ||
178 | |||
179 | return soc_camera_apply_sensor_flags(icl, flags); | ||
180 | } | ||
181 | |||
182 | static int sh_csi2_client_connect(struct sh_csi2 *priv) | ||
183 | { | ||
184 | struct sh_csi2_pdata *pdata = priv->pdev->dev.platform_data; | ||
185 | struct v4l2_subdev *sd, *csi2_sd = &priv->subdev; | ||
186 | struct soc_camera_device *icd = NULL; | ||
187 | struct device *dev = v4l2_get_subdevdata(&priv->subdev); | ||
188 | int i; | ||
189 | |||
190 | v4l2_device_for_each_subdev(sd, csi2_sd->v4l2_dev) | ||
191 | if (sd->grp_id) { | ||
192 | icd = (struct soc_camera_device *)sd->grp_id; | ||
193 | break; | ||
194 | } | ||
195 | |||
196 | if (!icd) | ||
197 | return -EINVAL; | ||
198 | |||
199 | for (i = 0; i < pdata->num_clients; i++) | ||
200 | if (&pdata->clients[i].pdev->dev == icd->pdev) | ||
201 | break; | ||
202 | |||
203 | dev_dbg(dev, "%s(%p): found #%d\n", __func__, dev, i); | ||
204 | |||
205 | if (i == pdata->num_clients) | ||
206 | return -ENODEV; | ||
207 | |||
208 | priv->client = pdata->clients + i; | ||
209 | |||
210 | priv->set_bus_param = icd->ops->set_bus_param; | ||
211 | priv->query_bus_param = icd->ops->query_bus_param; | ||
212 | icd->ops->set_bus_param = sh_csi2_set_bus_param; | ||
213 | icd->ops->query_bus_param = sh_csi2_query_bus_param; | ||
214 | |||
215 | csi2_sd->grp_id = (long)icd; | ||
216 | |||
217 | pm_runtime_get_sync(dev); | ||
218 | |||
219 | sh_csi2_hwinit(priv); | ||
220 | |||
221 | return 0; | ||
222 | } | ||
223 | |||
224 | static void sh_csi2_client_disconnect(struct sh_csi2 *priv) | ||
225 | { | ||
226 | struct soc_camera_device *icd = (struct soc_camera_device *)priv->subdev.grp_id; | ||
227 | |||
228 | priv->client = NULL; | ||
229 | priv->subdev.grp_id = 0; | ||
230 | |||
231 | /* Driver is about to be unbound */ | ||
232 | icd->ops->set_bus_param = priv->set_bus_param; | ||
233 | icd->ops->query_bus_param = priv->query_bus_param; | ||
234 | priv->set_bus_param = NULL; | ||
235 | priv->query_bus_param = NULL; | ||
236 | |||
237 | pm_runtime_put(v4l2_get_subdevdata(&priv->subdev)); | ||
238 | } | ||
239 | |||
240 | static int sh_csi2_s_power(struct v4l2_subdev *sd, int on) | ||
241 | { | ||
242 | struct sh_csi2 *priv = container_of(sd, struct sh_csi2, subdev); | ||
243 | |||
244 | if (on) | ||
245 | return sh_csi2_client_connect(priv); | ||
246 | |||
247 | sh_csi2_client_disconnect(priv); | ||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | static struct v4l2_subdev_core_ops sh_csi2_subdev_core_ops = { | ||
252 | .s_power = sh_csi2_s_power, | ||
253 | }; | ||
254 | |||
255 | static struct v4l2_subdev_ops sh_csi2_subdev_ops = { | ||
256 | .core = &sh_csi2_subdev_core_ops, | ||
257 | .video = &sh_csi2_subdev_video_ops, | ||
258 | }; | ||
259 | |||
260 | static __devinit int sh_csi2_probe(struct platform_device *pdev) | ||
261 | { | ||
262 | struct resource *res; | ||
263 | unsigned int irq; | ||
264 | int ret; | ||
265 | struct sh_csi2 *priv; | ||
266 | /* Platform data specify the PHY, lanes, ECC, CRC */ | ||
267 | struct sh_csi2_pdata *pdata = pdev->dev.platform_data; | ||
268 | |||
269 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
270 | /* Interrupt unused so far */ | ||
271 | irq = platform_get_irq(pdev, 0); | ||
272 | |||
273 | if (!res || (int)irq <= 0 || !pdata) { | ||
274 | dev_err(&pdev->dev, "Not enough CSI2 platform resources.\n"); | ||
275 | return -ENODEV; | ||
276 | } | ||
277 | |||
278 | /* TODO: Add support for CSI2I. Careful: different register layout! */ | ||
279 | if (pdata->type != SH_CSI2C) { | ||
280 | dev_err(&pdev->dev, "Only CSI2C supported ATM.\n"); | ||
281 | return -EINVAL; | ||
282 | } | ||
283 | |||
284 | priv = kzalloc(sizeof(struct sh_csi2), GFP_KERNEL); | ||
285 | if (!priv) | ||
286 | return -ENOMEM; | ||
287 | |||
288 | priv->irq = irq; | ||
289 | |||
290 | if (!request_mem_region(res->start, resource_size(res), pdev->name)) { | ||
291 | dev_err(&pdev->dev, "CSI2 register region already claimed\n"); | ||
292 | ret = -EBUSY; | ||
293 | goto ereqreg; | ||
294 | } | ||
295 | |||
296 | priv->base = ioremap(res->start, resource_size(res)); | ||
297 | if (!priv->base) { | ||
298 | ret = -ENXIO; | ||
299 | dev_err(&pdev->dev, "Unable to ioremap CSI2 registers.\n"); | ||
300 | goto eremap; | ||
301 | } | ||
302 | |||
303 | priv->pdev = pdev; | ||
304 | platform_set_drvdata(pdev, priv); | ||
305 | |||
306 | v4l2_subdev_init(&priv->subdev, &sh_csi2_subdev_ops); | ||
307 | v4l2_set_subdevdata(&priv->subdev, &pdev->dev); | ||
308 | |||
309 | snprintf(priv->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s.mipi-csi", | ||
310 | dev_name(pdata->v4l2_dev->dev)); | ||
311 | ret = v4l2_device_register_subdev(pdata->v4l2_dev, &priv->subdev); | ||
312 | dev_dbg(&pdev->dev, "%s(%p): ret(register_subdev) = %d\n", __func__, priv, ret); | ||
313 | if (ret < 0) | ||
314 | goto esdreg; | ||
315 | |||
316 | pm_runtime_enable(&pdev->dev); | ||
317 | |||
318 | dev_dbg(&pdev->dev, "CSI2 probed.\n"); | ||
319 | |||
320 | return 0; | ||
321 | |||
322 | esdreg: | ||
323 | iounmap(priv->base); | ||
324 | eremap: | ||
325 | release_mem_region(res->start, resource_size(res)); | ||
326 | ereqreg: | ||
327 | kfree(priv); | ||
328 | |||
329 | return ret; | ||
330 | } | ||
331 | |||
332 | static __devexit int sh_csi2_remove(struct platform_device *pdev) | ||
333 | { | ||
334 | struct sh_csi2 *priv = platform_get_drvdata(pdev); | ||
335 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
336 | |||
337 | v4l2_device_unregister_subdev(&priv->subdev); | ||
338 | pm_runtime_disable(&pdev->dev); | ||
339 | iounmap(priv->base); | ||
340 | release_mem_region(res->start, resource_size(res)); | ||
341 | platform_set_drvdata(pdev, NULL); | ||
342 | kfree(priv); | ||
343 | |||
344 | return 0; | ||
345 | } | ||
346 | |||
347 | static struct platform_driver __refdata sh_csi2_pdrv = { | ||
348 | .remove = __devexit_p(sh_csi2_remove), | ||
349 | .probe = sh_csi2_probe, | ||
350 | .driver = { | ||
351 | .name = "sh-mobile-csi2", | ||
352 | .owner = THIS_MODULE, | ||
353 | }, | ||
354 | }; | ||
355 | |||
356 | static int __init sh_csi2_init(void) | ||
357 | { | ||
358 | return platform_driver_register(&sh_csi2_pdrv); | ||
359 | } | ||
360 | |||
361 | static void __exit sh_csi2_exit(void) | ||
362 | { | ||
363 | platform_driver_unregister(&sh_csi2_pdrv); | ||
364 | } | ||
365 | |||
366 | module_init(sh_csi2_init); | ||
367 | module_exit(sh_csi2_exit); | ||
368 | |||
369 | MODULE_DESCRIPTION("SH-Mobile MIPI CSI-2 driver"); | ||
370 | MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); | ||
371 | MODULE_LICENSE("GPL v2"); | ||
372 | MODULE_ALIAS("platform:sh-mobile-csi2"); | ||