.. | .. |
---|
7 | 7 | * it under the terms of the GNU General Public License version 2 as |
---|
8 | 8 | * published by the Free Software Foundation. |
---|
9 | 9 | */ |
---|
| 10 | +#include <linux/input.h> |
---|
10 | 11 | #include <linux/interrupt.h> |
---|
11 | 12 | #include <linux/io.h> |
---|
| 13 | +#include <linux/miscdevice.h> |
---|
12 | 14 | #include <linux/module.h> |
---|
13 | 15 | #include <linux/platform_device.h> |
---|
14 | 16 | #include <linux/sched.h> |
---|
.. | .. |
---|
24 | 26 | enum { |
---|
25 | 27 | HDMI_IH_CEC_STAT0 = 0x0106, |
---|
26 | 28 | HDMI_IH_MUTE_CEC_STAT0 = 0x0186, |
---|
| 29 | + HDMI_IH_MUTE = 0x01ff, |
---|
27 | 30 | |
---|
28 | 31 | HDMI_CEC_CTRL = 0x7d00, |
---|
| 32 | + CEC_TRANS_MASK = 0x7, |
---|
| 33 | + CEC_CTRL_STANDBY = BIT(4), |
---|
29 | 34 | CEC_CTRL_START = BIT(0), |
---|
30 | 35 | CEC_CTRL_FRAME_TYP = 3 << 1, |
---|
31 | 36 | CEC_CTRL_RETRY = 0 << 1, |
---|
.. | .. |
---|
50 | 55 | HDMI_CEC_RX_CNT = 0x7d08, |
---|
51 | 56 | HDMI_CEC_TX_DATA0 = 0x7d10, |
---|
52 | 57 | HDMI_CEC_RX_DATA0 = 0x7d20, |
---|
| 58 | + HDMI_CEC_RX_DATA1 = 0x7d21, |
---|
53 | 59 | HDMI_CEC_LOCK = 0x7d30, |
---|
54 | 60 | HDMI_CEC_WKUPCTRL = 0x7d31, |
---|
55 | 61 | }; |
---|
56 | 62 | |
---|
57 | 63 | struct dw_hdmi_cec { |
---|
| 64 | + struct device *dev; |
---|
58 | 65 | struct dw_hdmi *hdmi; |
---|
| 66 | + struct miscdevice misc_dev; |
---|
59 | 67 | const struct dw_hdmi_cec_ops *ops; |
---|
60 | 68 | u32 addresses; |
---|
61 | 69 | struct cec_adapter *adap; |
---|
.. | .. |
---|
64 | 72 | bool tx_done; |
---|
65 | 73 | bool rx_done; |
---|
66 | 74 | struct cec_notifier *notify; |
---|
| 75 | + struct input_dev *devinput; |
---|
67 | 76 | int irq; |
---|
| 77 | + int wake_irq; |
---|
| 78 | + bool wake_en; |
---|
| 79 | + bool standby_en; |
---|
| 80 | + struct mutex wake_lock; |
---|
68 | 81 | }; |
---|
69 | 82 | |
---|
70 | 83 | static void dw_hdmi_write(struct dw_hdmi_cec *cec, u8 val, int offset) |
---|
.. | .. |
---|
75 | 88 | static u8 dw_hdmi_read(struct dw_hdmi_cec *cec, int offset) |
---|
76 | 89 | { |
---|
77 | 90 | return cec->ops->read(cec->hdmi, offset); |
---|
| 91 | +} |
---|
| 92 | + |
---|
| 93 | +static void dw_hdmi_mod(struct dw_hdmi_cec *cec, unsigned int offset, u8 mask, u8 val) |
---|
| 94 | +{ |
---|
| 95 | + cec->ops->mod(cec->hdmi, val, mask, offset); |
---|
78 | 96 | } |
---|
79 | 97 | |
---|
80 | 98 | static int dw_hdmi_cec_log_addr(struct cec_adapter *adap, u8 logical_addr) |
---|
.. | .. |
---|
115 | 133 | dw_hdmi_write(cec, msg->msg[i], HDMI_CEC_TX_DATA0 + i); |
---|
116 | 134 | |
---|
117 | 135 | dw_hdmi_write(cec, msg->len, HDMI_CEC_TX_CNT); |
---|
118 | | - dw_hdmi_write(cec, ctrl | CEC_CTRL_START, HDMI_CEC_CTRL); |
---|
| 136 | + dw_hdmi_mod(cec, HDMI_CEC_CTRL, CEC_TRANS_MASK, ctrl | CEC_CTRL_START); |
---|
119 | 137 | |
---|
120 | 138 | return 0; |
---|
121 | 139 | } |
---|
.. | .. |
---|
191 | 209 | struct dw_hdmi_cec *cec = cec_get_drvdata(adap); |
---|
192 | 210 | |
---|
193 | 211 | if (!enable) { |
---|
194 | | - dw_hdmi_write(cec, ~0, HDMI_CEC_MASK); |
---|
195 | | - dw_hdmi_write(cec, ~0, HDMI_IH_MUTE_CEC_STAT0); |
---|
196 | 212 | dw_hdmi_write(cec, 0, HDMI_CEC_POLARITY); |
---|
197 | 213 | |
---|
198 | | - cec->ops->disable(cec->hdmi); |
---|
| 214 | + if (cec->wake_en && cec->standby_en) { |
---|
| 215 | + dw_hdmi_write(cec, 0xff, HDMI_IH_CEC_STAT0); |
---|
| 216 | + dw_hdmi_mod(cec, HDMI_CEC_CTRL, CEC_CTRL_STANDBY, CEC_CTRL_STANDBY); |
---|
| 217 | + dw_hdmi_write(cec, 0, HDMI_CEC_LOCK); |
---|
| 218 | + dw_hdmi_write(cec, 0xff, HDMI_CEC_WKUPCTRL); |
---|
| 219 | + dw_hdmi_write(cec, ~(1 << 6), HDMI_CEC_MASK); |
---|
| 220 | + dw_hdmi_write(cec, ~(1 << 6), HDMI_IH_MUTE_CEC_STAT0); |
---|
| 221 | + dw_hdmi_write(cec, 0x01, HDMI_IH_MUTE); |
---|
| 222 | + } else { |
---|
| 223 | + cec->ops->disable(cec->hdmi); |
---|
| 224 | + } |
---|
199 | 225 | } else { |
---|
200 | 226 | unsigned int irqs; |
---|
201 | 227 | |
---|
202 | | - dw_hdmi_write(cec, 0, HDMI_CEC_CTRL); |
---|
| 228 | + dw_hdmi_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID); |
---|
| 229 | + dw_hdmi_mod(cec, HDMI_CEC_CTRL, CEC_CTRL_STANDBY, 0); |
---|
| 230 | + dw_hdmi_write(cec, 0x02, HDMI_IH_MUTE); |
---|
203 | 231 | dw_hdmi_write(cec, ~0, HDMI_IH_CEC_STAT0); |
---|
204 | 232 | dw_hdmi_write(cec, 0, HDMI_CEC_LOCK); |
---|
205 | | - |
---|
206 | | - dw_hdmi_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID); |
---|
207 | 233 | |
---|
208 | 234 | cec->ops->enable(cec->hdmi); |
---|
209 | 235 | |
---|
.. | .. |
---|
229 | 255 | cec_delete_adapter(cec->adap); |
---|
230 | 256 | } |
---|
231 | 257 | |
---|
| 258 | +static irqreturn_t dw_hdmi_cec_wake_irq(int irq, void *data) |
---|
| 259 | +{ |
---|
| 260 | + struct cec_adapter *adap = data; |
---|
| 261 | + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); |
---|
| 262 | + u8 cec_int; |
---|
| 263 | + |
---|
| 264 | + cec_int = dw_hdmi_read(cec, HDMI_IH_CEC_STAT0); |
---|
| 265 | + if (!cec_int) |
---|
| 266 | + return IRQ_NONE; |
---|
| 267 | + |
---|
| 268 | + dw_hdmi_write(cec, 0x02, HDMI_IH_MUTE); |
---|
| 269 | + dw_hdmi_write(cec, cec_int, HDMI_IH_CEC_STAT0); |
---|
| 270 | + dw_hdmi_write(cec, 0x00, HDMI_CEC_WKUPCTRL); |
---|
| 271 | + |
---|
| 272 | + if (!cec->wake_en) |
---|
| 273 | + return IRQ_HANDLED; |
---|
| 274 | + |
---|
| 275 | + return IRQ_WAKE_THREAD; |
---|
| 276 | +} |
---|
| 277 | + |
---|
| 278 | +static irqreturn_t dw_hdmi_cec_wake_thread(int irq, void *data) |
---|
| 279 | +{ |
---|
| 280 | + struct cec_adapter *adap = data; |
---|
| 281 | + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); |
---|
| 282 | + |
---|
| 283 | + mutex_lock(&cec->wake_lock); |
---|
| 284 | + |
---|
| 285 | + if (!cec->standby_en) { |
---|
| 286 | + mutex_unlock(&cec->wake_lock); |
---|
| 287 | + return IRQ_HANDLED; |
---|
| 288 | + } |
---|
| 289 | + cec->standby_en = false; |
---|
| 290 | + |
---|
| 291 | + dev_dbg(cec->dev, "wakeup opcode:0x%x\n", dw_hdmi_read(cec, HDMI_CEC_RX_DATA1)); |
---|
| 292 | + input_event(cec->devinput, EV_KEY, KEY_POWER, 1); |
---|
| 293 | + input_sync(cec->devinput); |
---|
| 294 | + input_event(cec->devinput, EV_KEY, KEY_POWER, 0); |
---|
| 295 | + input_sync(cec->devinput); |
---|
| 296 | + mutex_unlock(&cec->wake_lock); |
---|
| 297 | + |
---|
| 298 | + return IRQ_HANDLED; |
---|
| 299 | +} |
---|
| 300 | + |
---|
| 301 | +static int rockchip_hdmi_cec_input_init(struct dw_hdmi_cec *cec) |
---|
| 302 | +{ |
---|
| 303 | + int err; |
---|
| 304 | + |
---|
| 305 | + cec->devinput = devm_input_allocate_device(cec->dev); |
---|
| 306 | + if (!cec->devinput) |
---|
| 307 | + return -EPERM; |
---|
| 308 | + |
---|
| 309 | + cec->devinput->name = "hdmi_cec_key"; |
---|
| 310 | + cec->devinput->phys = "hdmi_cec_key/input0"; |
---|
| 311 | + cec->devinput->id.bustype = BUS_HOST; |
---|
| 312 | + cec->devinput->id.vendor = 0x0001; |
---|
| 313 | + cec->devinput->id.product = 0x0001; |
---|
| 314 | + cec->devinput->id.version = 0x0100; |
---|
| 315 | + |
---|
| 316 | + err = input_register_device(cec->devinput); |
---|
| 317 | + if (err < 0) { |
---|
| 318 | + input_free_device(cec->devinput); |
---|
| 319 | + return err; |
---|
| 320 | + } |
---|
| 321 | + input_set_capability(cec->devinput, EV_KEY, KEY_POWER); |
---|
| 322 | + |
---|
| 323 | + return 0; |
---|
| 324 | +} |
---|
| 325 | + |
---|
| 326 | +static long cec_standby(struct cec_adapter *adap, __u8 __user *parg) |
---|
| 327 | +{ |
---|
| 328 | + u8 en; |
---|
| 329 | + int ret; |
---|
| 330 | + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); |
---|
| 331 | + |
---|
| 332 | + mutex_lock(&cec->wake_lock); |
---|
| 333 | + if (copy_from_user(&en, parg, sizeof(en))) { |
---|
| 334 | + mutex_unlock(&cec->wake_lock); |
---|
| 335 | + return -EFAULT; |
---|
| 336 | + } |
---|
| 337 | + |
---|
| 338 | + cec->standby_en = !en; |
---|
| 339 | + ret = adap->ops->adap_enable(adap, en); |
---|
| 340 | + mutex_unlock(&cec->wake_lock); |
---|
| 341 | + |
---|
| 342 | + return ret; |
---|
| 343 | +} |
---|
| 344 | + |
---|
| 345 | +static long cec_func_en(struct dw_hdmi_cec *cec, int __user *parg) |
---|
| 346 | +{ |
---|
| 347 | + int en_mask; |
---|
| 348 | + |
---|
| 349 | + if (copy_from_user(&en_mask, parg, sizeof(en_mask))) |
---|
| 350 | + return -EFAULT; |
---|
| 351 | + |
---|
| 352 | + cec->wake_en = (en_mask & CEC_EN) && (en_mask & CEC_WAKE); |
---|
| 353 | + |
---|
| 354 | + return 0; |
---|
| 355 | +} |
---|
| 356 | + |
---|
| 357 | +static long dw_hdmi_cec_ioctl(struct file *f, unsigned int cmd, unsigned long arg) |
---|
| 358 | +{ |
---|
| 359 | + struct dw_hdmi_cec *cec; |
---|
| 360 | + struct miscdevice *misc_dev; |
---|
| 361 | + void __user *data; |
---|
| 362 | + |
---|
| 363 | + if (!f) |
---|
| 364 | + return -EFAULT; |
---|
| 365 | + |
---|
| 366 | + misc_dev = f->private_data; |
---|
| 367 | + cec = container_of(misc_dev, struct dw_hdmi_cec, misc_dev); |
---|
| 368 | + data = (void __user *)arg; |
---|
| 369 | + |
---|
| 370 | + switch (cmd) { |
---|
| 371 | + case CEC_STANDBY: |
---|
| 372 | + return cec_standby(cec->adap, data); |
---|
| 373 | + case CEC_FUNC_EN: |
---|
| 374 | + return cec_func_en(cec, data); |
---|
| 375 | + default: |
---|
| 376 | + return -EINVAL; |
---|
| 377 | + } |
---|
| 378 | + |
---|
| 379 | + return -ENOTTY; |
---|
| 380 | +} |
---|
| 381 | + |
---|
| 382 | +static int dw_hdmi_cec_open(struct inode *inode, struct file *f) |
---|
| 383 | +{ |
---|
| 384 | + return 0; |
---|
| 385 | +} |
---|
| 386 | + |
---|
| 387 | +static int dw_hdmi_cec_release(struct inode *inode, struct file *f) |
---|
| 388 | +{ |
---|
| 389 | + return 0; |
---|
| 390 | +} |
---|
| 391 | + |
---|
| 392 | +static const struct file_operations dw_hdmi_cec_file_operations = { |
---|
| 393 | + .compat_ioctl = dw_hdmi_cec_ioctl, |
---|
| 394 | + .unlocked_ioctl = dw_hdmi_cec_ioctl, |
---|
| 395 | + .open = dw_hdmi_cec_open, |
---|
| 396 | + .release = dw_hdmi_cec_release, |
---|
| 397 | + .owner = THIS_MODULE, |
---|
| 398 | +}; |
---|
| 399 | + |
---|
| 400 | +void dw_hdmi_hpd_wake_up(struct platform_device *pdev) |
---|
| 401 | +{ |
---|
| 402 | + struct dw_hdmi_cec *cec = platform_get_drvdata(pdev); |
---|
| 403 | + |
---|
| 404 | + mutex_lock(&cec->wake_lock); |
---|
| 405 | + |
---|
| 406 | + if (!cec->standby_en) { |
---|
| 407 | + mutex_unlock(&cec->wake_lock); |
---|
| 408 | + return; |
---|
| 409 | + } |
---|
| 410 | + cec->standby_en = false; |
---|
| 411 | + |
---|
| 412 | + dw_hdmi_write(cec, 0x02, HDMI_IH_MUTE); |
---|
| 413 | + |
---|
| 414 | + input_event(cec->devinput, EV_KEY, KEY_POWER, 1); |
---|
| 415 | + input_sync(cec->devinput); |
---|
| 416 | + input_event(cec->devinput, EV_KEY, KEY_POWER, 0); |
---|
| 417 | + input_sync(cec->devinput); |
---|
| 418 | + mutex_unlock(&cec->wake_lock); |
---|
| 419 | +} |
---|
| 420 | +EXPORT_SYMBOL_GPL(dw_hdmi_hpd_wake_up); |
---|
| 421 | + |
---|
232 | 422 | static int dw_hdmi_cec_probe(struct platform_device *pdev) |
---|
233 | 423 | { |
---|
234 | 424 | struct dw_hdmi_cec_data *data = dev_get_platdata(&pdev->dev); |
---|
.. | .. |
---|
247 | 437 | if (!cec) |
---|
248 | 438 | return -ENOMEM; |
---|
249 | 439 | |
---|
| 440 | + cec->dev = &pdev->dev; |
---|
250 | 441 | cec->irq = data->irq; |
---|
| 442 | + cec->wake_irq = data->wake_irq; |
---|
251 | 443 | cec->ops = data->ops; |
---|
252 | 444 | cec->hdmi = data->hdmi; |
---|
| 445 | + |
---|
| 446 | + mutex_init(&cec->wake_lock); |
---|
253 | 447 | |
---|
254 | 448 | platform_set_drvdata(pdev, cec); |
---|
255 | 449 | |
---|
.. | .. |
---|
276 | 470 | |
---|
277 | 471 | ret = devm_request_threaded_irq(&pdev->dev, cec->irq, |
---|
278 | 472 | dw_hdmi_cec_hardirq, |
---|
279 | | - dw_hdmi_cec_thread, IRQF_SHARED, |
---|
| 473 | + dw_hdmi_cec_thread, IRQF_SHARED | IRQF_ONESHOT, |
---|
280 | 474 | "dw-hdmi-cec", cec->adap); |
---|
281 | 475 | if (ret < 0) |
---|
282 | 476 | return ret; |
---|
| 477 | + |
---|
| 478 | + if (cec->wake_irq > 0) { |
---|
| 479 | + ret = devm_request_threaded_irq(&pdev->dev, cec->wake_irq, |
---|
| 480 | + dw_hdmi_cec_wake_irq, |
---|
| 481 | + dw_hdmi_cec_wake_thread, |
---|
| 482 | + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, |
---|
| 483 | + "cec-wakeup", cec->adap); |
---|
| 484 | + if (ret) { |
---|
| 485 | + dev_err(&pdev->dev, |
---|
| 486 | + "hdmi_cec request_irq failed (%d).\n", |
---|
| 487 | + ret); |
---|
| 488 | + return ret; |
---|
| 489 | + } |
---|
| 490 | + device_init_wakeup(&pdev->dev, 1); |
---|
| 491 | + enable_irq_wake(cec->wake_irq); |
---|
| 492 | + } |
---|
283 | 493 | |
---|
284 | 494 | cec->notify = cec_notifier_get(pdev->dev.parent); |
---|
285 | 495 | if (!cec->notify) |
---|
.. | .. |
---|
298 | 508 | devm_remove_action(&pdev->dev, dw_hdmi_cec_del, cec); |
---|
299 | 509 | |
---|
300 | 510 | cec_register_cec_notifier(cec->adap, cec->notify); |
---|
| 511 | + rockchip_hdmi_cec_input_init(cec); |
---|
301 | 512 | |
---|
302 | | - return 0; |
---|
| 513 | + cec->misc_dev.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "rk_cec"); |
---|
| 514 | + if (!cec->misc_dev.name) |
---|
| 515 | + return -ENOMEM; |
---|
| 516 | + cec->misc_dev.minor = MISC_DYNAMIC_MINOR; |
---|
| 517 | + cec->misc_dev.fops = &dw_hdmi_cec_file_operations; |
---|
| 518 | + cec->misc_dev.mode = 0666; |
---|
| 519 | + |
---|
| 520 | + ret = misc_register(&cec->misc_dev); |
---|
| 521 | + |
---|
| 522 | + return ret; |
---|
303 | 523 | } |
---|
304 | 524 | |
---|
305 | 525 | static int dw_hdmi_cec_remove(struct platform_device *pdev) |
---|
.. | .. |
---|
308 | 528 | |
---|
309 | 529 | cec_unregister_adapter(cec->adap); |
---|
310 | 530 | cec_notifier_put(cec->notify); |
---|
| 531 | + misc_deregister(&cec->misc_dev); |
---|
311 | 532 | |
---|
312 | 533 | return 0; |
---|
313 | 534 | } |
---|