aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/video
diff options
context:
space:
mode:
authorTomasz Stanislawski <t.stanislaws@samsung.com>2011-02-02 03:40:08 -0500
committerMauro Carvalho Chehab <mchehab@redhat.com>2011-07-27 16:56:01 -0400
commit9a498400fede652a5ada51e74ae47bba99c7ed07 (patch)
treeda6745b56bf4da4cd11642f8d20b0e5b20fc46ac /drivers/media/video
parenta52074ee7ad0b9ed4b4180c843d1c3114374e172 (diff)
[media] v4l: s5p-tv: add SDO driver for Samsung S5P platform
Add drivers for Standard Definition output (SDO) on Samsung platforms from S5P family. The driver provides control over streaming analog TV via Composite connector. Driver is using: - v4l2 framework - runtime PM Signed-off-by: Tomasz Stanislawski <t.stanislaws@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Reviewed-by: Marek Szyprowski <m.szyprowski@samsung.com> Reviewed-by: Sylwester Nawrocki <s.nawrocki@samsung.com> Reviewed-by: Hans Verkuil <hverkuil@xs4all.nl> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/video')
-rw-r--r--drivers/media/video/s5p-tv/Kconfig11
-rw-r--r--drivers/media/video/s5p-tv/Makefile2
-rw-r--r--drivers/media/video/s5p-tv/regs-sdo.h63
-rw-r--r--drivers/media/video/s5p-tv/sdo_drv.c479
4 files changed, 555 insertions, 0 deletions
diff --git a/drivers/media/video/s5p-tv/Kconfig b/drivers/media/video/s5p-tv/Kconfig
index 893be5b3677..9c1f6343f07 100644
--- a/drivers/media/video/s5p-tv/Kconfig
+++ b/drivers/media/video/s5p-tv/Kconfig
@@ -46,4 +46,15 @@ config VIDEO_SAMSUNG_S5P_HDMIPHY
46 as module. It is an I2C driver, that exposes a V4L2 46 as module. It is an I2C driver, that exposes a V4L2
47 subdev for use by other drivers. 47 subdev for use by other drivers.
48 48
49config VIDEO_SAMSUNG_S5P_SDO
50 tristate "Samsung Analog TV Driver"
51 depends on VIDEO_DEV && VIDEO_V4L2
52 depends on VIDEO_SAMSUNG_S5P_TV
53 help
54 Say Y here if you want support for the analog TV output
55 interface in S5P Samsung SoC. The driver can be compiled
56 as module. It is an auxiliary driver, that exposes a V4L2
57 subdev for use by other drivers. This driver requires
58 hdmiphy driver to work correctly.
59
49endif # VIDEO_SAMSUNG_S5P_TV 60endif # VIDEO_SAMSUNG_S5P_TV
diff --git a/drivers/media/video/s5p-tv/Makefile b/drivers/media/video/s5p-tv/Makefile
index 1b0713269c1..c874d16be9f 100644
--- a/drivers/media/video/s5p-tv/Makefile
+++ b/drivers/media/video/s5p-tv/Makefile
@@ -10,4 +10,6 @@ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_HDMIPHY) += s5p-hdmiphy.o
10s5p-hdmiphy-y += hdmiphy_drv.o 10s5p-hdmiphy-y += hdmiphy_drv.o
11obj-$(CONFIG_VIDEO_SAMSUNG_S5P_HDMI) += s5p-hdmi.o 11obj-$(CONFIG_VIDEO_SAMSUNG_S5P_HDMI) += s5p-hdmi.o
12s5p-hdmi-y += hdmi_drv.o 12s5p-hdmi-y += hdmi_drv.o
13obj-$(CONFIG_VIDEO_SAMSUNG_S5P_SDO) += s5p-sdo.o
14s5p-sdo-y += sdo_drv.o
13 15
diff --git a/drivers/media/video/s5p-tv/regs-sdo.h b/drivers/media/video/s5p-tv/regs-sdo.h
new file mode 100644
index 00000000000..7f7c2b8ac14
--- /dev/null
+++ b/drivers/media/video/s5p-tv/regs-sdo.h
@@ -0,0 +1,63 @@
1/* drivers/media/video/s5p-tv/regs-sdo.h
2 *
3 * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
4 * http://www.samsung.com/
5 *
6 * SDO register description file
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 */
12
13#ifndef SAMSUNG_REGS_SDO_H
14#define SAMSUNG_REGS_SDO_H
15
16/*
17 * Register part
18 */
19
20#define SDO_CLKCON 0x0000
21#define SDO_CONFIG 0x0008
22#define SDO_VBI 0x0014
23#define SDO_DAC 0x003C
24#define SDO_CCCON 0x0180
25#define SDO_IRQ 0x0280
26#define SDO_IRQMASK 0x0284
27#define SDO_VERSION 0x03D8
28
29/*
30 * Bit definition part
31 */
32
33/* SDO Clock Control Register (SDO_CLKCON) */
34#define SDO_TVOUT_SW_RESET (1 << 4)
35#define SDO_TVOUT_CLOCK_READY (1 << 1)
36#define SDO_TVOUT_CLOCK_ON (1 << 0)
37
38/* SDO Video Standard Configuration Register (SDO_CONFIG) */
39#define SDO_PROGRESSIVE (1 << 4)
40#define SDO_NTSC_M 0
41#define SDO_PAL_M 1
42#define SDO_PAL_BGHID 2
43#define SDO_PAL_N 3
44#define SDO_PAL_NC 4
45#define SDO_NTSC_443 8
46#define SDO_PAL_60 9
47#define SDO_STANDARD_MASK 0xf
48
49/* SDO VBI Configuration Register (SDO_VBI) */
50#define SDO_CVBS_WSS_INS (1 << 14)
51#define SDO_CVBS_CLOSED_CAPTION_MASK (3 << 12)
52
53/* SDO DAC Configuration Register (SDO_DAC) */
54#define SDO_POWER_ON_DAC (1 << 0)
55
56/* SDO Color Compensation On/Off Control (SDO_CCCON) */
57#define SDO_COMPENSATION_BHS_ADJ_OFF (1 << 4)
58#define SDO_COMPENSATION_CVBS_COMP_OFF (1 << 0)
59
60/* SDO Interrupt Request Register (SDO_IRQ) */
61#define SDO_VSYNC_IRQ_PEND (1 << 0)
62
63#endif /* SAMSUNG_REGS_SDO_H */
diff --git a/drivers/media/video/s5p-tv/sdo_drv.c b/drivers/media/video/s5p-tv/sdo_drv.c
new file mode 100644
index 00000000000..4dddd6bd635
--- /dev/null
+++ b/drivers/media/video/s5p-tv/sdo_drv.c
@@ -0,0 +1,479 @@
1/*
2 * Samsung Standard Definition Output (SDO) driver
3 *
4 * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
5 *
6 * Tomasz Stanislawski, <t.stanislaws@samsung.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published
10 * by the Free Software Foundiation. either version 2 of the License,
11 * or (at your option) any later version
12 */
13
14#include <linux/clk.h>
15#include <linux/delay.h>
16#include <linux/kernel.h>
17#include <linux/module.h>
18#include <linux/interrupt.h>
19#include <linux/io.h>
20#include <linux/irq.h>
21#include <linux/platform_device.h>
22#include <linux/pm_runtime.h>
23#include <linux/regulator/consumer.h>
24#include <linux/slab.h>
25
26#include <media/v4l2-subdev.h>
27
28#include "regs-sdo.h"
29
30MODULE_AUTHOR("Tomasz Stanislawski, <t.stanislaws@samsung.com>");
31MODULE_DESCRIPTION("Samsung Standard Definition Output (SDO)");
32MODULE_LICENSE("GPL");
33
34#define SDO_DEFAULT_STD V4L2_STD_PAL
35
36struct sdo_format {
37 v4l2_std_id id;
38 /* all modes are 720 pixels wide */
39 unsigned int height;
40 unsigned int cookie;
41};
42
43struct sdo_device {
44 /** pointer to device parent */
45 struct device *dev;
46 /** base address of SDO registers */
47 void __iomem *regs;
48 /** SDO interrupt */
49 unsigned int irq;
50 /** DAC source clock */
51 struct clk *sclk_dac;
52 /** DAC clock */
53 struct clk *dac;
54 /** DAC physical interface */
55 struct clk *dacphy;
56 /** clock for control of VPLL */
57 struct clk *fout_vpll;
58 /** regulator for SDO IP power */
59 struct regulator *vdac;
60 /** regulator for SDO plug detection */
61 struct regulator *vdet;
62 /** subdev used as device interface */
63 struct v4l2_subdev sd;
64 /** current format */
65 const struct sdo_format *fmt;
66};
67
68static inline struct sdo_device *sd_to_sdev(struct v4l2_subdev *sd)
69{
70 return container_of(sd, struct sdo_device, sd);
71}
72
73static inline
74void sdo_write_mask(struct sdo_device *sdev, u32 reg_id, u32 value, u32 mask)
75{
76 u32 old = readl(sdev->regs + reg_id);
77 value = (value & mask) | (old & ~mask);
78 writel(value, sdev->regs + reg_id);
79}
80
81static inline
82void sdo_write(struct sdo_device *sdev, u32 reg_id, u32 value)
83{
84 writel(value, sdev->regs + reg_id);
85}
86
87static inline
88u32 sdo_read(struct sdo_device *sdev, u32 reg_id)
89{
90 return readl(sdev->regs + reg_id);
91}
92
93static irqreturn_t sdo_irq_handler(int irq, void *dev_data)
94{
95 struct sdo_device *sdev = dev_data;
96
97 /* clear interrupt */
98 sdo_write_mask(sdev, SDO_IRQ, ~0, SDO_VSYNC_IRQ_PEND);
99 return IRQ_HANDLED;
100}
101
102static void sdo_reg_debug(struct sdo_device *sdev)
103{
104#define DBGREG(reg_id) \
105 dev_info(sdev->dev, #reg_id " = %08x\n", \
106 sdo_read(sdev, reg_id))
107
108 DBGREG(SDO_CLKCON);
109 DBGREG(SDO_CONFIG);
110 DBGREG(SDO_VBI);
111 DBGREG(SDO_DAC);
112 DBGREG(SDO_IRQ);
113 DBGREG(SDO_IRQMASK);
114 DBGREG(SDO_VERSION);
115}
116
117static const struct sdo_format sdo_format[] = {
118 { V4L2_STD_PAL_N, .height = 576, .cookie = SDO_PAL_N },
119 { V4L2_STD_PAL_Nc, .height = 576, .cookie = SDO_PAL_NC },
120 { V4L2_STD_PAL_M, .height = 480, .cookie = SDO_PAL_M },
121 { V4L2_STD_PAL_60, .height = 480, .cookie = SDO_PAL_60 },
122 { V4L2_STD_NTSC_443, .height = 480, .cookie = SDO_NTSC_443 },
123 { V4L2_STD_PAL, .height = 576, .cookie = SDO_PAL_BGHID },
124 { V4L2_STD_NTSC_M, .height = 480, .cookie = SDO_NTSC_M },
125};
126
127static const struct sdo_format *sdo_find_format(v4l2_std_id id)
128{
129 int i;
130 for (i = 0; i < ARRAY_SIZE(sdo_format); ++i)
131 if (sdo_format[i].id & id)
132 return &sdo_format[i];
133 return NULL;
134}
135
136static int sdo_g_tvnorms_output(struct v4l2_subdev *sd, v4l2_std_id *std)
137{
138 *std = V4L2_STD_NTSC_M | V4L2_STD_PAL_M | V4L2_STD_PAL |
139 V4L2_STD_PAL_N | V4L2_STD_PAL_Nc |
140 V4L2_STD_NTSC_443 | V4L2_STD_PAL_60;
141 return 0;
142}
143
144static int sdo_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std)
145{
146 struct sdo_device *sdev = sd_to_sdev(sd);
147 const struct sdo_format *fmt;
148 fmt = sdo_find_format(std);
149 if (fmt == NULL)
150 return -EINVAL;
151 sdev->fmt = fmt;
152 return 0;
153}
154
155static int sdo_g_std_output(struct v4l2_subdev *sd, v4l2_std_id *std)
156{
157 *std = sd_to_sdev(sd)->fmt->id;
158 return 0;
159}
160
161static int sdo_g_mbus_fmt(struct v4l2_subdev *sd,
162 struct v4l2_mbus_framefmt *fmt)
163{
164 struct sdo_device *sdev = sd_to_sdev(sd);
165
166 if (!sdev->fmt)
167 return -ENXIO;
168 /* all modes are 720 pixels wide */
169 fmt->width = 720;
170 fmt->height = sdev->fmt->height;
171 fmt->code = V4L2_MBUS_FMT_FIXED;
172 fmt->field = V4L2_FIELD_INTERLACED;
173 return 0;
174}
175
176static int sdo_s_power(struct v4l2_subdev *sd, int on)
177{
178 struct sdo_device *sdev = sd_to_sdev(sd);
179 struct device *dev = sdev->dev;
180 int ret;
181
182 dev_info(dev, "sdo_s_power(%d)\n", on);
183
184 if (on)
185 ret = pm_runtime_get_sync(dev);
186 else
187 ret = pm_runtime_put_sync(dev);
188
189 /* only values < 0 indicate errors */
190 return IS_ERR_VALUE(ret) ? ret : 0;
191}
192
193static int sdo_streamon(struct sdo_device *sdev)
194{
195 /* set proper clock for Timing Generator */
196 clk_set_rate(sdev->fout_vpll, 54000000);
197 dev_info(sdev->dev, "fout_vpll.rate = %lu\n",
198 clk_get_rate(sdev->fout_vpll));
199 /* enable clock in SDO */
200 sdo_write_mask(sdev, SDO_CLKCON, ~0, SDO_TVOUT_CLOCK_ON);
201 clk_enable(sdev->dacphy);
202 /* enable DAC */
203 sdo_write_mask(sdev, SDO_DAC, ~0, SDO_POWER_ON_DAC);
204 sdo_reg_debug(sdev);
205 return 0;
206}
207
208static int sdo_streamoff(struct sdo_device *sdev)
209{
210 int tries;
211
212 sdo_write_mask(sdev, SDO_DAC, 0, SDO_POWER_ON_DAC);
213 clk_disable(sdev->dacphy);
214 sdo_write_mask(sdev, SDO_CLKCON, 0, SDO_TVOUT_CLOCK_ON);
215 for (tries = 100; tries; --tries) {
216 if (sdo_read(sdev, SDO_CLKCON) & SDO_TVOUT_CLOCK_READY)
217 break;
218 mdelay(1);
219 }
220 if (tries == 0)
221 dev_err(sdev->dev, "failed to stop streaming\n");
222 return tries ? 0 : -EIO;
223}
224
225static int sdo_s_stream(struct v4l2_subdev *sd, int on)
226{
227 struct sdo_device *sdev = sd_to_sdev(sd);
228 return on ? sdo_streamon(sdev) : sdo_streamoff(sdev);
229}
230
231static const struct v4l2_subdev_core_ops sdo_sd_core_ops = {
232 .s_power = sdo_s_power,
233};
234
235static const struct v4l2_subdev_video_ops sdo_sd_video_ops = {
236 .s_std_output = sdo_s_std_output,
237 .g_std_output = sdo_g_std_output,
238 .g_tvnorms_output = sdo_g_tvnorms_output,
239 .g_mbus_fmt = sdo_g_mbus_fmt,
240 .s_stream = sdo_s_stream,
241};
242
243static const struct v4l2_subdev_ops sdo_sd_ops = {
244 .core = &sdo_sd_core_ops,
245 .video = &sdo_sd_video_ops,
246};
247
248static int sdo_runtime_suspend(struct device *dev)
249{
250 struct v4l2_subdev *sd = dev_get_drvdata(dev);
251 struct sdo_device *sdev = sd_to_sdev(sd);
252
253 dev_info(dev, "suspend\n");
254 regulator_disable(sdev->vdet);
255 regulator_disable(sdev->vdac);
256 clk_disable(sdev->sclk_dac);
257 return 0;
258}
259
260static int sdo_runtime_resume(struct device *dev)
261{
262 struct v4l2_subdev *sd = dev_get_drvdata(dev);
263 struct sdo_device *sdev = sd_to_sdev(sd);
264
265 dev_info(dev, "resume\n");
266 clk_enable(sdev->sclk_dac);
267 regulator_enable(sdev->vdac);
268 regulator_enable(sdev->vdet);
269
270 /* software reset */
271 sdo_write_mask(sdev, SDO_CLKCON, ~0, SDO_TVOUT_SW_RESET);
272 mdelay(10);
273 sdo_write_mask(sdev, SDO_CLKCON, 0, SDO_TVOUT_SW_RESET);
274
275 /* setting TV mode */
276 sdo_write_mask(sdev, SDO_CONFIG, sdev->fmt->cookie, SDO_STANDARD_MASK);
277 /* XXX: forcing interlaced mode using undocumented bit */
278 sdo_write_mask(sdev, SDO_CONFIG, 0, SDO_PROGRESSIVE);
279 /* turn all VBI off */
280 sdo_write_mask(sdev, SDO_VBI, 0, SDO_CVBS_WSS_INS |
281 SDO_CVBS_CLOSED_CAPTION_MASK);
282 /* turn all post processing off */
283 sdo_write_mask(sdev, SDO_CCCON, ~0, SDO_COMPENSATION_BHS_ADJ_OFF |
284 SDO_COMPENSATION_CVBS_COMP_OFF);
285 sdo_reg_debug(sdev);
286 return 0;
287}
288
289static const struct dev_pm_ops sdo_pm_ops = {
290 .runtime_suspend = sdo_runtime_suspend,
291 .runtime_resume = sdo_runtime_resume,
292};
293
294static int __devinit sdo_probe(struct platform_device *pdev)
295{
296 struct device *dev = &pdev->dev;
297 struct sdo_device *sdev;
298 struct resource *res;
299 int ret = 0;
300 struct clk *sclk_vpll;
301
302 dev_info(dev, "probe start\n");
303 sdev = kzalloc(sizeof *sdev, GFP_KERNEL);
304 if (!sdev) {
305 dev_err(dev, "not enough memory.\n");
306 ret = -ENOMEM;
307 goto fail;
308 }
309 sdev->dev = dev;
310
311 /* mapping registers */
312 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
313 if (res == NULL) {
314 dev_err(dev, "get memory resource failed.\n");
315 ret = -ENXIO;
316 goto fail_sdev;
317 }
318
319 sdev->regs = ioremap(res->start, resource_size(res));
320 if (sdev->regs == NULL) {
321 dev_err(dev, "register mapping failed.\n");
322 ret = -ENXIO;
323 goto fail_sdev;
324 }
325
326 /* acquiring interrupt */
327 res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
328 if (res == NULL) {
329 dev_err(dev, "get interrupt resource failed.\n");
330 ret = -ENXIO;
331 goto fail_regs;
332 }
333 ret = request_irq(res->start, sdo_irq_handler, 0, "s5p-sdo", sdev);
334 if (ret) {
335 dev_err(dev, "request interrupt failed.\n");
336 goto fail_regs;
337 }
338 sdev->irq = res->start;
339
340 /* acquire clocks */
341 sdev->sclk_dac = clk_get(dev, "sclk_dac");
342 if (IS_ERR_OR_NULL(sdev->sclk_dac)) {
343 dev_err(dev, "failed to get clock 'sclk_dac'\n");
344 ret = -ENXIO;
345 goto fail_irq;
346 }
347 sdev->dac = clk_get(dev, "dac");
348 if (IS_ERR_OR_NULL(sdev->dac)) {
349 dev_err(dev, "failed to get clock 'dac'\n");
350 ret = -ENXIO;
351 goto fail_sclk_dac;
352 }
353 sdev->dacphy = clk_get(dev, "dacphy");
354 if (IS_ERR_OR_NULL(sdev->dacphy)) {
355 dev_err(dev, "failed to get clock 'dacphy'\n");
356 ret = -ENXIO;
357 goto fail_dac;
358 }
359 sclk_vpll = clk_get(dev, "sclk_vpll");
360 if (IS_ERR_OR_NULL(sclk_vpll)) {
361 dev_err(dev, "failed to get clock 'sclk_vpll'\n");
362 ret = -ENXIO;
363 goto fail_dacphy;
364 }
365 clk_set_parent(sdev->sclk_dac, sclk_vpll);
366 clk_put(sclk_vpll);
367 sdev->fout_vpll = clk_get(dev, "fout_vpll");
368 if (IS_ERR_OR_NULL(sdev->fout_vpll)) {
369 dev_err(dev, "failed to get clock 'fout_vpll'\n");
370 goto fail_dacphy;
371 }
372 dev_info(dev, "fout_vpll.rate = %lu\n", clk_get_rate(sclk_vpll));
373
374 /* acquire regulator */
375 sdev->vdac = regulator_get(dev, "vdd33a_dac");
376 if (IS_ERR_OR_NULL(sdev->vdac)) {
377 dev_err(dev, "failed to get regulator 'vdac'\n");
378 goto fail_fout_vpll;
379 }
380 sdev->vdet = regulator_get(dev, "vdet");
381 if (IS_ERR_OR_NULL(sdev->vdet)) {
382 dev_err(dev, "failed to get regulator 'vdet'\n");
383 goto fail_vdac;
384 }
385
386 /* enable gate for dac clock, because mixer uses it */
387 clk_enable(sdev->dac);
388
389 /* configure power management */
390 pm_runtime_enable(dev);
391
392 /* configuration of interface subdevice */
393 v4l2_subdev_init(&sdev->sd, &sdo_sd_ops);
394 sdev->sd.owner = THIS_MODULE;
395 strlcpy(sdev->sd.name, "s5p-sdo", sizeof sdev->sd.name);
396
397 /* set default format */
398 sdev->fmt = sdo_find_format(SDO_DEFAULT_STD);
399 BUG_ON(sdev->fmt == NULL);
400
401 /* keeping subdev in device's private for use by other drivers */
402 dev_set_drvdata(dev, &sdev->sd);
403
404 dev_info(dev, "probe succeeded\n");
405 return 0;
406
407fail_vdac:
408 regulator_put(sdev->vdac);
409fail_fout_vpll:
410 clk_put(sdev->fout_vpll);
411fail_dacphy:
412 clk_put(sdev->dacphy);
413fail_dac:
414 clk_put(sdev->dac);
415fail_sclk_dac:
416 clk_put(sdev->sclk_dac);
417fail_irq:
418 free_irq(sdev->irq, sdev);
419fail_regs:
420 iounmap(sdev->regs);
421fail_sdev:
422 kfree(sdev);
423fail:
424 dev_info(dev, "probe failed\n");
425 return ret;
426}
427
428static int __devexit sdo_remove(struct platform_device *pdev)
429{
430 struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev);
431 struct sdo_device *sdev = sd_to_sdev(sd);
432
433 pm_runtime_disable(&pdev->dev);
434 clk_disable(sdev->dac);
435 regulator_put(sdev->vdet);
436 regulator_put(sdev->vdac);
437 clk_put(sdev->fout_vpll);
438 clk_put(sdev->dacphy);
439 clk_put(sdev->dac);
440 clk_put(sdev->sclk_dac);
441 free_irq(sdev->irq, sdev);
442 iounmap(sdev->regs);
443 kfree(sdev);
444
445 dev_info(&pdev->dev, "remove successful\n");
446 return 0;
447}
448
449static struct platform_driver sdo_driver __refdata = {
450 .probe = sdo_probe,
451 .remove = __devexit_p(sdo_remove),
452 .driver = {
453 .name = "s5p-sdo",
454 .owner = THIS_MODULE,
455 .pm = &sdo_pm_ops,
456 }
457};
458
459static int __init sdo_init(void)
460{
461 int ret;
462 static const char banner[] __initdata = KERN_INFO \
463 "Samsung Standard Definition Output (SDO) driver, "
464 "(c) 2010-2011 Samsung Electronics Co., Ltd.\n";
465 printk(banner);
466
467 ret = platform_driver_register(&sdo_driver);
468 if (ret)
469 printk(KERN_ERR "SDO platform driver register failed\n");
470
471 return ret;
472}
473module_init(sdo_init);
474
475static void __exit sdo_exit(void)
476{
477 platform_driver_unregister(&sdo_driver);
478}
479module_exit(sdo_exit);