From eaa4aee939157bd9758b7103153be9f0cf3de0d3 Mon Sep 17 00:00:00 2001 From: Jianwei Fan Date: Fri, 21 Jan 2022 09:25:00 +0800 Subject: [PATCH] media: i2c: ep9461e: add ep9461e HDMI 4-in 1-out switcher driver Signed-off-by: Jianwei Fan Change-Id: I45d9bab61a5d5656cd77be6bb858b79f545f334f --- drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ep9461e.c | 439 ++++++++++++++++++++++++++++++++++++ 3 files changed, 450 insertions(+) create mode 100644 drivers/media/i2c/ep9461e.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index a86dcf087c30..623496b1f1b1 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -324,6 +324,16 @@ config VIDEO_BT866 To compile this driver as a module, choose M here: the module will be called bt866. +config VIDEO_EP9461E + tristate "Semiconn EP9461E decoder" + depends on I2C + select HDMI + help + Support for the Semiconn EP9461E 4 HDMI switch. + + To compile this driver as a module, choose M here: the + module will be called ep9461e. + config VIDEO_KS0127 tristate "KS0127 video decoder" depends on VIDEO_V4L2 && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index e0e02a7aabc0..7f7222b2c5bf 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_VIDEO_VS6624) += vs6624.o obj-$(CONFIG_VIDEO_BT819) += bt819.o obj-$(CONFIG_VIDEO_BT856) += bt856.o obj-$(CONFIG_VIDEO_BT866) += bt866.o +obj-$(CONFIG_VIDEO_EP9461E) += ep9461e.o obj-$(CONFIG_VIDEO_KS0127) += ks0127.o obj-$(CONFIG_VIDEO_THS7303) += ths7303.o obj-$(CONFIG_VIDEO_THS8200) += ths8200.o diff --git a/drivers/media/i2c/ep9461e.c b/drivers/media/i2c/ep9461e.c new file mode 100644 index 000000000000..4e5c9c9a3e3d --- /dev/null +++ b/drivers/media/i2c/ep9461e.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 Rockchip Electronics Co. Ltd. + * + * Author: Jianwei Fan + * + * V0.0X01.0X00 first version. + * V0.0X01.0X01 add device attr hdmirxsel. + * V0.0X01.0X02 add device attr hdmiautoswitch. + * + */ + +// #define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_VERSION KERNEL_VERSION(0, 0x01, 0x02) +#define DRIVER_NAME "EP9461E" + +/*control reg*/ +#define RX_SIGNAL_DETECT 0x00 +#define GENERAL_CONTROL 0x08 +#define RX_SEL_CONTROL 0x09 +#define EDID_ENABLE 0x0B +#define ENTER_CODE 0x07 + +/*control mask*/ +#define MASK_RX0_SIGNAL 0x10 +#define MASK_AUTO_SWITCH 0x40 +#define MASK_CEC_SWITCH 0x20 +#define MASK_POWER 0x80 +#define MASK_RX_SEL 0x0f + +struct ep9461e_dev { + struct device *dev; + struct miscdevice miscdev; + struct i2c_client *client; + struct mutex confctl_mutex; + struct timer_list timer; + struct delayed_work work_i2c_poll; + bool auto_switch_en; + bool power_up_chip_en; + bool cec_switch_en; + bool nosignal; + u32 hdmi_rx_sel; + int err_cnt; +}; + +static struct ep9461e_dev *g_ep9461e; +static struct ep9461e_dev *ep9461e; + +static void ep9461e_rx_select(struct ep9461e_dev *ep9461e); +static void ep9461e_rx_manual_select(struct ep9461e_dev *ep9461e); + +static void i2c_wr(struct ep9461e_dev *ep9461e, u16 reg, u8 *val, u32 n) +{ + struct i2c_msg msg; + struct i2c_client *client = ep9461e->client; + int err; + u8 data[128]; + + data[0] = reg; + memcpy(&data[1], val, n); + msg.addr = client->addr; + msg.flags = 0; + msg.buf = data; + msg.len = n + 1; + + err = i2c_transfer(client->adapter, &msg, 1); + if (err != 1) { + dev_err(ep9461e->dev, "writing register 0x%x from 0x%x failed\n", + reg, client->addr); + } else { + switch (n) { + case 1: + dev_dbg(ep9461e->dev, "I2C write 0x%02x = 0x%02x\n", + reg, data[1]); + break; + case 2: + dev_dbg(ep9461e->dev, + "I2C write 0x%02x = 0x%02x%02x\n", + reg, data[2], data[1]); + break; + case 4: + dev_dbg(ep9461e->dev, + "I2C write 0x%02x = 0x%02x%02x%02x%02x\n", + reg, data[4], data[3], data[2], data[1]); + break; + default: + dev_dbg(ep9461e->dev, + "I2C write %d bytes from address 0x%02x\n", + n, reg); + } + } +} + +static void i2c_rd(struct ep9461e_dev *ep9461e, u16 reg, u8 *val, u32 n) +{ + struct i2c_msg msg[2]; + struct i2c_client *client = ep9461e->client; + int err; + u8 buf[1] = { reg }; + + /*msg[0] addr to read*/ + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].buf = buf; + msg[0].len = 1; + + /*msg[1] read data*/ + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = val; + msg[1].len = n; + + err = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (err != ARRAY_SIZE(msg)) { + dev_err(ep9461e->dev, "reading register 0x%x from 0x%x failed\n", + reg, client->addr); + } +} + +static void i2c_rd8(struct ep9461e_dev *ep9461e, u16 reg, u8 *val) +{ + i2c_rd(ep9461e, reg, val, 1); +} + +static void i2c_wr8(struct ep9461e_dev *ep9461e, u16 reg, u8 buf) +{ + i2c_wr(ep9461e, reg, &buf, 1); +} + +static void i2c_wr8_and_or(struct ep9461e_dev *ep9461e, u16 reg, u32 mask, + u32 val) +{ + u8 val_p; + + i2c_rd8(ep9461e, reg, &val_p); + i2c_wr8(ep9461e, reg, (val_p & mask) | val); +} + +static long ep9461e_ioctl(struct file *file, uint32_t cmd, unsigned long arg) +{ + return 0; +} + +static ssize_t ep9461e_write(struct file *file, const char __user *buf, + size_t size, loff_t *ppos) +{ + return 1; +} + +static ssize_t ep9461e_read(struct file *file, char __user *buf, size_t size, + loff_t *ppos) +{ + return 1; +} + +static ssize_t hdmirxsel_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ep9461e_dev *ep9461e = g_ep9461e; + + dev_info(ep9461e->dev, "%s: hdmi rx select state: %d\n", + __func__, ep9461e->hdmi_rx_sel); + + return sprintf(buf, "%d\n", ep9461e->hdmi_rx_sel); +} + +static ssize_t hdmirxsel_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ep9461e_dev *ep9461e = g_ep9461e; + u32 hdmirxstate = 0; + int ret; + + ret = kstrtouint(buf, 10, &hdmirxstate); + if (!ret) { + dev_dbg(ep9461e->dev, "state: %d\n", hdmirxstate); + ep9461e->hdmi_rx_sel = hdmirxstate; + if (ep9461e->auto_switch_en | ep9461e->cec_switch_en) + ep9461e->auto_switch_en = ep9461e->cec_switch_en = 0; + ep9461e_rx_select(ep9461e); + } else { + dev_err(ep9461e->dev, "write hdmi_rx_sel failed!!!\n"); + } + + return count; +} + +static ssize_t hdmiautoswitch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ep9461e_dev *ep9461e = g_ep9461e; + + dev_info(ep9461e->dev, "hdmi rx select auto_switch state: %d\n", + ep9461e->auto_switch_en); + + return sprintf(buf, "%d\n", ep9461e->auto_switch_en); +} + +static ssize_t hdmiautoswitch_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ep9461e_dev *ep9461e = g_ep9461e; + u32 hdmiautoswitch = 0; + int ret; + + ret = kstrtouint(buf, 10, &hdmiautoswitch); + if (!ret) { + dev_dbg(ep9461e->dev, "state: %d\n", hdmiautoswitch); + ep9461e->auto_switch_en = hdmiautoswitch; + ep9461e_rx_select(ep9461e); + } else { + dev_err(ep9461e->dev, "write hdmi auto switch failed!!!\n"); + } + + return count; +} + +static DEVICE_ATTR_RW(hdmirxsel); +static DEVICE_ATTR_RW(hdmiautoswitch); + +static inline bool detect_rx_signal(struct ep9461e_dev *ep9461e) +{ + u8 val; + + i2c_rd8(ep9461e, RX_SIGNAL_DETECT, &val); + if (!(val & MASK_RX0_SIGNAL)) + return false; + else + return true; +} + +static void ep9461e_init(struct ep9461e_dev *ep9461e) +{ + ep9461e->power_up_chip_en = false; + ep9461e->auto_switch_en = false; + ep9461e->hdmi_rx_sel = 0; + ep9461e->err_cnt = 0; + + if (ep9461e->power_up_chip_en) { + i2c_wr8_and_or(ep9461e, GENERAL_CONTROL, ~MASK_POWER, + MASK_POWER); + } + ep9461e_rx_select(ep9461e); + schedule_delayed_work(&ep9461e->work_i2c_poll, msecs_to_jiffies(1000)); +} + +static void ep9461e_rx_manual_select(struct ep9461e_dev *ep9461e) +{ + i2c_wr8(ep9461e, RX_SEL_CONTROL, ep9461e->hdmi_rx_sel); +} + +static void ep9461e_rx_select(struct ep9461e_dev *ep9461e) +{ + int ret; + + if (ep9461e->auto_switch_en) { + i2c_wr8_and_or(ep9461e, GENERAL_CONTROL, ~MASK_AUTO_SWITCH, + MASK_AUTO_SWITCH); + } else { + ep9461e_rx_manual_select(ep9461e); + } + + ret = detect_rx_signal(ep9461e); + if (ret) + dev_info(ep9461e->dev, "Detect HDMI RX valid signal!\n"); + else + dev_err(ep9461e->dev, "HDMI RX has no valid signal!\n"); +} + + +static void ep9461e_work_i2c_poll(struct work_struct *work) +{ + int ret; + struct delayed_work *dwork = to_delayed_work(work); + struct ep9461e_dev *ep9461e = + container_of(dwork, struct ep9461e_dev, work_i2c_poll); + + ret = detect_rx_signal(ep9461e); + if (!ret && (ep9461e->err_cnt < 10)) { + ep9461e->err_cnt++; + dev_err(ep9461e->dev, + "ERROR: HDMI RX has no valid signal, err cnt: %d\n", + ep9461e->err_cnt); + if (ep9461e->err_cnt >= 10) + dev_err(ep9461e->dev, + "error count greater than 10, please check HDMIRX!"); + } else if (ret) { + ep9461e->err_cnt = 0; + } + schedule_delayed_work(&ep9461e->work_i2c_poll, msecs_to_jiffies(1000)); +} + +static const struct file_operations ep9461e_fops = { + .owner = THIS_MODULE, + .read = ep9461e_read, + .write = ep9461e_write, + .unlocked_ioctl = ep9461e_ioctl, +}; + +struct miscdevice ep9461e_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "ep9461e_dev", + .fops = &ep9461e_fops, +}; + +static int ep9461e_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ep9461e_dev *ep9461e; + struct device *dev = &client->dev; + int ret; + + dev_info(dev, "driver version: %02x.%02x.%02x", + DRIVER_VERSION >> 16, + (DRIVER_VERSION & 0xff00) >> 8, + DRIVER_VERSION & 0x00ff); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + dev_info(dev, "chip found @ 0x%x (%s)\n", client->addr << 1, + client->adapter->name); + + ep9461e = devm_kzalloc(dev, sizeof(struct ep9461e_dev), GFP_KERNEL); + if (!ep9461e) + return -ENOMEM; + + ep9461e->client = client; + ep9461e->dev = dev; + client->flags |= I2C_CLIENT_SCCB; + + ret = misc_register(&ep9461e_miscdev); + if (ret) { + dev_err(ep9461e->dev, + "EP9461E ERROR: could not register ep9461e device\n"); + return ret; + } + + mutex_init(&ep9461e->confctl_mutex); + ret = device_create_file(ep9461e_miscdev.this_device, + &dev_attr_hdmirxsel); + if (ret) { + dev_err(ep9461e->dev, "failed to create attr hdmirxsel!\n"); + goto err1; + } + + ret = device_create_file(ep9461e_miscdev.this_device, + &dev_attr_hdmiautoswitch); + if (ret) { + dev_err(ep9461e->dev, + "failed to create attr hdmiautoswitch!\n"); + goto err; + } + + INIT_DELAYED_WORK(&ep9461e->work_i2c_poll, ep9461e_work_i2c_poll); + + ep9461e_init(ep9461e); + g_ep9461e = ep9461e; + + dev_info(ep9461e->dev, "%s found @ 0x%x (%s)\n", + client->name, client->addr << 1, + client->adapter->name); + + return 0; + +err: + device_remove_file(ep9461e_miscdev.this_device, + &dev_attr_hdmirxsel); +err1: + misc_deregister(&ep9461e_miscdev); + return ret; +} + +static int ep9461e_remove(struct i2c_client *client) +{ + cancel_delayed_work_sync(&ep9461e->work_i2c_poll); + device_remove_file(ep9461e_miscdev.this_device, + &dev_attr_hdmirxsel); + device_remove_file(ep9461e_miscdev.this_device, + &dev_attr_hdmiautoswitch); + mutex_destroy(&ep9461e->confctl_mutex); + misc_deregister(&ep9461e_miscdev); + + return 0; +} + +static const struct of_device_id ep9461e_of_match[] = { + { .compatible = "semiconn,ep9461e" }, + {} +}; +MODULE_DEVICE_TABLE(of, ep9461e_of_match); + +static struct i2c_driver ep9461e_driver = { + .probe = ep9461e_probe, + .remove = ep9461e_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(ep9461e_of_match), + }, +}; + +static int __init ep9461e_driver_init(void) +{ + return i2c_add_driver(&ep9461e_driver); +} + +static void __exit ep9461e_driver_exit(void) +{ + i2c_del_driver(&ep9461e_driver); +} + +device_initcall_sync(ep9461e_driver_init); +module_exit(ep9461e_driver_exit); + +MODULE_DESCRIPTION("semiconn EP9461E 4 HDMI in switch driver"); +MODULE_AUTHOR("Jianwei Fan "); +MODULE_LICENSE("GPL v2");