Commit | Line | Data |
---|---|---|
077e2c10 GL |
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> | |
2e2d612d | 12 | #include <linux/err.h> |
077e2c10 GL |
13 | #include <linux/i2c.h> |
14 | #include <linux/io.h> | |
15 | #include <linux/platform_device.h> | |
16 | #include <linux/pm_runtime.h> | |
17 | #include <linux/slab.h> | |
18 | #include <linux/videodev2.h> | |
7a707b89 | 19 | #include <linux/module.h> |
077e2c10 | 20 | |
6b526fed | 21 | #include <media/sh_mobile_ceu.h> |
077e2c10 GL |
22 | #include <media/sh_mobile_csi2.h> |
23 | #include <media/soc_camera.h> | |
f836c628 | 24 | #include <media/soc_mediabus.h> |
077e2c10 GL |
25 | #include <media/v4l2-common.h> |
26 | #include <media/v4l2-dev.h> | |
27 | #include <media/v4l2-device.h> | |
28 | #include <media/v4l2-mediabus.h> | |
29 | #include <media/v4l2-subdev.h> | |
30 | ||
31 | #define SH_CSI2_TREF 0x00 | |
32 | #define SH_CSI2_SRST 0x04 | |
33 | #define SH_CSI2_PHYCNT 0x08 | |
34 | #define SH_CSI2_CHKSUM 0x0C | |
35 | #define SH_CSI2_VCDT 0x10 | |
36 | ||
37 | struct sh_csi2 { | |
38 | struct v4l2_subdev subdev; | |
077e2c10 | 39 | unsigned int irq; |
022e52cf | 40 | unsigned long mipi_flags; |
077e2c10 GL |
41 | void __iomem *base; |
42 | struct platform_device *pdev; | |
43 | struct sh_csi2_client_config *client; | |
44 | }; | |
45 | ||
676d2d4f GL |
46 | static void sh_csi2_hwinit(struct sh_csi2 *priv); |
47 | ||
717fd5b4 HV |
48 | static int sh_csi2_set_fmt(struct v4l2_subdev *sd, |
49 | struct v4l2_subdev_pad_config *cfg, | |
50 | struct v4l2_subdev_format *format) | |
077e2c10 GL |
51 | { |
52 | struct sh_csi2 *priv = container_of(sd, struct sh_csi2, subdev); | |
53 | struct sh_csi2_pdata *pdata = priv->pdev->dev.platform_data; | |
717fd5b4 HV |
54 | struct v4l2_mbus_framefmt *mf = &format->format; |
55 | u32 tmp = (priv->client->channel & 3) << 8; | |
56 | ||
57 | if (format->pad) | |
58 | return -EINVAL; | |
077e2c10 GL |
59 | |
60 | if (mf->width > 8188) | |
61 | mf->width = 8188; | |
62 | else if (mf->width & 1) | |
63 | mf->width &= ~1; | |
64 | ||
65 | switch (pdata->type) { | |
66 | case SH_CSI2C: | |
67 | switch (mf->code) { | |
27ffaeb0 BB |
68 | case MEDIA_BUS_FMT_UYVY8_2X8: /* YUV422 */ |
69 | case MEDIA_BUS_FMT_YUYV8_1_5X8: /* YUV420 */ | |
70 | case MEDIA_BUS_FMT_Y8_1X8: /* RAW8 */ | |
71 | case MEDIA_BUS_FMT_SBGGR8_1X8: | |
72 | case MEDIA_BUS_FMT_SGRBG8_1X8: | |
077e2c10 GL |
73 | break; |
74 | default: | |
75 | /* All MIPI CSI-2 devices must support one of primary formats */ | |
27ffaeb0 | 76 | mf->code = MEDIA_BUS_FMT_YUYV8_2X8; |
077e2c10 GL |
77 | } |
78 | break; | |
79 | case SH_CSI2I: | |
80 | switch (mf->code) { | |
27ffaeb0 BB |
81 | case MEDIA_BUS_FMT_Y8_1X8: /* RAW8 */ |
82 | case MEDIA_BUS_FMT_SBGGR8_1X8: | |
83 | case MEDIA_BUS_FMT_SGRBG8_1X8: | |
84 | case MEDIA_BUS_FMT_SBGGR10_1X10: /* RAW10 */ | |
85 | case MEDIA_BUS_FMT_SBGGR12_1X12: /* RAW12 */ | |
077e2c10 GL |
86 | break; |
87 | default: | |
88 | /* All MIPI CSI-2 devices must support one of primary formats */ | |
27ffaeb0 | 89 | mf->code = MEDIA_BUS_FMT_SBGGR8_1X8; |
077e2c10 GL |
90 | } |
91 | break; | |
92 | } | |
93 | ||
717fd5b4 HV |
94 | if (format->which == V4L2_SUBDEV_FORMAT_TRY) { |
95 | cfg->try_fmt = *mf; | |
96 | return 0; | |
97 | } | |
077e2c10 | 98 | |
077e2c10 GL |
99 | if (mf->width > 8188 || mf->width & 1) |
100 | return -EINVAL; | |
101 | ||
102 | switch (mf->code) { | |
27ffaeb0 | 103 | case MEDIA_BUS_FMT_UYVY8_2X8: |
077e2c10 GL |
104 | tmp |= 0x1e; /* YUV422 8 bit */ |
105 | break; | |
27ffaeb0 | 106 | case MEDIA_BUS_FMT_YUYV8_1_5X8: |
077e2c10 GL |
107 | tmp |= 0x18; /* YUV420 8 bit */ |
108 | break; | |
27ffaeb0 | 109 | case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE: |
077e2c10 GL |
110 | tmp |= 0x21; /* RGB555 */ |
111 | break; | |
27ffaeb0 | 112 | case MEDIA_BUS_FMT_RGB565_2X8_BE: |
077e2c10 GL |
113 | tmp |= 0x22; /* RGB565 */ |
114 | break; | |
27ffaeb0 BB |
115 | case MEDIA_BUS_FMT_Y8_1X8: |
116 | case MEDIA_BUS_FMT_SBGGR8_1X8: | |
117 | case MEDIA_BUS_FMT_SGRBG8_1X8: | |
077e2c10 GL |
118 | tmp |= 0x2a; /* RAW8 */ |
119 | break; | |
120 | default: | |
121 | return -EINVAL; | |
122 | } | |
123 | ||
124 | iowrite32(tmp, priv->base + SH_CSI2_VCDT); | |
125 | ||
126 | return 0; | |
127 | } | |
128 | ||
022e52cf GL |
129 | static int sh_csi2_g_mbus_config(struct v4l2_subdev *sd, |
130 | struct v4l2_mbus_config *cfg) | |
131 | { | |
676d2d4f GL |
132 | struct sh_csi2 *priv = container_of(sd, struct sh_csi2, subdev); |
133 | ||
134 | if (!priv->mipi_flags) { | |
135 | struct soc_camera_device *icd = v4l2_get_subdev_hostdata(sd); | |
136 | struct v4l2_subdev *client_sd = soc_camera_to_subdev(icd); | |
137 | struct sh_csi2_pdata *pdata = priv->pdev->dev.platform_data; | |
138 | unsigned long common_flags, csi2_flags; | |
139 | struct v4l2_mbus_config client_cfg = {.type = V4L2_MBUS_CSI2,}; | |
140 | int ret; | |
141 | ||
142 | /* Check if we can support this camera */ | |
143 | csi2_flags = V4L2_MBUS_CSI2_CONTINUOUS_CLOCK | | |
144 | V4L2_MBUS_CSI2_1_LANE; | |
145 | ||
146 | switch (pdata->type) { | |
147 | case SH_CSI2C: | |
148 | if (priv->client->lanes != 1) | |
149 | csi2_flags |= V4L2_MBUS_CSI2_2_LANE; | |
150 | break; | |
151 | case SH_CSI2I: | |
152 | switch (priv->client->lanes) { | |
153 | default: | |
154 | csi2_flags |= V4L2_MBUS_CSI2_4_LANE; | |
155 | case 3: | |
156 | csi2_flags |= V4L2_MBUS_CSI2_3_LANE; | |
157 | case 2: | |
158 | csi2_flags |= V4L2_MBUS_CSI2_2_LANE; | |
159 | } | |
160 | } | |
161 | ||
162 | ret = v4l2_subdev_call(client_sd, video, g_mbus_config, &client_cfg); | |
163 | if (ret == -ENOIOCTLCMD) | |
164 | common_flags = csi2_flags; | |
165 | else if (!ret) | |
166 | common_flags = soc_mbus_config_compatible(&client_cfg, | |
167 | csi2_flags); | |
168 | else | |
169 | common_flags = 0; | |
170 | ||
171 | if (!common_flags) | |
172 | return -EINVAL; | |
173 | ||
174 | /* All good: camera MIPI configuration supported */ | |
175 | priv->mipi_flags = common_flags; | |
176 | } | |
177 | ||
178 | if (cfg) { | |
179 | cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | | |
180 | V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_HIGH | | |
181 | V4L2_MBUS_MASTER | V4L2_MBUS_DATA_ACTIVE_HIGH; | |
182 | cfg->type = V4L2_MBUS_PARALLEL; | |
183 | } | |
022e52cf GL |
184 | |
185 | return 0; | |
186 | } | |
187 | ||
188 | static int sh_csi2_s_mbus_config(struct v4l2_subdev *sd, | |
189 | const struct v4l2_mbus_config *cfg) | |
190 | { | |
191 | struct sh_csi2 *priv = container_of(sd, struct sh_csi2, subdev); | |
4c0b036d | 192 | struct soc_camera_device *icd = v4l2_get_subdev_hostdata(sd); |
022e52cf | 193 | struct v4l2_subdev *client_sd = soc_camera_to_subdev(icd); |
676d2d4f GL |
194 | struct v4l2_mbus_config client_cfg = {.type = V4L2_MBUS_CSI2,}; |
195 | int ret = sh_csi2_g_mbus_config(sd, NULL); | |
196 | ||
197 | if (ret < 0) | |
198 | return ret; | |
199 | ||
200 | pm_runtime_get_sync(&priv->pdev->dev); | |
201 | ||
202 | sh_csi2_hwinit(priv); | |
203 | ||
204 | client_cfg.flags = priv->mipi_flags; | |
022e52cf GL |
205 | |
206 | return v4l2_subdev_call(client_sd, video, s_mbus_config, &client_cfg); | |
207 | } | |
208 | ||
077e2c10 | 209 | static struct v4l2_subdev_video_ops sh_csi2_subdev_video_ops = { |
022e52cf GL |
210 | .g_mbus_config = sh_csi2_g_mbus_config, |
211 | .s_mbus_config = sh_csi2_s_mbus_config, | |
077e2c10 GL |
212 | }; |
213 | ||
717fd5b4 HV |
214 | static struct v4l2_subdev_pad_ops sh_csi2_subdev_pad_ops = { |
215 | .set_fmt = sh_csi2_set_fmt, | |
216 | }; | |
217 | ||
077e2c10 GL |
218 | static void sh_csi2_hwinit(struct sh_csi2 *priv) |
219 | { | |
220 | struct sh_csi2_pdata *pdata = priv->pdev->dev.platform_data; | |
221 | __u32 tmp = 0x10; /* Enable MIPI CSI clock lane */ | |
222 | ||
223 | /* Reflect registers immediately */ | |
224 | iowrite32(0x00000001, priv->base + SH_CSI2_TREF); | |
225 | /* reset CSI2 harware */ | |
226 | iowrite32(0x00000001, priv->base + SH_CSI2_SRST); | |
227 | udelay(5); | |
228 | iowrite32(0x00000000, priv->base + SH_CSI2_SRST); | |
229 | ||
f836c628 GL |
230 | switch (pdata->type) { |
231 | case SH_CSI2C: | |
232 | if (priv->client->lanes == 1) | |
233 | tmp |= 1; | |
234 | else | |
235 | /* Default - both lanes */ | |
236 | tmp |= 3; | |
237 | break; | |
238 | case SH_CSI2I: | |
239 | if (!priv->client->lanes || priv->client->lanes > 4) | |
240 | /* Default - all 4 lanes */ | |
241 | tmp |= 0xf; | |
242 | else | |
243 | tmp |= (1 << priv->client->lanes) - 1; | |
244 | } | |
077e2c10 GL |
245 | |
246 | if (priv->client->phy == SH_CSI2_PHY_MAIN) | |
247 | tmp |= 0x8000; | |
248 | ||
249 | iowrite32(tmp, priv->base + SH_CSI2_PHYCNT); | |
250 | ||
251 | tmp = 0; | |
252 | if (pdata->flags & SH_CSI2_ECC) | |
253 | tmp |= 2; | |
254 | if (pdata->flags & SH_CSI2_CRC) | |
255 | tmp |= 1; | |
256 | iowrite32(tmp, priv->base + SH_CSI2_CHKSUM); | |
257 | } | |
258 | ||
6b526fed | 259 | static int sh_csi2_client_connect(struct sh_csi2 *priv) |
077e2c10 | 260 | { |
6b526fed | 261 | struct device *dev = v4l2_get_subdevdata(&priv->subdev); |
676d2d4f GL |
262 | struct sh_csi2_pdata *pdata = dev->platform_data; |
263 | struct soc_camera_device *icd = v4l2_get_subdev_hostdata(&priv->subdev); | |
264 | int i; | |
6b526fed | 265 | |
2fbdc9bd GL |
266 | if (priv->client) |
267 | return -EBUSY; | |
268 | ||
077e2c10 | 269 | for (i = 0; i < pdata->num_clients; i++) |
676d2d4f GL |
270 | if ((pdata->clients[i].pdev && |
271 | &pdata->clients[i].pdev->dev == icd->pdev) || | |
272 | (icd->control && | |
273 | strcmp(pdata->clients[i].name, dev_name(icd->control)))) | |
077e2c10 GL |
274 | break; |
275 | ||
6b526fed | 276 | dev_dbg(dev, "%s(%p): found #%d\n", __func__, dev, i); |
077e2c10 GL |
277 | |
278 | if (i == pdata->num_clients) | |
6b526fed | 279 | return -ENODEV; |
077e2c10 | 280 | |
6b526fed | 281 | priv->client = pdata->clients + i; |
077e2c10 | 282 | |
6b526fed GL |
283 | return 0; |
284 | } | |
077e2c10 | 285 | |
6b526fed GL |
286 | static void sh_csi2_client_disconnect(struct sh_csi2 *priv) |
287 | { | |
2fbdc9bd GL |
288 | if (!priv->client) |
289 | return; | |
290 | ||
6b526fed | 291 | priv->client = NULL; |
077e2c10 | 292 | |
6b526fed | 293 | pm_runtime_put(v4l2_get_subdevdata(&priv->subdev)); |
077e2c10 GL |
294 | } |
295 | ||
6b526fed GL |
296 | static int sh_csi2_s_power(struct v4l2_subdev *sd, int on) |
297 | { | |
298 | struct sh_csi2 *priv = container_of(sd, struct sh_csi2, subdev); | |
299 | ||
300 | if (on) | |
301 | return sh_csi2_client_connect(priv); | |
302 | ||
303 | sh_csi2_client_disconnect(priv); | |
304 | return 0; | |
305 | } | |
306 | ||
307 | static struct v4l2_subdev_core_ops sh_csi2_subdev_core_ops = { | |
308 | .s_power = sh_csi2_s_power, | |
309 | }; | |
310 | ||
311 | static struct v4l2_subdev_ops sh_csi2_subdev_ops = { | |
312 | .core = &sh_csi2_subdev_core_ops, | |
313 | .video = &sh_csi2_subdev_video_ops, | |
717fd5b4 | 314 | .pad = &sh_csi2_subdev_pad_ops, |
6b526fed GL |
315 | }; |
316 | ||
4c62e976 | 317 | static int sh_csi2_probe(struct platform_device *pdev) |
077e2c10 GL |
318 | { |
319 | struct resource *res; | |
320 | unsigned int irq; | |
321 | int ret; | |
322 | struct sh_csi2 *priv; | |
323 | /* Platform data specify the PHY, lanes, ECC, CRC */ | |
324 | struct sh_csi2_pdata *pdata = pdev->dev.platform_data; | |
325 | ||
676d2d4f GL |
326 | if (!pdata) |
327 | return -EINVAL; | |
328 | ||
329 | priv = devm_kzalloc(&pdev->dev, sizeof(struct sh_csi2), GFP_KERNEL); | |
330 | if (!priv) | |
331 | return -ENOMEM; | |
332 | ||
077e2c10 GL |
333 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
334 | /* Interrupt unused so far */ | |
335 | irq = platform_get_irq(pdev, 0); | |
336 | ||
676d2d4f | 337 | if (!res || (int)irq <= 0) { |
077e2c10 GL |
338 | dev_err(&pdev->dev, "Not enough CSI2 platform resources.\n"); |
339 | return -ENODEV; | |
340 | } | |
341 | ||
342 | /* TODO: Add support for CSI2I. Careful: different register layout! */ | |
343 | if (pdata->type != SH_CSI2C) { | |
344 | dev_err(&pdev->dev, "Only CSI2C supported ATM.\n"); | |
345 | return -EINVAL; | |
346 | } | |
347 | ||
077e2c10 | 348 | priv->irq = irq; |
077e2c10 | 349 | |
2e2d612d SK |
350 | priv->base = devm_ioremap_resource(&pdev->dev, res); |
351 | if (IS_ERR(priv->base)) | |
352 | return PTR_ERR(priv->base); | |
077e2c10 GL |
353 | |
354 | priv->pdev = pdev; | |
676d2d4f GL |
355 | priv->subdev.owner = THIS_MODULE; |
356 | priv->subdev.dev = &pdev->dev; | |
357 | platform_set_drvdata(pdev, &priv->subdev); | |
077e2c10 GL |
358 | |
359 | v4l2_subdev_init(&priv->subdev, &sh_csi2_subdev_ops); | |
360 | v4l2_set_subdevdata(&priv->subdev, &pdev->dev); | |
361 | ||
6b526fed | 362 | snprintf(priv->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s.mipi-csi", |
676d2d4f GL |
363 | dev_name(&pdev->dev)); |
364 | ||
365 | ret = v4l2_async_register_subdev(&priv->subdev); | |
6b526fed | 366 | if (ret < 0) |
721110b3 | 367 | return ret; |
077e2c10 GL |
368 | |
369 | pm_runtime_enable(&pdev->dev); | |
370 | ||
371 | dev_dbg(&pdev->dev, "CSI2 probed.\n"); | |
372 | ||
373 | return 0; | |
077e2c10 GL |
374 | } |
375 | ||
4c62e976 | 376 | static int sh_csi2_remove(struct platform_device *pdev) |
077e2c10 | 377 | { |
676d2d4f GL |
378 | struct v4l2_subdev *subdev = platform_get_drvdata(pdev); |
379 | struct sh_csi2 *priv = container_of(subdev, struct sh_csi2, subdev); | |
077e2c10 | 380 | |
676d2d4f | 381 | v4l2_async_unregister_subdev(&priv->subdev); |
077e2c10 | 382 | pm_runtime_disable(&pdev->dev); |
077e2c10 GL |
383 | |
384 | return 0; | |
385 | } | |
386 | ||
387 | static struct platform_driver __refdata sh_csi2_pdrv = { | |
4c62e976 | 388 | .remove = sh_csi2_remove, |
6b526fed GL |
389 | .probe = sh_csi2_probe, |
390 | .driver = { | |
077e2c10 | 391 | .name = "sh-mobile-csi2", |
077e2c10 GL |
392 | }, |
393 | }; | |
394 | ||
1d6629b1 | 395 | module_platform_driver(sh_csi2_pdrv); |
077e2c10 GL |
396 | |
397 | MODULE_DESCRIPTION("SH-Mobile MIPI CSI-2 driver"); | |
398 | MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); | |
399 | MODULE_LICENSE("GPL v2"); | |
400 | MODULE_ALIAS("platform:sh-mobile-csi2"); |