From 0a8b81fb3fa4dac96a19030fa837ba05d0275352 Mon Sep 17 00:00:00 2001 From: Algea Cao Date: Wed, 23 Nov 2022 09:58:54 +0800 Subject: [PATCH] drm: bridge: dw-hdmi: Support cec wake up If cec in standby status, cec wake up interrupt will be triggered when specific cec message is received. Then input power key event to wake up system. Signed-off-by: Algea Cao Change-Id: I91b4482ab78f91e5e9df66fa8384e118b08f35a2 --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c | 199 +++++++++++++++++- drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.h | 5 + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 9 +- include/uapi/linux/cec.h | 6 + 4 files changed, 209 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c index 48fc36d56bc2..e2d56ab59ef3 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c @@ -4,8 +4,10 @@ * * Copyright (C) 2015-2017 Russell King. */ +#include #include #include +#include #include #include #include @@ -22,8 +24,11 @@ enum { HDMI_IH_CEC_STAT0 = 0x0106, HDMI_IH_MUTE_CEC_STAT0 = 0x0186, + HDMI_IH_MUTE = 0x01ff, HDMI_CEC_CTRL = 0x7d00, + CEC_TRANS_MASK = 0x7, + CEC_CTRL_STANDBY = BIT(4), CEC_CTRL_START = BIT(0), CEC_CTRL_FRAME_TYP = 3 << 1, CEC_CTRL_RETRY = 0 << 1, @@ -48,12 +53,15 @@ enum { HDMI_CEC_RX_CNT = 0x7d08, HDMI_CEC_TX_DATA0 = 0x7d10, HDMI_CEC_RX_DATA0 = 0x7d20, + HDMI_CEC_RX_DATA1 = 0x7d21, HDMI_CEC_LOCK = 0x7d30, HDMI_CEC_WKUPCTRL = 0x7d31, }; struct dw_hdmi_cec { + struct device *dev; struct dw_hdmi *hdmi; + struct miscdevice misc_dev; const struct dw_hdmi_cec_ops *ops; u32 addresses; struct cec_adapter *adap; @@ -62,7 +70,11 @@ struct dw_hdmi_cec { bool tx_done; bool rx_done; struct cec_notifier *notify; + struct input_dev *devinput; int irq; + int wake_irq; + bool wake_en; + bool standby_en; }; static void dw_hdmi_write(struct dw_hdmi_cec *cec, u8 val, int offset) @@ -75,6 +87,11 @@ static u8 dw_hdmi_read(struct dw_hdmi_cec *cec, int offset) return cec->ops->read(cec->hdmi, offset); } +static void dw_hdmi_mod(struct dw_hdmi_cec *cec, unsigned int offset, u8 mask, u8 val) +{ + cec->ops->mod(cec->hdmi, val, mask, offset); +} + static int dw_hdmi_cec_log_addr(struct cec_adapter *adap, u8 logical_addr) { struct dw_hdmi_cec *cec = cec_get_drvdata(adap); @@ -113,7 +130,7 @@ static int dw_hdmi_cec_transmit(struct cec_adapter *adap, u8 attempts, dw_hdmi_write(cec, msg->msg[i], HDMI_CEC_TX_DATA0 + i); dw_hdmi_write(cec, msg->len, HDMI_CEC_TX_CNT); - dw_hdmi_write(cec, ctrl | CEC_CTRL_START, HDMI_CEC_CTRL); + dw_hdmi_mod(cec, HDMI_CEC_CTRL, CEC_TRANS_MASK, ctrl | CEC_CTRL_START); return 0; } @@ -189,20 +206,28 @@ static int dw_hdmi_cec_enable(struct cec_adapter *adap, bool enable) struct dw_hdmi_cec *cec = cec_get_drvdata(adap); if (!enable) { - dw_hdmi_write(cec, ~0, HDMI_CEC_MASK); - dw_hdmi_write(cec, ~0, HDMI_IH_MUTE_CEC_STAT0); dw_hdmi_write(cec, 0, HDMI_CEC_POLARITY); - cec->ops->disable(cec->hdmi); + if (cec->wake_en && cec->standby_en) { + dw_hdmi_write(cec, 0xff, HDMI_IH_CEC_STAT0); + dw_hdmi_mod(cec, HDMI_CEC_CTRL, CEC_CTRL_STANDBY, CEC_CTRL_STANDBY); + dw_hdmi_write(cec, 0, HDMI_CEC_LOCK); + dw_hdmi_write(cec, 0xff, HDMI_CEC_WKUPCTRL); + dw_hdmi_write(cec, ~(1 << 6), HDMI_CEC_MASK); + dw_hdmi_write(cec, ~(1 << 6), HDMI_IH_MUTE_CEC_STAT0); + dw_hdmi_write(cec, 0x01, HDMI_IH_MUTE); + } else { + cec->ops->disable(cec->hdmi); + } } else { unsigned int irqs; - dw_hdmi_write(cec, 0, HDMI_CEC_CTRL); + dw_hdmi_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID); + dw_hdmi_mod(cec, HDMI_CEC_CTRL, CEC_CTRL_STANDBY, 0); + dw_hdmi_write(cec, 0x02, HDMI_IH_MUTE); dw_hdmi_write(cec, ~0, HDMI_IH_CEC_STAT0); dw_hdmi_write(cec, 0, HDMI_CEC_LOCK); - dw_hdmi_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID); - cec->ops->enable(cec->hdmi); irqs = CEC_STAT_ERROR_INIT | CEC_STAT_NACK | CEC_STAT_EOM | @@ -227,6 +252,132 @@ static void dw_hdmi_cec_del(void *data) cec_delete_adapter(cec->adap); } +static irqreturn_t dw_hdmi_cec_wake_irq(int irq, void *data) +{ + struct cec_adapter *adap = data; + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + u8 cec_int; + + cec_int = dw_hdmi_read(cec, HDMI_IH_CEC_STAT0); + if (!cec_int) + return IRQ_NONE; + + dw_hdmi_write(cec, 0x02, HDMI_IH_MUTE); + dw_hdmi_write(cec, cec_int, HDMI_IH_CEC_STAT0); + dw_hdmi_write(cec, 0x00, HDMI_CEC_WKUPCTRL); + + if (!cec->wake_en) + return IRQ_HANDLED; + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t dw_hdmi_cec_wake_thread(int irq, void *data) +{ + struct cec_adapter *adap = data; + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + + dev_dbg(cec->dev, "wakeup opcode:0x%x\n", dw_hdmi_read(cec, HDMI_CEC_RX_DATA1)); + input_event(cec->devinput, EV_KEY, KEY_POWER, 1); + input_sync(cec->devinput); + input_event(cec->devinput, EV_KEY, KEY_POWER, 0); + input_sync(cec->devinput); + + return IRQ_HANDLED; +} + +static int rockchip_hdmi_cec_input_init(struct dw_hdmi_cec *cec) +{ + int err; + + cec->devinput = devm_input_allocate_device(cec->dev); + if (!cec->devinput) + return -EPERM; + + cec->devinput->name = "hdmi_cec_key"; + cec->devinput->phys = "hdmi_cec_key/input0"; + cec->devinput->id.bustype = BUS_HOST; + cec->devinput->id.vendor = 0x0001; + cec->devinput->id.product = 0x0001; + cec->devinput->id.version = 0x0100; + + err = input_register_device(cec->devinput); + if (err < 0) { + input_free_device(cec->devinput); + return err; + } + input_set_capability(cec->devinput, EV_KEY, KEY_POWER); + + return 0; +} + +static long cec_standby(struct cec_adapter *adap, __u8 __user *parg) +{ + u8 en; + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + + if (copy_from_user(&en, parg, sizeof(en))) + return -EFAULT; + + cec->standby_en = !en; + return adap->ops->adap_enable(adap, en); +} + +static long cec_func_en(struct dw_hdmi_cec *cec, int __user *parg) +{ + int en_mask; + + if (copy_from_user(&en_mask, parg, sizeof(en_mask))) + return -EFAULT; + + cec->wake_en = (en_mask & CEC_EN) && (en_mask & CEC_WAKE); + + return 0; +} + +static long dw_hdmi_cec_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + struct dw_hdmi_cec *cec; + struct miscdevice *misc_dev; + void __user *data; + + if (!f) + return -EFAULT; + + misc_dev = f->private_data; + cec = container_of(misc_dev, struct dw_hdmi_cec, misc_dev); + data = (void __user *)arg; + + switch (cmd) { + case CEC_STANDBY: + return cec_standby(cec->adap, data); + case CEC_FUNC_EN: + return cec_func_en(cec, data); + default: + return -EINVAL; + } + + return -ENOTTY; +} + +static int dw_hdmi_cec_open(struct inode *inode, struct file *f) +{ + return 0; +} + +static int dw_hdmi_cec_release(struct inode *inode, struct file *f) +{ + return 0; +} + +static const struct file_operations dw_hdmi_cec_file_operations = { + .compat_ioctl = dw_hdmi_cec_ioctl, + .unlocked_ioctl = dw_hdmi_cec_ioctl, + .open = dw_hdmi_cec_open, + .release = dw_hdmi_cec_release, + .owner = THIS_MODULE, +}; + static int dw_hdmi_cec_probe(struct platform_device *pdev) { struct dw_hdmi_cec_data *data = dev_get_platdata(&pdev->dev); @@ -245,7 +396,9 @@ static int dw_hdmi_cec_probe(struct platform_device *pdev) if (!cec) return -ENOMEM; + cec->dev = &pdev->dev; cec->irq = data->irq; + cec->wake_irq = data->wake_irq; cec->ops = data->ops; cec->hdmi = data->hdmi; @@ -276,11 +429,27 @@ static int dw_hdmi_cec_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(&pdev->dev, cec->irq, dw_hdmi_cec_hardirq, - dw_hdmi_cec_thread, IRQF_SHARED, + dw_hdmi_cec_thread, IRQF_SHARED | IRQF_ONESHOT, "dw-hdmi-cec", cec->adap); if (ret < 0) return ret; + if (cec->wake_irq > 0) { + ret = devm_request_threaded_irq(&pdev->dev, cec->wake_irq, + dw_hdmi_cec_wake_irq, + dw_hdmi_cec_wake_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "cec-wakeup", cec->adap); + if (ret) { + dev_err(&pdev->dev, + "hdmi_cec request_irq failed (%d).\n", + ret); + return ret; + } + device_init_wakeup(&pdev->dev, 1); + enable_irq_wake(cec->wake_irq); + } + cec->notify = cec_notifier_cec_adap_register(pdev->dev.parent, NULL, cec->adap); if (!cec->notify) @@ -298,7 +467,18 @@ static int dw_hdmi_cec_probe(struct platform_device *pdev) */ devm_remove_action(&pdev->dev, dw_hdmi_cec_del, cec); - return 0; + rockchip_hdmi_cec_input_init(cec); + + cec->misc_dev.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "rk_cec"); + if (!cec->misc_dev.name) + return -ENOMEM; + cec->misc_dev.minor = MISC_DYNAMIC_MINOR; + cec->misc_dev.fops = &dw_hdmi_cec_file_operations; + cec->misc_dev.mode = 0666; + + ret = misc_register(&cec->misc_dev); + + return ret; } static int dw_hdmi_cec_remove(struct platform_device *pdev) @@ -307,6 +487,7 @@ static int dw_hdmi_cec_remove(struct platform_device *pdev) cec_notifier_cec_adap_unregister(cec->notify, cec->adap); cec_unregister_adapter(cec->adap); + misc_deregister(&cec->misc_dev); return 0; } diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.h index cf4dc121a2c4..122741dba6b7 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.h +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.h @@ -3,17 +3,22 @@ struct dw_hdmi; +#define CEC_EN BIT(0) +#define CEC_WAKE BIT(1) + struct dw_hdmi_cec_ops { void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); u8 (*read)(struct dw_hdmi *hdmi, int offset); void (*enable)(struct dw_hdmi *hdmi); void (*disable)(struct dw_hdmi *hdmi); + void (*mod)(struct dw_hdmi *hdmi, u8 data, u8 mask, unsigned int reg); }; struct dw_hdmi_cec_data { struct dw_hdmi *hdmi; const struct dw_hdmi_cec_ops *ops; int irq; + int wake_irq; }; #endif diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 761314033f0e..8cc1ccd76250 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -3991,6 +3991,7 @@ static void dw_hdmi_cec_disable(struct dw_hdmi *hdmi) static const struct dw_hdmi_cec_ops dw_hdmi_cec_ops = { .write = hdmi_writeb, .read = hdmi_readb, + .mod = hdmi_modb, .enable = dw_hdmi_cec_enable, .disable = dw_hdmi_cec_disable, }; @@ -4560,7 +4561,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, hdmi->irq = irq; ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq, - dw_hdmi_irq, IRQF_SHARED, + dw_hdmi_irq, IRQF_SHARED | IRQF_ONESHOT, dev_name(dev), hdmi); if (ret) goto err_iahb; @@ -4685,6 +4686,12 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, cec.ops = &dw_hdmi_cec_ops; cec.irq = irq; + irq = platform_get_irq(pdev, 1); + if (irq < 0) + dev_dbg(hdmi->dev, "can't get cec wake up irq\n"); + + cec.wake_irq = irq; + pdevinfo.name = "dw-hdmi-cec"; pdevinfo.data = &cec; pdevinfo.size_data = sizeof(cec); diff --git a/include/uapi/linux/cec.h b/include/uapi/linux/cec.h index 7d1a06c52469..ebbcb9b0104a 100644 --- a/include/uapi/linux/cec.h +++ b/include/uapi/linux/cec.h @@ -516,6 +516,12 @@ struct cec_event { /* Get the connector info */ #define CEC_ADAP_G_CONNECTOR_INFO _IOR('a', 10, struct cec_connector_info) +/* set cec into standby mode */ +#define CEC_STANDBY _IOW('a', 10, __u8) + +/* set cec wake up function enable */ +#define CEC_FUNC_EN _IOW('a', 11, int) + /* * The remainder of this header defines all CEC messages and operands. * The format matters since it the cec-ctl utility parses it to generate