// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2014 MediaTek Inc. * Author: Jie Qiu <jie.qiu@mediatek.com> */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/time.h> #include <linux/delay.h> #include <linux/errno.h> #include <linux/err.h> #include <linux/platform_device.h> #include <linux/clk.h> #include <linux/slab.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/of_platform.h> #include "mtk_drm_drv.h" #include "mtk_hdmi.h" #define SIF1_CLOK (288) #define DDC_DDCMCTL0 (0x0) #define DDCM_ODRAIN BIT(31) #define DDCM_CLK_DIV_OFFSET (16) #define DDCM_CLK_DIV_MASK (0xfff << 16) #define DDCM_CS_STATUS BIT(4) #define DDCM_SCL_STATE BIT(3) #define DDCM_SDA_STATE BIT(2) #define DDCM_SM0EN BIT(1) #define DDCM_SCL_STRECH BIT(0) #define DDC_DDCMCTL1 (0x4) #define DDCM_ACK_OFFSET (16) #define DDCM_ACK_MASK (0xff << 16) #define DDCM_PGLEN_OFFSET (8) #define DDCM_PGLEN_MASK (0x7 << 8) #define DDCM_SIF_MODE_OFFSET (4) #define DDCM_SIF_MODE_MASK (0x7 << 4) #define DDCM_START (0x1) #define DDCM_WRITE_DATA (0x2) #define DDCM_STOP (0x3) #define DDCM_READ_DATA_NO_ACK (0x4) #define DDCM_READ_DATA_ACK (0x5) #define DDCM_TRI BIT(0) #define DDC_DDCMD0 (0x8) #define DDCM_DATA3 (0xff << 24) #define DDCM_DATA2 (0xff << 16) #define DDCM_DATA1 (0xff << 8) #define DDCM_DATA0 (0xff << 0) #define DDC_DDCMD1 (0xc) #define DDCM_DATA7 (0xff << 24) #define DDCM_DATA6 (0xff << 16) #define DDCM_DATA5 (0xff << 8) #define DDCM_DATA4 (0xff << 0) struct mtk_hdmi_ddc { struct i2c_adapter adap; struct clk *clk; void __iomem *regs; }; static inline void sif_set_bit(struct mtk_hdmi_ddc *ddc, unsigned int offset, unsigned int val) { writel(readl(ddc->regs + offset) | val, ddc->regs + offset); } static inline void sif_clr_bit(struct mtk_hdmi_ddc *ddc, unsigned int offset, unsigned int val) { writel(readl(ddc->regs + offset) & ~val, ddc->regs + offset); } static inline bool sif_bit_is_set(struct mtk_hdmi_ddc *ddc, unsigned int offset, unsigned int val) { return (readl(ddc->regs + offset) & val) == val; } static inline void sif_write_mask(struct mtk_hdmi_ddc *ddc, unsigned int offset, unsigned int mask, unsigned int shift, unsigned int val) { unsigned int tmp; tmp = readl(ddc->regs + offset); tmp &= ~mask; tmp |= (val << shift) & mask; writel(tmp, ddc->regs + offset); } static inline unsigned int sif_read_mask(struct mtk_hdmi_ddc *ddc, unsigned int offset, unsigned int mask, unsigned int shift) { return (readl(ddc->regs + offset) & mask) >> shift; } static void ddcm_trigger_mode(struct mtk_hdmi_ddc *ddc, int mode) { u32 val; sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_SIF_MODE_MASK, DDCM_SIF_MODE_OFFSET, mode); sif_set_bit(ddc, DDC_DDCMCTL1, DDCM_TRI); readl_poll_timeout(ddc->regs + DDC_DDCMCTL1, val, (val & DDCM_TRI) != DDCM_TRI, 4, 20000); } static int mtk_hdmi_ddc_read_msg(struct mtk_hdmi_ddc *ddc, struct i2c_msg *msg) { struct device *dev = ddc->adap.dev.parent; u32 remain_count, ack_count, ack_final, read_count, temp_count; u32 index = 0; u32 ack; int i; ddcm_trigger_mode(ddc, DDCM_START); sif_write_mask(ddc, DDC_DDCMD0, 0xff, 0, (msg->addr << 1) | 0x01); sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET, 0x00); ddcm_trigger_mode(ddc, DDCM_WRITE_DATA); ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK, DDCM_ACK_OFFSET); dev_dbg(dev, "ack = 0x%x\n", ack); if (ack != 0x01) { dev_err(dev, "i2c ack err!\n"); return -ENXIO; } remain_count = msg->len; ack_count = (msg->len - 1) / 8; ack_final = 0; while (remain_count > 0) { if (ack_count > 0) { read_count = 8; ack_final = 0; ack_count--; } else { read_count = remain_count; ack_final = 1; } sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET, read_count - 1); ddcm_trigger_mode(ddc, (ack_final == 1) ? DDCM_READ_DATA_NO_ACK : DDCM_READ_DATA_ACK); ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK, DDCM_ACK_OFFSET); temp_count = 0; while (((ack & (1 << temp_count)) != 0) && (temp_count < 8)) temp_count++; if (((ack_final == 1) && (temp_count != (read_count - 1))) || ((ack_final == 0) && (temp_count != read_count))) { dev_err(dev, "Address NACK! ACK(0x%x)\n", ack); break; } for (i = read_count; i >= 1; i--) { int shift; int offset; if (i > 4) { offset = DDC_DDCMD1; shift = (i - 5) * 8; } else { offset = DDC_DDCMD0; shift = (i - 1) * 8; } msg->buf[index + i - 1] = sif_read_mask(ddc, offset, 0xff << shift, shift); } remain_count -= read_count; index += read_count; } return 0; } static int mtk_hdmi_ddc_write_msg(struct mtk_hdmi_ddc *ddc, struct i2c_msg *msg) { struct device *dev = ddc->adap.dev.parent; u32 ack; ddcm_trigger_mode(ddc, DDCM_START); sif_write_mask(ddc, DDC_DDCMD0, DDCM_DATA0, 0, msg->addr << 1); sif_write_mask(ddc, DDC_DDCMD0, DDCM_DATA1, 8, msg->buf[0]); sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET, 0x1); ddcm_trigger_mode(ddc, DDCM_WRITE_DATA); ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK, DDCM_ACK_OFFSET); dev_dbg(dev, "ack = %d\n", ack); if (ack != 0x03) { dev_err(dev, "i2c ack err!\n"); return -EIO; } return 0; } static int mtk_hdmi_ddc_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num) { struct mtk_hdmi_ddc *ddc = adapter->algo_data; struct device *dev = adapter->dev.parent; int ret; int i; if (!ddc) { dev_err(dev, "invalid arguments\n"); return -EINVAL; } sif_set_bit(ddc, DDC_DDCMCTL0, DDCM_SCL_STRECH); sif_set_bit(ddc, DDC_DDCMCTL0, DDCM_SM0EN); sif_clr_bit(ddc, DDC_DDCMCTL0, DDCM_ODRAIN); if (sif_bit_is_set(ddc, DDC_DDCMCTL1, DDCM_TRI)) { dev_err(dev, "ddc line is busy!\n"); return -EBUSY; } sif_write_mask(ddc, DDC_DDCMCTL0, DDCM_CLK_DIV_MASK, DDCM_CLK_DIV_OFFSET, SIF1_CLOK); for (i = 0; i < num; i++) { struct i2c_msg *msg = &msgs[i]; dev_dbg(dev, "i2c msg, adr:0x%x, flags:%d, len :0x%x\n", msg->addr, msg->flags, msg->len); if (msg->flags & I2C_M_RD) ret = mtk_hdmi_ddc_read_msg(ddc, msg); else ret = mtk_hdmi_ddc_write_msg(ddc, msg); if (ret < 0) goto xfer_end; } ddcm_trigger_mode(ddc, DDCM_STOP); return i; xfer_end: ddcm_trigger_mode(ddc, DDCM_STOP); dev_err(dev, "ddc failed!\n"); return ret; } static u32 mtk_hdmi_ddc_func(struct i2c_adapter *adapter) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } static const struct i2c_algorithm mtk_hdmi_ddc_algorithm = { .master_xfer = mtk_hdmi_ddc_xfer, .functionality = mtk_hdmi_ddc_func, }; static int mtk_hdmi_ddc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mtk_hdmi_ddc *ddc; struct resource *mem; int ret; ddc = devm_kzalloc(dev, sizeof(struct mtk_hdmi_ddc), GFP_KERNEL); if (!ddc) return -ENOMEM; ddc->clk = devm_clk_get(dev, "ddc-i2c"); if (IS_ERR(ddc->clk)) { dev_err(dev, "get ddc_clk failed: %p ,\n", ddc->clk); return PTR_ERR(ddc->clk); } mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); ddc->regs = devm_ioremap_resource(&pdev->dev, mem); if (IS_ERR(ddc->regs)) return PTR_ERR(ddc->regs); ret = clk_prepare_enable(ddc->clk); if (ret) { dev_err(dev, "enable ddc clk failed!\n"); return ret; } strscpy(ddc->adap.name, "mediatek-hdmi-ddc", sizeof(ddc->adap.name)); ddc->adap.owner = THIS_MODULE; ddc->adap.class = I2C_CLASS_DDC; ddc->adap.algo = &mtk_hdmi_ddc_algorithm; ddc->adap.retries = 3; ddc->adap.dev.of_node = dev->of_node; ddc->adap.algo_data = ddc; ddc->adap.dev.parent = &pdev->dev; ret = i2c_add_adapter(&ddc->adap); if (ret < 0) { dev_err(dev, "failed to add bus to i2c core\n"); goto err_clk_disable; } platform_set_drvdata(pdev, ddc); dev_dbg(dev, "ddc->adap: %p\n", &ddc->adap); dev_dbg(dev, "ddc->clk: %p\n", ddc->clk); dev_dbg(dev, "physical adr: %pa, end: %pa\n", &mem->start, &mem->end); return 0; err_clk_disable: clk_disable_unprepare(ddc->clk); return ret; } static void mtk_hdmi_ddc_remove(struct platform_device *pdev) { struct mtk_hdmi_ddc *ddc = platform_get_drvdata(pdev); i2c_del_adapter(&ddc->adap); clk_disable_unprepare(ddc->clk); } static const struct of_device_id mtk_hdmi_ddc_match[] = { { .compatible = "mediatek,mt8173-hdmi-ddc", }, {}, }; MODULE_DEVICE_TABLE(of, mtk_hdmi_ddc_match); struct platform_driver mtk_hdmi_ddc_driver = { .probe = mtk_hdmi_ddc_probe, .remove_new = mtk_hdmi_ddc_remove, .driver = { .name = "mediatek-hdmi-ddc", .of_match_table = mtk_hdmi_ddc_match, }, }; MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>"); MODULE_DESCRIPTION("MediaTek HDMI DDC Driver"); MODULE_LICENSE("GPL v2");