Commit | Line | Data |
---|---|---|
9a498400 TS |
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 | ||
30 | MODULE_AUTHOR("Tomasz Stanislawski, <t.stanislaws@samsung.com>"); | |
31 | MODULE_DESCRIPTION("Samsung Standard Definition Output (SDO)"); | |
32 | MODULE_LICENSE("GPL"); | |
33 | ||
34 | #define SDO_DEFAULT_STD V4L2_STD_PAL | |
35 | ||
36 | struct sdo_format { | |
37 | v4l2_std_id id; | |
38 | /* all modes are 720 pixels wide */ | |
39 | unsigned int height; | |
40 | unsigned int cookie; | |
41 | }; | |
42 | ||
43 | struct 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; | |
0495d405 MK |
58 | /** vpll rate before sdo stream was on */ |
59 | unsigned long vpll_rate; | |
9a498400 TS |
60 | /** regulator for SDO IP power */ |
61 | struct regulator *vdac; | |
62 | /** regulator for SDO plug detection */ | |
63 | struct regulator *vdet; | |
64 | /** subdev used as device interface */ | |
65 | struct v4l2_subdev sd; | |
66 | /** current format */ | |
67 | const struct sdo_format *fmt; | |
68 | }; | |
69 | ||
70 | static inline struct sdo_device *sd_to_sdev(struct v4l2_subdev *sd) | |
71 | { | |
72 | return container_of(sd, struct sdo_device, sd); | |
73 | } | |
74 | ||
75 | static inline | |
76 | void sdo_write_mask(struct sdo_device *sdev, u32 reg_id, u32 value, u32 mask) | |
77 | { | |
78 | u32 old = readl(sdev->regs + reg_id); | |
79 | value = (value & mask) | (old & ~mask); | |
80 | writel(value, sdev->regs + reg_id); | |
81 | } | |
82 | ||
83 | static inline | |
84 | void sdo_write(struct sdo_device *sdev, u32 reg_id, u32 value) | |
85 | { | |
86 | writel(value, sdev->regs + reg_id); | |
87 | } | |
88 | ||
89 | static inline | |
90 | u32 sdo_read(struct sdo_device *sdev, u32 reg_id) | |
91 | { | |
92 | return readl(sdev->regs + reg_id); | |
93 | } | |
94 | ||
95 | static irqreturn_t sdo_irq_handler(int irq, void *dev_data) | |
96 | { | |
97 | struct sdo_device *sdev = dev_data; | |
98 | ||
99 | /* clear interrupt */ | |
100 | sdo_write_mask(sdev, SDO_IRQ, ~0, SDO_VSYNC_IRQ_PEND); | |
101 | return IRQ_HANDLED; | |
102 | } | |
103 | ||
104 | static void sdo_reg_debug(struct sdo_device *sdev) | |
105 | { | |
106 | #define DBGREG(reg_id) \ | |
107 | dev_info(sdev->dev, #reg_id " = %08x\n", \ | |
108 | sdo_read(sdev, reg_id)) | |
109 | ||
110 | DBGREG(SDO_CLKCON); | |
111 | DBGREG(SDO_CONFIG); | |
112 | DBGREG(SDO_VBI); | |
113 | DBGREG(SDO_DAC); | |
114 | DBGREG(SDO_IRQ); | |
115 | DBGREG(SDO_IRQMASK); | |
116 | DBGREG(SDO_VERSION); | |
117 | } | |
118 | ||
119 | static const struct sdo_format sdo_format[] = { | |
120 | { V4L2_STD_PAL_N, .height = 576, .cookie = SDO_PAL_N }, | |
121 | { V4L2_STD_PAL_Nc, .height = 576, .cookie = SDO_PAL_NC }, | |
122 | { V4L2_STD_PAL_M, .height = 480, .cookie = SDO_PAL_M }, | |
123 | { V4L2_STD_PAL_60, .height = 480, .cookie = SDO_PAL_60 }, | |
124 | { V4L2_STD_NTSC_443, .height = 480, .cookie = SDO_NTSC_443 }, | |
125 | { V4L2_STD_PAL, .height = 576, .cookie = SDO_PAL_BGHID }, | |
126 | { V4L2_STD_NTSC_M, .height = 480, .cookie = SDO_NTSC_M }, | |
127 | }; | |
128 | ||
129 | static const struct sdo_format *sdo_find_format(v4l2_std_id id) | |
130 | { | |
131 | int i; | |
132 | for (i = 0; i < ARRAY_SIZE(sdo_format); ++i) | |
133 | if (sdo_format[i].id & id) | |
134 | return &sdo_format[i]; | |
135 | return NULL; | |
136 | } | |
137 | ||
138 | static int sdo_g_tvnorms_output(struct v4l2_subdev *sd, v4l2_std_id *std) | |
139 | { | |
140 | *std = V4L2_STD_NTSC_M | V4L2_STD_PAL_M | V4L2_STD_PAL | | |
141 | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | | |
142 | V4L2_STD_NTSC_443 | V4L2_STD_PAL_60; | |
143 | return 0; | |
144 | } | |
145 | ||
146 | static int sdo_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) | |
147 | { | |
148 | struct sdo_device *sdev = sd_to_sdev(sd); | |
149 | const struct sdo_format *fmt; | |
150 | fmt = sdo_find_format(std); | |
151 | if (fmt == NULL) | |
152 | return -EINVAL; | |
153 | sdev->fmt = fmt; | |
154 | return 0; | |
155 | } | |
156 | ||
157 | static int sdo_g_std_output(struct v4l2_subdev *sd, v4l2_std_id *std) | |
158 | { | |
159 | *std = sd_to_sdev(sd)->fmt->id; | |
160 | return 0; | |
161 | } | |
162 | ||
da298c6d HV |
163 | static int sdo_get_fmt(struct v4l2_subdev *sd, |
164 | struct v4l2_subdev_pad_config *cfg, | |
165 | struct v4l2_subdev_format *format) | |
9a498400 | 166 | { |
da298c6d | 167 | struct v4l2_mbus_framefmt *fmt = &format->format; |
9a498400 TS |
168 | struct sdo_device *sdev = sd_to_sdev(sd); |
169 | ||
170 | if (!sdev->fmt) | |
171 | return -ENXIO; | |
da298c6d HV |
172 | if (format->pad) |
173 | return -EINVAL; | |
9a498400 TS |
174 | /* all modes are 720 pixels wide */ |
175 | fmt->width = 720; | |
176 | fmt->height = sdev->fmt->height; | |
27ffaeb0 | 177 | fmt->code = MEDIA_BUS_FMT_FIXED; |
9a498400 | 178 | fmt->field = V4L2_FIELD_INTERLACED; |
0689133b | 179 | fmt->colorspace = V4L2_COLORSPACE_JPEG; |
9a498400 TS |
180 | return 0; |
181 | } | |
182 | ||
183 | static int sdo_s_power(struct v4l2_subdev *sd, int on) | |
184 | { | |
185 | struct sdo_device *sdev = sd_to_sdev(sd); | |
186 | struct device *dev = sdev->dev; | |
187 | int ret; | |
188 | ||
189 | dev_info(dev, "sdo_s_power(%d)\n", on); | |
190 | ||
191 | if (on) | |
192 | ret = pm_runtime_get_sync(dev); | |
193 | else | |
194 | ret = pm_runtime_put_sync(dev); | |
195 | ||
196 | /* only values < 0 indicate errors */ | |
d8e8b40c | 197 | return ret < 0 ? ret : 0; |
9a498400 TS |
198 | } |
199 | ||
200 | static int sdo_streamon(struct sdo_device *sdev) | |
201 | { | |
0495d405 MK |
202 | int ret; |
203 | ||
9a498400 | 204 | /* set proper clock for Timing Generator */ |
0495d405 MK |
205 | sdev->vpll_rate = clk_get_rate(sdev->fout_vpll); |
206 | ret = clk_set_rate(sdev->fout_vpll, 54000000); | |
207 | if (ret < 0) { | |
208 | dev_err(sdev->dev, "Failed to set vpll rate\n"); | |
209 | return ret; | |
210 | } | |
9a498400 TS |
211 | dev_info(sdev->dev, "fout_vpll.rate = %lu\n", |
212 | clk_get_rate(sdev->fout_vpll)); | |
213 | /* enable clock in SDO */ | |
214 | sdo_write_mask(sdev, SDO_CLKCON, ~0, SDO_TVOUT_CLOCK_ON); | |
a889c115 | 215 | ret = clk_prepare_enable(sdev->dacphy); |
0495d405 | 216 | if (ret < 0) { |
a889c115 | 217 | dev_err(sdev->dev, "clk_prepare_enable(dacphy) failed\n"); |
0495d405 MK |
218 | goto fail; |
219 | } | |
9a498400 TS |
220 | /* enable DAC */ |
221 | sdo_write_mask(sdev, SDO_DAC, ~0, SDO_POWER_ON_DAC); | |
222 | sdo_reg_debug(sdev); | |
223 | return 0; | |
0495d405 MK |
224 | |
225 | fail: | |
226 | sdo_write_mask(sdev, SDO_CLKCON, 0, SDO_TVOUT_CLOCK_ON); | |
227 | clk_set_rate(sdev->fout_vpll, sdev->vpll_rate); | |
228 | return ret; | |
9a498400 TS |
229 | } |
230 | ||
231 | static int sdo_streamoff(struct sdo_device *sdev) | |
232 | { | |
233 | int tries; | |
234 | ||
235 | sdo_write_mask(sdev, SDO_DAC, 0, SDO_POWER_ON_DAC); | |
a889c115 | 236 | clk_disable_unprepare(sdev->dacphy); |
9a498400 TS |
237 | sdo_write_mask(sdev, SDO_CLKCON, 0, SDO_TVOUT_CLOCK_ON); |
238 | for (tries = 100; tries; --tries) { | |
239 | if (sdo_read(sdev, SDO_CLKCON) & SDO_TVOUT_CLOCK_READY) | |
240 | break; | |
241 | mdelay(1); | |
242 | } | |
243 | if (tries == 0) | |
244 | dev_err(sdev->dev, "failed to stop streaming\n"); | |
0495d405 | 245 | clk_set_rate(sdev->fout_vpll, sdev->vpll_rate); |
9a498400 TS |
246 | return tries ? 0 : -EIO; |
247 | } | |
248 | ||
249 | static int sdo_s_stream(struct v4l2_subdev *sd, int on) | |
250 | { | |
251 | struct sdo_device *sdev = sd_to_sdev(sd); | |
252 | return on ? sdo_streamon(sdev) : sdo_streamoff(sdev); | |
253 | } | |
254 | ||
255 | static const struct v4l2_subdev_core_ops sdo_sd_core_ops = { | |
256 | .s_power = sdo_s_power, | |
257 | }; | |
258 | ||
259 | static const struct v4l2_subdev_video_ops sdo_sd_video_ops = { | |
260 | .s_std_output = sdo_s_std_output, | |
261 | .g_std_output = sdo_g_std_output, | |
262 | .g_tvnorms_output = sdo_g_tvnorms_output, | |
9a498400 TS |
263 | .s_stream = sdo_s_stream, |
264 | }; | |
265 | ||
da298c6d HV |
266 | static const struct v4l2_subdev_pad_ops sdo_sd_pad_ops = { |
267 | .get_fmt = sdo_get_fmt, | |
268 | }; | |
269 | ||
9a498400 TS |
270 | static const struct v4l2_subdev_ops sdo_sd_ops = { |
271 | .core = &sdo_sd_core_ops, | |
272 | .video = &sdo_sd_video_ops, | |
da298c6d | 273 | .pad = &sdo_sd_pad_ops, |
9a498400 TS |
274 | }; |
275 | ||
276 | static int sdo_runtime_suspend(struct device *dev) | |
277 | { | |
278 | struct v4l2_subdev *sd = dev_get_drvdata(dev); | |
279 | struct sdo_device *sdev = sd_to_sdev(sd); | |
280 | ||
281 | dev_info(dev, "suspend\n"); | |
282 | regulator_disable(sdev->vdet); | |
283 | regulator_disable(sdev->vdac); | |
a889c115 | 284 | clk_disable_unprepare(sdev->sclk_dac); |
9a498400 TS |
285 | return 0; |
286 | } | |
287 | ||
288 | static int sdo_runtime_resume(struct device *dev) | |
289 | { | |
290 | struct v4l2_subdev *sd = dev_get_drvdata(dev); | |
291 | struct sdo_device *sdev = sd_to_sdev(sd); | |
d285837e | 292 | int ret; |
9a498400 TS |
293 | |
294 | dev_info(dev, "resume\n"); | |
d285837e | 295 | |
a889c115 | 296 | ret = clk_prepare_enable(sdev->sclk_dac); |
d285837e SN |
297 | if (ret < 0) |
298 | return ret; | |
299 | ||
300 | ret = regulator_enable(sdev->vdac); | |
301 | if (ret < 0) | |
302 | goto dac_clk_dis; | |
303 | ||
304 | ret = regulator_enable(sdev->vdet); | |
305 | if (ret < 0) | |
306 | goto vdac_r_dis; | |
9a498400 TS |
307 | |
308 | /* software reset */ | |
309 | sdo_write_mask(sdev, SDO_CLKCON, ~0, SDO_TVOUT_SW_RESET); | |
310 | mdelay(10); | |
311 | sdo_write_mask(sdev, SDO_CLKCON, 0, SDO_TVOUT_SW_RESET); | |
312 | ||
313 | /* setting TV mode */ | |
314 | sdo_write_mask(sdev, SDO_CONFIG, sdev->fmt->cookie, SDO_STANDARD_MASK); | |
315 | /* XXX: forcing interlaced mode using undocumented bit */ | |
316 | sdo_write_mask(sdev, SDO_CONFIG, 0, SDO_PROGRESSIVE); | |
317 | /* turn all VBI off */ | |
318 | sdo_write_mask(sdev, SDO_VBI, 0, SDO_CVBS_WSS_INS | | |
319 | SDO_CVBS_CLOSED_CAPTION_MASK); | |
320 | /* turn all post processing off */ | |
321 | sdo_write_mask(sdev, SDO_CCCON, ~0, SDO_COMPENSATION_BHS_ADJ_OFF | | |
322 | SDO_COMPENSATION_CVBS_COMP_OFF); | |
323 | sdo_reg_debug(sdev); | |
324 | return 0; | |
d285837e SN |
325 | |
326 | vdac_r_dis: | |
327 | regulator_disable(sdev->vdac); | |
328 | dac_clk_dis: | |
a889c115 | 329 | clk_disable_unprepare(sdev->sclk_dac); |
d285837e | 330 | return ret; |
9a498400 TS |
331 | } |
332 | ||
333 | static const struct dev_pm_ops sdo_pm_ops = { | |
334 | .runtime_suspend = sdo_runtime_suspend, | |
335 | .runtime_resume = sdo_runtime_resume, | |
336 | }; | |
337 | ||
4c62e976 | 338 | static int sdo_probe(struct platform_device *pdev) |
9a498400 TS |
339 | { |
340 | struct device *dev = &pdev->dev; | |
341 | struct sdo_device *sdev; | |
342 | struct resource *res; | |
343 | int ret = 0; | |
344 | struct clk *sclk_vpll; | |
345 | ||
346 | dev_info(dev, "probe start\n"); | |
80f0dee2 | 347 | sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); |
9a498400 TS |
348 | if (!sdev) { |
349 | dev_err(dev, "not enough memory.\n"); | |
350 | ret = -ENOMEM; | |
351 | goto fail; | |
352 | } | |
353 | sdev->dev = dev; | |
354 | ||
355 | /* mapping registers */ | |
356 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
357 | if (res == NULL) { | |
358 | dev_err(dev, "get memory resource failed.\n"); | |
359 | ret = -ENXIO; | |
e861dccc | 360 | goto fail; |
9a498400 TS |
361 | } |
362 | ||
e861dccc | 363 | sdev->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); |
9a498400 TS |
364 | if (sdev->regs == NULL) { |
365 | dev_err(dev, "register mapping failed.\n"); | |
366 | ret = -ENXIO; | |
e861dccc | 367 | goto fail; |
9a498400 TS |
368 | } |
369 | ||
370 | /* acquiring interrupt */ | |
371 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
372 | if (res == NULL) { | |
373 | dev_err(dev, "get interrupt resource failed.\n"); | |
374 | ret = -ENXIO; | |
e861dccc | 375 | goto fail; |
9a498400 | 376 | } |
e861dccc JL |
377 | ret = devm_request_irq(&pdev->dev, res->start, sdo_irq_handler, 0, |
378 | "s5p-sdo", sdev); | |
9a498400 TS |
379 | if (ret) { |
380 | dev_err(dev, "request interrupt failed.\n"); | |
e861dccc | 381 | goto fail; |
9a498400 TS |
382 | } |
383 | sdev->irq = res->start; | |
384 | ||
385 | /* acquire clocks */ | |
386 | sdev->sclk_dac = clk_get(dev, "sclk_dac"); | |
cf48f56c | 387 | if (IS_ERR(sdev->sclk_dac)) { |
9a498400 | 388 | dev_err(dev, "failed to get clock 'sclk_dac'\n"); |
cf48f56c | 389 | ret = PTR_ERR(sdev->sclk_dac); |
e861dccc | 390 | goto fail; |
9a498400 TS |
391 | } |
392 | sdev->dac = clk_get(dev, "dac"); | |
cf48f56c | 393 | if (IS_ERR(sdev->dac)) { |
9a498400 | 394 | dev_err(dev, "failed to get clock 'dac'\n"); |
cf48f56c | 395 | ret = PTR_ERR(sdev->dac); |
9a498400 TS |
396 | goto fail_sclk_dac; |
397 | } | |
398 | sdev->dacphy = clk_get(dev, "dacphy"); | |
cf48f56c | 399 | if (IS_ERR(sdev->dacphy)) { |
9a498400 | 400 | dev_err(dev, "failed to get clock 'dacphy'\n"); |
cf48f56c | 401 | ret = PTR_ERR(sdev->dacphy); |
9a498400 TS |
402 | goto fail_dac; |
403 | } | |
404 | sclk_vpll = clk_get(dev, "sclk_vpll"); | |
cf48f56c | 405 | if (IS_ERR(sclk_vpll)) { |
9a498400 | 406 | dev_err(dev, "failed to get clock 'sclk_vpll'\n"); |
cf48f56c | 407 | ret = PTR_ERR(sclk_vpll); |
9a498400 TS |
408 | goto fail_dacphy; |
409 | } | |
410 | clk_set_parent(sdev->sclk_dac, sclk_vpll); | |
411 | clk_put(sclk_vpll); | |
412 | sdev->fout_vpll = clk_get(dev, "fout_vpll"); | |
cf48f56c | 413 | if (IS_ERR(sdev->fout_vpll)) { |
9a498400 | 414 | dev_err(dev, "failed to get clock 'fout_vpll'\n"); |
cf48f56c | 415 | ret = PTR_ERR(sdev->fout_vpll); |
9a498400 TS |
416 | goto fail_dacphy; |
417 | } | |
418 | dev_info(dev, "fout_vpll.rate = %lu\n", clk_get_rate(sclk_vpll)); | |
419 | ||
420 | /* acquire regulator */ | |
00d98c1b | 421 | sdev->vdac = devm_regulator_get(dev, "vdd33a_dac"); |
cf48f56c | 422 | if (IS_ERR(sdev->vdac)) { |
9a498400 | 423 | dev_err(dev, "failed to get regulator 'vdac'\n"); |
cf48f56c | 424 | ret = PTR_ERR(sdev->vdac); |
9a498400 TS |
425 | goto fail_fout_vpll; |
426 | } | |
00d98c1b | 427 | sdev->vdet = devm_regulator_get(dev, "vdet"); |
cf48f56c | 428 | if (IS_ERR(sdev->vdet)) { |
9a498400 | 429 | dev_err(dev, "failed to get regulator 'vdet'\n"); |
cf48f56c | 430 | ret = PTR_ERR(sdev->vdet); |
00d98c1b | 431 | goto fail_fout_vpll; |
9a498400 TS |
432 | } |
433 | ||
434 | /* enable gate for dac clock, because mixer uses it */ | |
a889c115 MK |
435 | ret = clk_prepare_enable(sdev->dac); |
436 | if (ret < 0) { | |
437 | dev_err(dev, "clk_prepare_enable(dac) failed\n"); | |
438 | goto fail_fout_vpll; | |
439 | } | |
9a498400 TS |
440 | |
441 | /* configure power management */ | |
442 | pm_runtime_enable(dev); | |
443 | ||
444 | /* configuration of interface subdevice */ | |
445 | v4l2_subdev_init(&sdev->sd, &sdo_sd_ops); | |
446 | sdev->sd.owner = THIS_MODULE; | |
80f0dee2 | 447 | strlcpy(sdev->sd.name, "s5p-sdo", sizeof(sdev->sd.name)); |
9a498400 TS |
448 | |
449 | /* set default format */ | |
450 | sdev->fmt = sdo_find_format(SDO_DEFAULT_STD); | |
451 | BUG_ON(sdev->fmt == NULL); | |
452 | ||
453 | /* keeping subdev in device's private for use by other drivers */ | |
454 | dev_set_drvdata(dev, &sdev->sd); | |
455 | ||
456 | dev_info(dev, "probe succeeded\n"); | |
457 | return 0; | |
458 | ||
9a498400 TS |
459 | fail_fout_vpll: |
460 | clk_put(sdev->fout_vpll); | |
461 | fail_dacphy: | |
462 | clk_put(sdev->dacphy); | |
463 | fail_dac: | |
464 | clk_put(sdev->dac); | |
465 | fail_sclk_dac: | |
466 | clk_put(sdev->sclk_dac); | |
9a498400 TS |
467 | fail: |
468 | dev_info(dev, "probe failed\n"); | |
469 | return ret; | |
470 | } | |
471 | ||
4c62e976 | 472 | static int sdo_remove(struct platform_device *pdev) |
9a498400 TS |
473 | { |
474 | struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev); | |
475 | struct sdo_device *sdev = sd_to_sdev(sd); | |
476 | ||
477 | pm_runtime_disable(&pdev->dev); | |
a889c115 | 478 | clk_disable_unprepare(sdev->dac); |
9a498400 TS |
479 | clk_put(sdev->fout_vpll); |
480 | clk_put(sdev->dacphy); | |
481 | clk_put(sdev->dac); | |
482 | clk_put(sdev->sclk_dac); | |
9a498400 TS |
483 | |
484 | dev_info(&pdev->dev, "remove successful\n"); | |
485 | return 0; | |
486 | } | |
487 | ||
488 | static struct platform_driver sdo_driver __refdata = { | |
489 | .probe = sdo_probe, | |
4c62e976 | 490 | .remove = sdo_remove, |
9a498400 TS |
491 | .driver = { |
492 | .name = "s5p-sdo", | |
9a498400 TS |
493 | .pm = &sdo_pm_ops, |
494 | } | |
495 | }; | |
496 | ||
1d6629b1 | 497 | module_platform_driver(sdo_driver); |