/* SPDX-License-Identifier: GPL-2.0 */ #include <linux/delay.h> #include <linux/errno.h> #include <linux/firmware.h> #include <linux/ioctl.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/pagemap.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/workqueue.h> #include "rockchip-hdmi-cec.h" static struct cec_device *cec_dev; static int cecreadframe(struct cec_framedata *frame) { int ret = -1; if (frame && cec_dev && cec_dev->readframe && cec_dev->enable) { mutex_lock(&cec_dev->hdmi->pclk_lock); ret = cec_dev->readframe(cec_dev->hdmi, frame); mutex_unlock(&cec_dev->hdmi->pclk_lock); } return ret; } static int cecsendframe(struct cec_framedata *frame) { int ret = -1; if (frame && cec_dev && cec_dev->sendframe) { mutex_lock(&cec_dev->hdmi->pclk_lock); ret = cec_dev->sendframe(cec_dev->hdmi, frame); mutex_unlock(&cec_dev->hdmi->pclk_lock); } return ret; } static void cecsetlogicaddr(int addr) { if (cec_dev && cec_dev->setceclogicaddr) { mutex_lock(&cec_dev->hdmi->pclk_lock); cec_dev->setceclogicaddr(cec_dev->hdmi, addr); mutex_unlock(&cec_dev->hdmi->pclk_lock); } } static void cecworkfunc(struct work_struct *work) { struct cec_delayed_work *cec_w = container_of(work, struct cec_delayed_work, work.work); struct cecframelist *list_node; switch (cec_w->event) { case EVENT_ENUMERATE: break; case EVENT_RX_FRAME: list_node = kmalloc(sizeof(*list_node), GFP_KERNEL); if (!list_node) return; cecreadframe(&list_node->cecframe); if (cec_dev->enable) { mutex_lock(&cec_dev->cec_lock); list_add_tail(&list_node->framelist, &cec_dev->ceclist); sysfs_notify(&cec_dev->device.this_device->kobj, NULL, "stat"); mutex_unlock(&cec_dev->cec_lock); } else { kfree(list_node); } break; default: break; } kfree(cec_w->data); kfree(cec_w); } void rockchip_hdmi_cec_submit_work(int event, int delay, void *data) { struct cec_delayed_work *work; HDMIDBG(1, "%s event %04x delay %d\n", __func__, event, delay); if (!cec_dev) return; work = kmalloc(sizeof(*work), GFP_ATOMIC); if (work) { INIT_DELAYED_WORK(&work->work, cecworkfunc); work->event = event; work->data = data; queue_delayed_work(cec_dev->workqueue, &work->work, msecs_to_jiffies(delay)); } else { HDMIDBG(1, "CEC: Cannot allocate memory\n"); } } void rockchip_hdmi_cec_set_pa(int devpa) { struct list_head *pos, *n; if (cec_dev) { cec_dev->address_phy = devpa; pr_info("%s %x\n", __func__, devpa); /*when hdmi hpd , ceclist will be reset*/ mutex_lock(&cec_dev->cec_lock); if (!list_empty(&cec_dev->ceclist)) { list_for_each_safe(pos, n, &cec_dev->ceclist) { list_del(pos); kfree(pos); } } INIT_LIST_HEAD(&cec_dev->ceclist); sysfs_notify(&cec_dev->device.this_device->kobj, NULL, "stat"); mutex_unlock(&cec_dev->cec_lock); } } static ssize_t cec_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%d\n", cec_dev->enable); } static ssize_t cec_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; ret = kstrtoint(buf, 0, &cec_dev->enable); return count; } static ssize_t cec_phy_show(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "0x%x\n", cec_dev->address_phy); } static ssize_t cec_phy_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; ret = kstrtoint(buf, 0, &cec_dev->address_phy); return count; } static ssize_t cec_logic_show(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "0x%02x\n", cec_dev->address_logic); } static ssize_t cec_logic_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; ret = kstrtoint(buf, 0, &cec_dev->address_logic); return count; } static ssize_t cec_state_show(struct device *dev, struct device_attribute *attr, char *buf) { int stat; mutex_lock(&cec_dev->cec_lock); if (!cec_dev->address_phy) stat = 0; else if (list_empty(&cec_dev->ceclist)) stat = 1; else stat = 2; mutex_unlock(&cec_dev->cec_lock); return snprintf(buf, PAGE_SIZE, "%d\n", stat); } static struct device_attribute cec_attrs[] = { __ATTR(logic, 0644, cec_logic_show, cec_logic_store), __ATTR(phy, 0644, cec_phy_show, cec_phy_store), __ATTR(enable, 0644, cec_enable_show, cec_enable_store), __ATTR(stat, S_IRUGO, cec_state_show, NULL), }; static long cec_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret; void __user *argp; struct cec_framedata cecsendtemp; struct cecframelist *listemp; argp = (void __user *)arg; switch (cmd) { case HDMI_IOCTL_CECSETLA: ret = copy_from_user(&cec_dev->address_logic, argp, sizeof(int)); cecsetlogicaddr(cec_dev->address_logic); break; case HDMI_IOCTL_CECSEND: ret = copy_from_user(&cecsendtemp, argp, sizeof(struct cec_framedata)); ret = cecsendframe(&cecsendtemp); cecsendtemp.returnval = ret; ret = copy_to_user(argp, &cecsendtemp, sizeof(struct cec_framedata)); break; case HDMI_IOCTL_CECENAB: ret = copy_from_user(&cec_dev->enable, argp, sizeof(int)); break; case HDMI_IOCTL_CECPHY: ret = copy_to_user(argp, &cec_dev->address_phy, sizeof(int)); break; case HDMI_IOCTL_CECLOGIC: ret = copy_to_user(argp, &cec_dev->address_logic, sizeof(int)); break; case HDMI_IOCTL_CECREAD: mutex_lock(&cec_dev->cec_lock); if (!list_empty(&cec_dev->ceclist)) { listemp = list_entry(cec_dev->ceclist.next, struct cecframelist, framelist); ret = copy_to_user(argp, &listemp->cecframe, sizeof(struct cec_framedata)); list_del(&listemp->framelist); kfree(listemp); } mutex_unlock(&cec_dev->cec_lock); break; case HDMI_IOCTL_CECCLEARLA: break; case HDMI_IOCTL_CECWAKESTATE: ret = copy_to_user(argp, &cec_dev->hdmi->sleep, sizeof(int)); break; default: break; } return 0; } static const struct file_operations cec_fops = { .owner = THIS_MODULE, .compat_ioctl = cec_ioctl, .unlocked_ioctl = cec_ioctl, }; int rockchip_hdmi_cec_init(struct hdmi *hdmi, int (*sendframe)(struct hdmi *, struct cec_framedata *), int (*readframe)(struct hdmi *, struct cec_framedata *), void (*setceclogicaddr)(struct hdmi *, int)) { int ret, i; cec_dev = kmalloc(sizeof(*cec_dev), GFP_KERNEL); if (!cec_dev) return -ENOMEM; memset(cec_dev, 0, sizeof(struct cec_device)); mutex_init(&cec_dev->cec_lock); INIT_LIST_HEAD(&cec_dev->ceclist); cec_dev->hdmi = hdmi; cec_dev->enable = 1; cec_dev->sendframe = sendframe; cec_dev->readframe = readframe; cec_dev->setceclogicaddr = setceclogicaddr; cec_dev->workqueue = create_singlethread_workqueue("hdmi-cec"); if (!cec_dev->workqueue) { pr_err("HDMI CEC: create workqueue failed.\n"); return -1; } cec_dev->device.minor = MISC_DYNAMIC_MINOR; cec_dev->device.name = "cec"; cec_dev->device.mode = 0666; cec_dev->device.fops = &cec_fops; if (misc_register(&cec_dev->device)) { pr_err("CEC: Could not add cec misc driver\n"); goto error; } for (i = 0; i < ARRAY_SIZE(cec_attrs); i++) { ret = device_create_file(cec_dev->device.this_device, &cec_attrs[i]); if (ret) { pr_err("CEC: Could not add sys file\n"); goto error1; } } return 0; error1: misc_deregister(&cec_dev->device); error: return -EINVAL; }