diff options
Diffstat (limited to 'drivers/media/cec/platform/meson')
-rw-r--r-- | drivers/media/cec/platform/meson/Makefile | 3 | ||||
-rw-r--r-- | drivers/media/cec/platform/meson/ao-cec-g12a.c | 794 | ||||
-rw-r--r-- | drivers/media/cec/platform/meson/ao-cec.c | 730 |
3 files changed, 1527 insertions, 0 deletions
diff --git a/drivers/media/cec/platform/meson/Makefile b/drivers/media/cec/platform/meson/Makefile new file mode 100644 index 000000000..34fc5d444 --- /dev/null +++ b/drivers/media/cec/platform/meson/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_CEC_MESON_AO) += ao-cec.o +obj-$(CONFIG_CEC_MESON_G12A_AO) += ao-cec-g12a.o diff --git a/drivers/media/cec/platform/meson/ao-cec-g12a.c b/drivers/media/cec/platform/meson/ao-cec-g12a.c new file mode 100644 index 000000000..68fe6d6a8 --- /dev/null +++ b/drivers/media/cec/platform/meson/ao-cec-g12a.c @@ -0,0 +1,794 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Amlogic Meson AO CEC G12A Controller + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved + * Copyright (C) 2019 BayLibre, SAS + * Author: Neil Armstrong <narmstrong@baylibre.com> + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <media/cec.h> +#include <media/cec-notifier.h> +#include <linux/clk-provider.h> + +/* CEC Registers */ + +#define CECB_CLK_CNTL_REG0 0x00 + +#define CECB_CLK_CNTL_N1 GENMASK(11, 0) +#define CECB_CLK_CNTL_N2 GENMASK(23, 12) +#define CECB_CLK_CNTL_DUAL_EN BIT(28) +#define CECB_CLK_CNTL_OUTPUT_EN BIT(30) +#define CECB_CLK_CNTL_INPUT_EN BIT(31) + +#define CECB_CLK_CNTL_REG1 0x04 + +#define CECB_CLK_CNTL_M1 GENMASK(11, 0) +#define CECB_CLK_CNTL_M2 GENMASK(23, 12) +#define CECB_CLK_CNTL_BYPASS_EN BIT(24) + +/* + * [14:12] Filter_del. For glitch-filtering CEC line, ignore signal + * change pulse width < filter_del * T(filter_tick) * 3. + * [9:8] Filter_tick_sel: Select which periodical pulse for + * glitch-filtering CEC line signal. + * - 0=Use T(xtal)*3 = 125ns; + * - 1=Use once-per-1us pulse; + * - 2=Use once-per-10us pulse; + * - 3=Use once-per-100us pulse. + * [3] Sysclk_en. 0=Disable system clock; 1=Enable system clock. + * [2:1] cntl_clk + * - 0 = Disable clk (Power-off mode) + * - 1 = Enable gated clock (Normal mode) + * - 2 = Enable free-run clk (Debug mode) + * [0] SW_RESET 1=Apply reset; 0=No reset. + */ +#define CECB_GEN_CNTL_REG 0x08 + +#define CECB_GEN_CNTL_RESET BIT(0) +#define CECB_GEN_CNTL_CLK_DISABLE 0 +#define CECB_GEN_CNTL_CLK_ENABLE 1 +#define CECB_GEN_CNTL_CLK_ENABLE_DBG 2 +#define CECB_GEN_CNTL_CLK_CTRL_MASK GENMASK(2, 1) +#define CECB_GEN_CNTL_SYS_CLK_EN BIT(3) +#define CECB_GEN_CNTL_FILTER_TICK_125NS 0 +#define CECB_GEN_CNTL_FILTER_TICK_1US 1 +#define CECB_GEN_CNTL_FILTER_TICK_10US 2 +#define CECB_GEN_CNTL_FILTER_TICK_100US 3 +#define CECB_GEN_CNTL_FILTER_TICK_SEL GENMASK(9, 8) +#define CECB_GEN_CNTL_FILTER_DEL GENMASK(14, 12) + +/* + * [7:0] cec_reg_addr + * [15:8] cec_reg_wrdata + * [16] cec_reg_wr + * - 0 = Read + * - 1 = Write + * [31:24] cec_reg_rddata + */ +#define CECB_RW_REG 0x0c + +#define CECB_RW_ADDR GENMASK(7, 0) +#define CECB_RW_WR_DATA GENMASK(15, 8) +#define CECB_RW_WRITE_EN BIT(16) +#define CECB_RW_BUS_BUSY BIT(23) +#define CECB_RW_RD_DATA GENMASK(31, 24) + +/* + * [0] DONE Interrupt + * [1] End Of Message Interrupt + * [2] Not Acknowlegde Interrupt + * [3] Arbitration Loss Interrupt + * [4] Initiator Error Interrupt + * [5] Follower Error Interrupt + * [6] Wake-Up Interrupt + */ +#define CECB_INTR_MASKN_REG 0x10 +#define CECB_INTR_CLR_REG 0x14 +#define CECB_INTR_STAT_REG 0x18 + +#define CECB_INTR_DONE BIT(0) +#define CECB_INTR_EOM BIT(1) +#define CECB_INTR_NACK BIT(2) +#define CECB_INTR_ARB_LOSS BIT(3) +#define CECB_INTR_INITIATOR_ERR BIT(4) +#define CECB_INTR_FOLLOWER_ERR BIT(5) +#define CECB_INTR_WAKE_UP BIT(6) + +/* CEC Commands */ + +#define CECB_CTRL 0x00 + +#define CECB_CTRL_SEND BIT(0) +#define CECB_CTRL_TYPE GENMASK(2, 1) +#define CECB_CTRL_TYPE_RETRY 0 +#define CECB_CTRL_TYPE_NEW 1 +#define CECB_CTRL_TYPE_NEXT 2 + +#define CECB_CTRL2 0x01 + +#define CECB_CTRL2_RISE_DEL_MAX GENMASK(4, 0) + +#define CECB_INTR_MASK 0x02 +#define CECB_LADD_LOW 0x05 +#define CECB_LADD_HIGH 0x06 +#define CECB_TX_CNT 0x07 +#define CECB_RX_CNT 0x08 +#define CECB_STAT0 0x09 +#define CECB_TX_DATA00 0x10 +#define CECB_TX_DATA01 0x11 +#define CECB_TX_DATA02 0x12 +#define CECB_TX_DATA03 0x13 +#define CECB_TX_DATA04 0x14 +#define CECB_TX_DATA05 0x15 +#define CECB_TX_DATA06 0x16 +#define CECB_TX_DATA07 0x17 +#define CECB_TX_DATA08 0x18 +#define CECB_TX_DATA09 0x19 +#define CECB_TX_DATA10 0x1A +#define CECB_TX_DATA11 0x1B +#define CECB_TX_DATA12 0x1C +#define CECB_TX_DATA13 0x1D +#define CECB_TX_DATA14 0x1E +#define CECB_TX_DATA15 0x1F +#define CECB_RX_DATA00 0x20 +#define CECB_RX_DATA01 0x21 +#define CECB_RX_DATA02 0x22 +#define CECB_RX_DATA03 0x23 +#define CECB_RX_DATA04 0x24 +#define CECB_RX_DATA05 0x25 +#define CECB_RX_DATA06 0x26 +#define CECB_RX_DATA07 0x27 +#define CECB_RX_DATA08 0x28 +#define CECB_RX_DATA09 0x29 +#define CECB_RX_DATA10 0x2A +#define CECB_RX_DATA11 0x2B +#define CECB_RX_DATA12 0x2C +#define CECB_RX_DATA13 0x2D +#define CECB_RX_DATA14 0x2E +#define CECB_RX_DATA15 0x2F +#define CECB_LOCK_BUF 0x30 + +#define CECB_LOCK_BUF_EN BIT(0) + +#define CECB_WAKEUPCTRL 0x31 + +struct meson_ao_cec_g12a_data { + /* Setup the internal CECB_CTRL2 register */ + bool ctrl2_setup; +}; + +struct meson_ao_cec_g12a_device { + struct platform_device *pdev; + struct regmap *regmap; + struct regmap *regmap_cec; + spinlock_t cec_reg_lock; + struct cec_notifier *notify; + struct cec_adapter *adap; + struct cec_msg rx_msg; + struct clk *oscin; + struct clk *core; + const struct meson_ao_cec_g12a_data *data; +}; + +static const struct regmap_config meson_ao_cec_g12a_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = CECB_INTR_STAT_REG, +}; + +/* + * The AO-CECB embeds a dual/divider to generate a more precise + * 32,768KHz clock for CEC core clock. + * ______ ______ + * | | | | + * ______ | Div1 |-| Cnt1 | ______ + * | | /|______| |______|\ | | + * Xtal-->| Gate |---| ______ ______ X-X--| Gate |--> + * |______| | \| | | |/ | |______| + * | | Div2 |-| Cnt2 | | + * | |______| |______| | + * |_______________________| + * + * The dividing can be switched to single or dual, with a counter + * for each divider to set when the switching is done. + * The entire dividing mechanism can be also bypassed. + */ + +struct meson_ao_cec_g12a_dualdiv_clk { + struct clk_hw hw; + struct regmap *regmap; +}; + +#define hw_to_meson_ao_cec_g12a_dualdiv_clk(_hw) \ + container_of(_hw, struct meson_ao_cec_g12a_dualdiv_clk, hw) \ + +static unsigned long +meson_ao_cec_g12a_dualdiv_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk = + hw_to_meson_ao_cec_g12a_dualdiv_clk(hw); + unsigned long n1; + u32 reg0, reg1; + + regmap_read(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, ®0); + regmap_read(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, ®1); + + if (reg1 & CECB_CLK_CNTL_BYPASS_EN) + return parent_rate; + + if (reg0 & CECB_CLK_CNTL_DUAL_EN) { + unsigned long n2, m1, m2, f1, f2, p1, p2; + + n1 = FIELD_GET(CECB_CLK_CNTL_N1, reg0) + 1; + n2 = FIELD_GET(CECB_CLK_CNTL_N2, reg0) + 1; + + m1 = FIELD_GET(CECB_CLK_CNTL_M1, reg1) + 1; + m2 = FIELD_GET(CECB_CLK_CNTL_M1, reg1) + 1; + + f1 = DIV_ROUND_CLOSEST(parent_rate, n1); + f2 = DIV_ROUND_CLOSEST(parent_rate, n2); + + p1 = DIV_ROUND_CLOSEST(100000000 * m1, f1 * (m1 + m2)); + p2 = DIV_ROUND_CLOSEST(100000000 * m2, f2 * (m1 + m2)); + + return DIV_ROUND_UP(100000000, p1 + p2); + } + + n1 = FIELD_GET(CECB_CLK_CNTL_N1, reg0) + 1; + + return DIV_ROUND_CLOSEST(parent_rate, n1); +} + +static int meson_ao_cec_g12a_dualdiv_clk_enable(struct clk_hw *hw) +{ + struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk = + hw_to_meson_ao_cec_g12a_dualdiv_clk(hw); + + + /* Disable Input & Output */ + regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, + CECB_CLK_CNTL_INPUT_EN | CECB_CLK_CNTL_OUTPUT_EN, + 0); + + /* Set N1 & N2 */ + regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, + CECB_CLK_CNTL_N1, + FIELD_PREP(CECB_CLK_CNTL_N1, 733 - 1)); + + regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, + CECB_CLK_CNTL_N2, + FIELD_PREP(CECB_CLK_CNTL_N2, 732 - 1)); + + /* Set M1 & M2 */ + regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG1, + CECB_CLK_CNTL_M1, + FIELD_PREP(CECB_CLK_CNTL_M1, 8 - 1)); + + regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG1, + CECB_CLK_CNTL_M2, + FIELD_PREP(CECB_CLK_CNTL_M2, 11 - 1)); + + /* Enable Dual divisor */ + regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, + CECB_CLK_CNTL_DUAL_EN, CECB_CLK_CNTL_DUAL_EN); + + /* Disable divisor bypass */ + regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG1, + CECB_CLK_CNTL_BYPASS_EN, 0); + + /* Enable Input & Output */ + regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, + CECB_CLK_CNTL_INPUT_EN | CECB_CLK_CNTL_OUTPUT_EN, + CECB_CLK_CNTL_INPUT_EN | CECB_CLK_CNTL_OUTPUT_EN); + + return 0; +} + +static void meson_ao_cec_g12a_dualdiv_clk_disable(struct clk_hw *hw) +{ + struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk = + hw_to_meson_ao_cec_g12a_dualdiv_clk(hw); + + regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, + CECB_CLK_CNTL_INPUT_EN | CECB_CLK_CNTL_OUTPUT_EN, + 0); +} + +static int meson_ao_cec_g12a_dualdiv_clk_is_enabled(struct clk_hw *hw) +{ + struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk = + hw_to_meson_ao_cec_g12a_dualdiv_clk(hw); + int val; + + regmap_read(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, &val); + + return !!(val & (CECB_CLK_CNTL_INPUT_EN | CECB_CLK_CNTL_OUTPUT_EN)); +} + +static const struct clk_ops meson_ao_cec_g12a_dualdiv_clk_ops = { + .recalc_rate = meson_ao_cec_g12a_dualdiv_clk_recalc_rate, + .is_enabled = meson_ao_cec_g12a_dualdiv_clk_is_enabled, + .enable = meson_ao_cec_g12a_dualdiv_clk_enable, + .disable = meson_ao_cec_g12a_dualdiv_clk_disable, +}; + +static int meson_ao_cec_g12a_setup_clk(struct meson_ao_cec_g12a_device *ao_cec) +{ + struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk; + struct device *dev = &ao_cec->pdev->dev; + struct clk_init_data init; + const char *parent_name; + struct clk *clk; + char *name; + + dualdiv_clk = devm_kzalloc(dev, sizeof(*dualdiv_clk), GFP_KERNEL); + if (!dualdiv_clk) + return -ENOMEM; + + name = kasprintf(GFP_KERNEL, "%s#dualdiv_clk", dev_name(dev)); + if (!name) + return -ENOMEM; + + parent_name = __clk_get_name(ao_cec->oscin); + + init.name = name; + init.ops = &meson_ao_cec_g12a_dualdiv_clk_ops; + init.flags = 0; + init.parent_names = &parent_name; + init.num_parents = 1; + dualdiv_clk->regmap = ao_cec->regmap; + dualdiv_clk->hw.init = &init; + + clk = devm_clk_register(dev, &dualdiv_clk->hw); + kfree(name); + if (IS_ERR(clk)) { + dev_err(dev, "failed to register clock\n"); + return PTR_ERR(clk); + } + + ao_cec->core = clk; + + return 0; +} + +static int meson_ao_cec_g12a_read(void *context, unsigned int addr, + unsigned int *data) +{ + struct meson_ao_cec_g12a_device *ao_cec = context; + u32 reg = FIELD_PREP(CECB_RW_ADDR, addr); + int ret = 0; + + ret = regmap_write(ao_cec->regmap, CECB_RW_REG, reg); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(ao_cec->regmap, CECB_RW_REG, reg, + !(reg & CECB_RW_BUS_BUSY), + 5, 1000); + if (ret) + return ret; + + ret = regmap_read(ao_cec->regmap, CECB_RW_REG, ®); + + *data = FIELD_GET(CECB_RW_RD_DATA, reg); + + return ret; +} + +static int meson_ao_cec_g12a_write(void *context, unsigned int addr, + unsigned int data) +{ + struct meson_ao_cec_g12a_device *ao_cec = context; + u32 reg = FIELD_PREP(CECB_RW_ADDR, addr) | + FIELD_PREP(CECB_RW_WR_DATA, data) | + CECB_RW_WRITE_EN; + + return regmap_write(ao_cec->regmap, CECB_RW_REG, reg); +} + +static const struct regmap_config meson_ao_cec_g12a_cec_regmap_conf = { + .reg_bits = 8, + .val_bits = 8, + .reg_read = meson_ao_cec_g12a_read, + .reg_write = meson_ao_cec_g12a_write, + .max_register = 0xffff, +}; + +static inline void +meson_ao_cec_g12a_irq_setup(struct meson_ao_cec_g12a_device *ao_cec, + bool enable) +{ + u32 cfg = CECB_INTR_DONE | CECB_INTR_EOM | CECB_INTR_NACK | + CECB_INTR_ARB_LOSS | CECB_INTR_INITIATOR_ERR | + CECB_INTR_FOLLOWER_ERR; + + regmap_write(ao_cec->regmap, CECB_INTR_MASKN_REG, + enable ? cfg : 0); +} + +static void meson_ao_cec_g12a_irq_rx(struct meson_ao_cec_g12a_device *ao_cec) +{ + int i, ret = 0; + u32 val; + + ret = regmap_read(ao_cec->regmap_cec, CECB_RX_CNT, &val); + + ao_cec->rx_msg.len = val; + if (ao_cec->rx_msg.len > CEC_MAX_MSG_SIZE) + ao_cec->rx_msg.len = CEC_MAX_MSG_SIZE; + + for (i = 0; i < ao_cec->rx_msg.len; i++) { + ret |= regmap_read(ao_cec->regmap_cec, + CECB_RX_DATA00 + i, &val); + + ao_cec->rx_msg.msg[i] = val & 0xff; + } + + ret |= regmap_write(ao_cec->regmap_cec, CECB_LOCK_BUF, 0); + if (ret) + return; + + cec_received_msg(ao_cec->adap, &ao_cec->rx_msg); +} + +static irqreturn_t meson_ao_cec_g12a_irq(int irq, void *data) +{ + struct meson_ao_cec_g12a_device *ao_cec = data; + u32 stat; + + regmap_read(ao_cec->regmap, CECB_INTR_STAT_REG, &stat); + if (stat) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +static irqreturn_t meson_ao_cec_g12a_irq_thread(int irq, void *data) +{ + struct meson_ao_cec_g12a_device *ao_cec = data; + u32 stat; + + regmap_read(ao_cec->regmap, CECB_INTR_STAT_REG, &stat); + regmap_write(ao_cec->regmap, CECB_INTR_CLR_REG, stat); + + if (stat & CECB_INTR_DONE) + cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_OK); + + if (stat & CECB_INTR_EOM) + meson_ao_cec_g12a_irq_rx(ao_cec); + + if (stat & CECB_INTR_NACK) + cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_NACK); + + if (stat & CECB_INTR_ARB_LOSS) { + regmap_write(ao_cec->regmap_cec, CECB_TX_CNT, 0); + regmap_update_bits(ao_cec->regmap_cec, CECB_CTRL, + CECB_CTRL_SEND | CECB_CTRL_TYPE, 0); + cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_ARB_LOST); + } + + /* Initiator reports an error on the CEC bus */ + if (stat & CECB_INTR_INITIATOR_ERR) + cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_ERROR); + + /* Follower reports a receive error, just reset RX buffer */ + if (stat & CECB_INTR_FOLLOWER_ERR) + regmap_write(ao_cec->regmap_cec, CECB_LOCK_BUF, 0); + + return IRQ_HANDLED; +} + +static int +meson_ao_cec_g12a_set_log_addr(struct cec_adapter *adap, u8 logical_addr) +{ + struct meson_ao_cec_g12a_device *ao_cec = adap->priv; + int ret = 0; + + if (logical_addr == CEC_LOG_ADDR_INVALID) { + /* Assume this will allways succeed */ + regmap_write(ao_cec->regmap_cec, CECB_LADD_LOW, 0); + regmap_write(ao_cec->regmap_cec, CECB_LADD_HIGH, 0); + + return 0; + } else if (logical_addr < 8) { + ret = regmap_update_bits(ao_cec->regmap_cec, CECB_LADD_LOW, + BIT(logical_addr), + BIT(logical_addr)); + } else { + ret = regmap_update_bits(ao_cec->regmap_cec, CECB_LADD_HIGH, + BIT(logical_addr - 8), + BIT(logical_addr - 8)); + } + + /* Always set Broadcast/Unregistered 15 address */ + ret |= regmap_update_bits(ao_cec->regmap_cec, CECB_LADD_HIGH, + BIT(CEC_LOG_ADDR_UNREGISTERED - 8), + BIT(CEC_LOG_ADDR_UNREGISTERED - 8)); + + return ret ? -EIO : 0; +} + +static int meson_ao_cec_g12a_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct meson_ao_cec_g12a_device *ao_cec = adap->priv; + unsigned int type; + int ret = 0; + u32 val; + int i; + + /* Check if RX is in progress */ + ret = regmap_read(ao_cec->regmap_cec, CECB_LOCK_BUF, &val); + if (ret) + return ret; + if (val & CECB_LOCK_BUF_EN) + return -EBUSY; + + /* Check if TX Busy */ + ret = regmap_read(ao_cec->regmap_cec, CECB_CTRL, &val); + if (ret) + return ret; + if (val & CECB_CTRL_SEND) + return -EBUSY; + + switch (signal_free_time) { + case CEC_SIGNAL_FREE_TIME_RETRY: + type = CECB_CTRL_TYPE_RETRY; + break; + case CEC_SIGNAL_FREE_TIME_NEXT_XFER: + type = CECB_CTRL_TYPE_NEXT; + break; + case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR: + default: + type = CECB_CTRL_TYPE_NEW; + break; + } + + for (i = 0; i < msg->len; i++) + ret |= regmap_write(ao_cec->regmap_cec, CECB_TX_DATA00 + i, + msg->msg[i]); + + ret |= regmap_write(ao_cec->regmap_cec, CECB_TX_CNT, msg->len); + if (ret) + return -EIO; + + ret = regmap_update_bits(ao_cec->regmap_cec, CECB_CTRL, + CECB_CTRL_SEND | + CECB_CTRL_TYPE, + CECB_CTRL_SEND | + FIELD_PREP(CECB_CTRL_TYPE, type)); + + return ret; +} + +static int meson_ao_cec_g12a_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct meson_ao_cec_g12a_device *ao_cec = adap->priv; + + meson_ao_cec_g12a_irq_setup(ao_cec, false); + + regmap_update_bits(ao_cec->regmap, CECB_GEN_CNTL_REG, + CECB_GEN_CNTL_RESET, CECB_GEN_CNTL_RESET); + + if (!enable) + return 0; + + /* Setup Filter */ + regmap_update_bits(ao_cec->regmap, CECB_GEN_CNTL_REG, + CECB_GEN_CNTL_FILTER_TICK_SEL | + CECB_GEN_CNTL_FILTER_DEL, + FIELD_PREP(CECB_GEN_CNTL_FILTER_TICK_SEL, + CECB_GEN_CNTL_FILTER_TICK_1US) | + FIELD_PREP(CECB_GEN_CNTL_FILTER_DEL, 7)); + + /* Enable System Clock */ + regmap_update_bits(ao_cec->regmap, CECB_GEN_CNTL_REG, + CECB_GEN_CNTL_SYS_CLK_EN, + CECB_GEN_CNTL_SYS_CLK_EN); + + /* Enable gated clock (Normal mode). */ + regmap_update_bits(ao_cec->regmap, CECB_GEN_CNTL_REG, + CECB_GEN_CNTL_CLK_CTRL_MASK, + FIELD_PREP(CECB_GEN_CNTL_CLK_CTRL_MASK, + CECB_GEN_CNTL_CLK_ENABLE)); + + /* Release Reset */ + regmap_update_bits(ao_cec->regmap, CECB_GEN_CNTL_REG, + CECB_GEN_CNTL_RESET, 0); + + if (ao_cec->data->ctrl2_setup) + regmap_write(ao_cec->regmap_cec, CECB_CTRL2, + FIELD_PREP(CECB_CTRL2_RISE_DEL_MAX, 2)); + + meson_ao_cec_g12a_irq_setup(ao_cec, true); + + return 0; +} + +static const struct cec_adap_ops meson_ao_cec_g12a_ops = { + .adap_enable = meson_ao_cec_g12a_adap_enable, + .adap_log_addr = meson_ao_cec_g12a_set_log_addr, + .adap_transmit = meson_ao_cec_g12a_transmit, +}; + +static int meson_ao_cec_g12a_probe(struct platform_device *pdev) +{ + struct meson_ao_cec_g12a_device *ao_cec; + struct device *hdmi_dev; + void __iomem *base; + int ret, irq; + + hdmi_dev = cec_notifier_parse_hdmi_phandle(&pdev->dev); + if (IS_ERR(hdmi_dev)) + return PTR_ERR(hdmi_dev); + + ao_cec = devm_kzalloc(&pdev->dev, sizeof(*ao_cec), GFP_KERNEL); + if (!ao_cec) + return -ENOMEM; + + ao_cec->data = of_device_get_match_data(&pdev->dev); + if (!ao_cec->data) { + dev_err(&pdev->dev, "failed to get match data\n"); + return -ENODEV; + } + + spin_lock_init(&ao_cec->cec_reg_lock); + ao_cec->pdev = pdev; + + ao_cec->adap = cec_allocate_adapter(&meson_ao_cec_g12a_ops, ao_cec, + "meson_g12a_ao_cec", + CEC_CAP_DEFAULTS | + CEC_CAP_CONNECTOR_INFO, + CEC_MAX_LOG_ADDRS); + if (IS_ERR(ao_cec->adap)) + return PTR_ERR(ao_cec->adap); + + ao_cec->adap->owner = THIS_MODULE; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) { + ret = PTR_ERR(base); + goto out_probe_adapter; + } + + ao_cec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &meson_ao_cec_g12a_regmap_conf); + if (IS_ERR(ao_cec->regmap)) { + ret = PTR_ERR(ao_cec->regmap); + goto out_probe_adapter; + } + + ao_cec->regmap_cec = devm_regmap_init(&pdev->dev, NULL, ao_cec, + &meson_ao_cec_g12a_cec_regmap_conf); + if (IS_ERR(ao_cec->regmap_cec)) { + ret = PTR_ERR(ao_cec->regmap_cec); + goto out_probe_adapter; + } + + irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(&pdev->dev, irq, + meson_ao_cec_g12a_irq, + meson_ao_cec_g12a_irq_thread, + 0, NULL, ao_cec); + if (ret) { + dev_err(&pdev->dev, "irq request failed\n"); + goto out_probe_adapter; + } + + ao_cec->oscin = devm_clk_get(&pdev->dev, "oscin"); + if (IS_ERR(ao_cec->oscin)) { + dev_err(&pdev->dev, "oscin clock request failed\n"); + ret = PTR_ERR(ao_cec->oscin); + goto out_probe_adapter; + } + + ret = meson_ao_cec_g12a_setup_clk(ao_cec); + if (ret) + goto out_probe_adapter; + + ret = clk_prepare_enable(ao_cec->core); + if (ret) { + dev_err(&pdev->dev, "core clock enable failed\n"); + goto out_probe_adapter; + } + + device_reset_optional(&pdev->dev); + + platform_set_drvdata(pdev, ao_cec); + + ao_cec->notify = cec_notifier_cec_adap_register(hdmi_dev, NULL, + ao_cec->adap); + if (!ao_cec->notify) { + ret = -ENOMEM; + goto out_probe_core_clk; + } + + ret = cec_register_adapter(ao_cec->adap, &pdev->dev); + if (ret < 0) + goto out_probe_notify; + + /* Setup Hardware */ + regmap_write(ao_cec->regmap, CECB_GEN_CNTL_REG, CECB_GEN_CNTL_RESET); + + return 0; + +out_probe_notify: + cec_notifier_cec_adap_unregister(ao_cec->notify, ao_cec->adap); + +out_probe_core_clk: + clk_disable_unprepare(ao_cec->core); + +out_probe_adapter: + cec_delete_adapter(ao_cec->adap); + + dev_err(&pdev->dev, "CEC controller registration failed\n"); + + return ret; +} + +static int meson_ao_cec_g12a_remove(struct platform_device *pdev) +{ + struct meson_ao_cec_g12a_device *ao_cec = platform_get_drvdata(pdev); + + clk_disable_unprepare(ao_cec->core); + + cec_notifier_cec_adap_unregister(ao_cec->notify, ao_cec->adap); + + cec_unregister_adapter(ao_cec->adap); + + return 0; +} + +static const struct meson_ao_cec_g12a_data ao_cec_g12a_data = { + .ctrl2_setup = false, +}; + +static const struct meson_ao_cec_g12a_data ao_cec_sm1_data = { + .ctrl2_setup = true, +}; + +static const struct of_device_id meson_ao_cec_g12a_of_match[] = { + { + .compatible = "amlogic,meson-g12a-ao-cec", + .data = &ao_cec_g12a_data, + }, + { + .compatible = "amlogic,meson-sm1-ao-cec", + .data = &ao_cec_sm1_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, meson_ao_cec_g12a_of_match); + +static struct platform_driver meson_ao_cec_g12a_driver = { + .probe = meson_ao_cec_g12a_probe, + .remove = meson_ao_cec_g12a_remove, + .driver = { + .name = "meson-ao-cec-g12a", + .of_match_table = of_match_ptr(meson_ao_cec_g12a_of_match), + }, +}; + +module_platform_driver(meson_ao_cec_g12a_driver); + +MODULE_DESCRIPTION("Meson AO CEC G12A Controller driver"); +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/cec/platform/meson/ao-cec.c b/drivers/media/cec/platform/meson/ao-cec.c new file mode 100644 index 000000000..6b440f063 --- /dev/null +++ b/drivers/media/cec/platform/meson/ao-cec.c @@ -0,0 +1,730 @@ +/* + * Driver for Amlogic Meson AO CEC Controller + * + * Copyright (C) 2015 Amlogic, Inc. All rights reserved + * Copyright (C) 2017 BayLibre, SAS + * Author: Neil Armstrong <narmstrong@baylibre.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/reset.h> +#include <media/cec.h> +#include <media/cec-notifier.h> + +/* CEC Registers */ + +/* + * [2:1] cntl_clk + * - 0 = Disable clk (Power-off mode) + * - 1 = Enable gated clock (Normal mode) + * - 2 = Enable free-run clk (Debug mode) + */ +#define CEC_GEN_CNTL_REG 0x00 + +#define CEC_GEN_CNTL_RESET BIT(0) +#define CEC_GEN_CNTL_CLK_DISABLE 0 +#define CEC_GEN_CNTL_CLK_ENABLE 1 +#define CEC_GEN_CNTL_CLK_ENABLE_DBG 2 +#define CEC_GEN_CNTL_CLK_CTRL_MASK GENMASK(2, 1) + +/* + * [7:0] cec_reg_addr + * [15:8] cec_reg_wrdata + * [16] cec_reg_wr + * - 0 = Read + * - 1 = Write + * [23] bus free + * [31:24] cec_reg_rddata + */ +#define CEC_RW_REG 0x04 + +#define CEC_RW_ADDR GENMASK(7, 0) +#define CEC_RW_WR_DATA GENMASK(15, 8) +#define CEC_RW_WRITE_EN BIT(16) +#define CEC_RW_BUS_BUSY BIT(23) +#define CEC_RW_RD_DATA GENMASK(31, 24) + +/* + * [1] tx intr + * [2] rx intr + */ +#define CEC_INTR_MASKN_REG 0x08 +#define CEC_INTR_CLR_REG 0x0c +#define CEC_INTR_STAT_REG 0x10 + +#define CEC_INTR_TX BIT(1) +#define CEC_INTR_RX BIT(2) + +/* CEC Commands */ + +#define CEC_TX_MSG_0_HEADER 0x00 +#define CEC_TX_MSG_1_OPCODE 0x01 +#define CEC_TX_MSG_2_OP1 0x02 +#define CEC_TX_MSG_3_OP2 0x03 +#define CEC_TX_MSG_4_OP3 0x04 +#define CEC_TX_MSG_5_OP4 0x05 +#define CEC_TX_MSG_6_OP5 0x06 +#define CEC_TX_MSG_7_OP6 0x07 +#define CEC_TX_MSG_8_OP7 0x08 +#define CEC_TX_MSG_9_OP8 0x09 +#define CEC_TX_MSG_A_OP9 0x0A +#define CEC_TX_MSG_B_OP10 0x0B +#define CEC_TX_MSG_C_OP11 0x0C +#define CEC_TX_MSG_D_OP12 0x0D +#define CEC_TX_MSG_E_OP13 0x0E +#define CEC_TX_MSG_F_OP14 0x0F +#define CEC_TX_MSG_LENGTH 0x10 +#define CEC_TX_MSG_CMD 0x11 +#define CEC_TX_WRITE_BUF 0x12 +#define CEC_TX_CLEAR_BUF 0x13 +#define CEC_RX_MSG_CMD 0x14 +#define CEC_RX_CLEAR_BUF 0x15 +#define CEC_LOGICAL_ADDR0 0x16 +#define CEC_LOGICAL_ADDR1 0x17 +#define CEC_LOGICAL_ADDR2 0x18 +#define CEC_LOGICAL_ADDR3 0x19 +#define CEC_LOGICAL_ADDR4 0x1A +#define CEC_CLOCK_DIV_H 0x1B +#define CEC_CLOCK_DIV_L 0x1C +#define CEC_QUIESCENT_25MS_BIT7_0 0x20 +#define CEC_QUIESCENT_25MS_BIT11_8 0x21 +#define CEC_STARTBITMINL2H_3MS5_BIT7_0 0x22 +#define CEC_STARTBITMINL2H_3MS5_BIT8 0x23 +#define CEC_STARTBITMAXL2H_3MS9_BIT7_0 0x24 +#define CEC_STARTBITMAXL2H_3MS9_BIT8 0x25 +#define CEC_STARTBITMINH_0MS6_BIT7_0 0x26 +#define CEC_STARTBITMINH_0MS6_BIT8 0x27 +#define CEC_STARTBITMAXH_1MS0_BIT7_0 0x28 +#define CEC_STARTBITMAXH_1MS0_BIT8 0x29 +#define CEC_STARTBITMINTOT_4MS3_BIT7_0 0x2A +#define CEC_STARTBITMINTOT_4MS3_BIT9_8 0x2B +#define CEC_STARTBITMAXTOT_4MS7_BIT7_0 0x2C +#define CEC_STARTBITMAXTOT_4MS7_BIT9_8 0x2D +#define CEC_LOGIC1MINL2H_0MS4_BIT7_0 0x2E +#define CEC_LOGIC1MINL2H_0MS4_BIT8 0x2F +#define CEC_LOGIC1MAXL2H_0MS8_BIT7_0 0x30 +#define CEC_LOGIC1MAXL2H_0MS8_BIT8 0x31 +#define CEC_LOGIC0MINL2H_1MS3_BIT7_0 0x32 +#define CEC_LOGIC0MINL2H_1MS3_BIT8 0x33 +#define CEC_LOGIC0MAXL2H_1MS7_BIT7_0 0x34 +#define CEC_LOGIC0MAXL2H_1MS7_BIT8 0x35 +#define CEC_LOGICMINTOTAL_2MS05_BIT7_0 0x36 +#define CEC_LOGICMINTOTAL_2MS05_BIT9_8 0x37 +#define CEC_LOGICMAXHIGH_2MS8_BIT7_0 0x38 +#define CEC_LOGICMAXHIGH_2MS8_BIT8 0x39 +#define CEC_LOGICERRLOW_3MS4_BIT7_0 0x3A +#define CEC_LOGICERRLOW_3MS4_BIT8 0x3B +#define CEC_NOMSMPPOINT_1MS05 0x3C +#define CEC_DELCNTR_LOGICERR 0x3E +#define CEC_TXTIME_17MS_BIT7_0 0x40 +#define CEC_TXTIME_17MS_BIT10_8 0x41 +#define CEC_TXTIME_2BIT_BIT7_0 0x42 +#define CEC_TXTIME_2BIT_BIT10_8 0x43 +#define CEC_TXTIME_4BIT_BIT7_0 0x44 +#define CEC_TXTIME_4BIT_BIT10_8 0x45 +#define CEC_STARTBITNOML2H_3MS7_BIT7_0 0x46 +#define CEC_STARTBITNOML2H_3MS7_BIT8 0x47 +#define CEC_STARTBITNOMH_0MS8_BIT7_0 0x48 +#define CEC_STARTBITNOMH_0MS8_BIT8 0x49 +#define CEC_LOGIC1NOML2H_0MS6_BIT7_0 0x4A +#define CEC_LOGIC1NOML2H_0MS6_BIT8 0x4B +#define CEC_LOGIC0NOML2H_1MS5_BIT7_0 0x4C +#define CEC_LOGIC0NOML2H_1MS5_BIT8 0x4D +#define CEC_LOGIC1NOMH_1MS8_BIT7_0 0x4E +#define CEC_LOGIC1NOMH_1MS8_BIT8 0x4F +#define CEC_LOGIC0NOMH_0MS9_BIT7_0 0x50 +#define CEC_LOGIC0NOMH_0MS9_BIT8 0x51 +#define CEC_LOGICERRLOW_3MS6_BIT7_0 0x52 +#define CEC_LOGICERRLOW_3MS6_BIT8 0x53 +#define CEC_CHKCONTENTION_0MS1 0x54 +#define CEC_PREPARENXTBIT_0MS05_BIT7_0 0x56 +#define CEC_PREPARENXTBIT_0MS05_BIT8 0x57 +#define CEC_NOMSMPACKPOINT_0MS45 0x58 +#define CEC_ACK0NOML2H_1MS5_BIT7_0 0x5A +#define CEC_ACK0NOML2H_1MS5_BIT8 0x5B +#define CEC_BUGFIX_DISABLE_0 0x60 +#define CEC_BUGFIX_DISABLE_1 0x61 +#define CEC_RX_MSG_0_HEADER 0x80 +#define CEC_RX_MSG_1_OPCODE 0x81 +#define CEC_RX_MSG_2_OP1 0x82 +#define CEC_RX_MSG_3_OP2 0x83 +#define CEC_RX_MSG_4_OP3 0x84 +#define CEC_RX_MSG_5_OP4 0x85 +#define CEC_RX_MSG_6_OP5 0x86 +#define CEC_RX_MSG_7_OP6 0x87 +#define CEC_RX_MSG_8_OP7 0x88 +#define CEC_RX_MSG_9_OP8 0x89 +#define CEC_RX_MSG_A_OP9 0x8A +#define CEC_RX_MSG_B_OP10 0x8B +#define CEC_RX_MSG_C_OP11 0x8C +#define CEC_RX_MSG_D_OP12 0x8D +#define CEC_RX_MSG_E_OP13 0x8E +#define CEC_RX_MSG_F_OP14 0x8F +#define CEC_RX_MSG_LENGTH 0x90 +#define CEC_RX_MSG_STATUS 0x91 +#define CEC_RX_NUM_MSG 0x92 +#define CEC_TX_MSG_STATUS 0x93 +#define CEC_TX_NUM_MSG 0x94 + + +/* CEC_TX_MSG_CMD definition */ +#define TX_NO_OP 0 /* No transaction */ +#define TX_REQ_CURRENT 1 /* Transmit earliest message in buffer */ +#define TX_ABORT 2 /* Abort transmitting earliest message */ +#define TX_REQ_NEXT 3 /* Overwrite earliest msg, transmit next */ + +/* tx_msg_status definition */ +#define TX_IDLE 0 /* No transaction */ +#define TX_BUSY 1 /* Transmitter is busy */ +#define TX_DONE 2 /* Message successfully transmitted */ +#define TX_ERROR 3 /* Message transmitted with error */ + +/* rx_msg_cmd */ +#define RX_NO_OP 0 /* No transaction */ +#define RX_ACK_CURRENT 1 /* Read earliest message in buffer */ +#define RX_DISABLE 2 /* Disable receiving latest message */ +#define RX_ACK_NEXT 3 /* Clear earliest msg, read next */ + +/* rx_msg_status */ +#define RX_IDLE 0 /* No transaction */ +#define RX_BUSY 1 /* Receiver is busy */ +#define RX_DONE 2 /* Message has been received successfully */ +#define RX_ERROR 3 /* Message has been received with error */ + +/* RX_CLEAR_BUF options */ +#define CLEAR_START 1 +#define CLEAR_STOP 0 + +/* CEC_LOGICAL_ADDRx options */ +#define LOGICAL_ADDR_MASK 0xf +#define LOGICAL_ADDR_VALID BIT(4) +#define LOGICAL_ADDR_DISABLE 0 + +#define CEC_CLK_RATE 32768 + +struct meson_ao_cec_device { + struct platform_device *pdev; + void __iomem *base; + struct clk *core; + spinlock_t cec_reg_lock; + struct cec_notifier *notify; + struct cec_adapter *adap; + struct cec_msg rx_msg; +}; + +#define writel_bits_relaxed(mask, val, addr) \ + writel_relaxed((readl_relaxed(addr) & ~(mask)) | (val), addr) + +static inline int meson_ao_cec_wait_busy(struct meson_ao_cec_device *ao_cec) +{ + ktime_t timeout = ktime_add_us(ktime_get(), 5000); + + while (readl_relaxed(ao_cec->base + CEC_RW_REG) & CEC_RW_BUS_BUSY) { + if (ktime_compare(ktime_get(), timeout) > 0) + return -ETIMEDOUT; + } + + return 0; +} + +static void meson_ao_cec_read(struct meson_ao_cec_device *ao_cec, + unsigned long address, u8 *data, + int *res) +{ + unsigned long flags; + u32 reg = FIELD_PREP(CEC_RW_ADDR, address); + int ret = 0; + + if (res && *res) + return; + + spin_lock_irqsave(&ao_cec->cec_reg_lock, flags); + + ret = meson_ao_cec_wait_busy(ao_cec); + if (ret) + goto read_out; + + writel_relaxed(reg, ao_cec->base + CEC_RW_REG); + + ret = meson_ao_cec_wait_busy(ao_cec); + if (ret) + goto read_out; + + *data = FIELD_GET(CEC_RW_RD_DATA, + readl_relaxed(ao_cec->base + CEC_RW_REG)); + +read_out: + spin_unlock_irqrestore(&ao_cec->cec_reg_lock, flags); + + if (res) + *res = ret; +} + +static void meson_ao_cec_write(struct meson_ao_cec_device *ao_cec, + unsigned long address, u8 data, + int *res) +{ + unsigned long flags; + u32 reg = FIELD_PREP(CEC_RW_ADDR, address) | + FIELD_PREP(CEC_RW_WR_DATA, data) | + CEC_RW_WRITE_EN; + int ret = 0; + + if (res && *res) + return; + + spin_lock_irqsave(&ao_cec->cec_reg_lock, flags); + + ret = meson_ao_cec_wait_busy(ao_cec); + if (ret) + goto write_out; + + writel_relaxed(reg, ao_cec->base + CEC_RW_REG); + +write_out: + spin_unlock_irqrestore(&ao_cec->cec_reg_lock, flags); + + if (res) + *res = ret; +} + +static inline void meson_ao_cec_irq_setup(struct meson_ao_cec_device *ao_cec, + bool enable) +{ + u32 cfg = CEC_INTR_TX | CEC_INTR_RX; + + writel_bits_relaxed(cfg, enable ? cfg : 0, + ao_cec->base + CEC_INTR_MASKN_REG); +} + +static inline int meson_ao_cec_clear(struct meson_ao_cec_device *ao_cec) +{ + int ret = 0; + + meson_ao_cec_write(ao_cec, CEC_RX_MSG_CMD, RX_DISABLE, &ret); + meson_ao_cec_write(ao_cec, CEC_TX_MSG_CMD, TX_ABORT, &ret); + meson_ao_cec_write(ao_cec, CEC_RX_CLEAR_BUF, 1, &ret); + meson_ao_cec_write(ao_cec, CEC_TX_CLEAR_BUF, 1, &ret); + if (ret) + return ret; + + udelay(100); + + meson_ao_cec_write(ao_cec, CEC_RX_CLEAR_BUF, 0, &ret); + meson_ao_cec_write(ao_cec, CEC_TX_CLEAR_BUF, 0, &ret); + if (ret) + return ret; + + udelay(100); + + meson_ao_cec_write(ao_cec, CEC_RX_MSG_CMD, RX_NO_OP, &ret); + meson_ao_cec_write(ao_cec, CEC_TX_MSG_CMD, TX_NO_OP, &ret); + + return ret; +} + +static int meson_ao_cec_arbit_bit_time_set(struct meson_ao_cec_device *ao_cec, + unsigned int bit_set, + unsigned int time_set) +{ + int ret = 0; + + switch (bit_set) { + case CEC_SIGNAL_FREE_TIME_RETRY: + meson_ao_cec_write(ao_cec, CEC_TXTIME_4BIT_BIT7_0, + time_set & 0xff, &ret); + meson_ao_cec_write(ao_cec, CEC_TXTIME_4BIT_BIT10_8, + (time_set >> 8) & 0x7, &ret); + break; + + case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR: + meson_ao_cec_write(ao_cec, CEC_TXTIME_2BIT_BIT7_0, + time_set & 0xff, &ret); + meson_ao_cec_write(ao_cec, CEC_TXTIME_2BIT_BIT10_8, + (time_set >> 8) & 0x7, &ret); + break; + + case CEC_SIGNAL_FREE_TIME_NEXT_XFER: + meson_ao_cec_write(ao_cec, CEC_TXTIME_17MS_BIT7_0, + time_set & 0xff, &ret); + meson_ao_cec_write(ao_cec, CEC_TXTIME_17MS_BIT10_8, + (time_set >> 8) & 0x7, &ret); + break; + } + + return ret; +} + +static irqreturn_t meson_ao_cec_irq(int irq, void *data) +{ + struct meson_ao_cec_device *ao_cec = data; + u32 stat = readl_relaxed(ao_cec->base + CEC_INTR_STAT_REG); + + if (stat) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +static void meson_ao_cec_irq_tx(struct meson_ao_cec_device *ao_cec) +{ + unsigned long tx_status = 0; + u8 stat; + int ret = 0; + + meson_ao_cec_read(ao_cec, CEC_TX_MSG_STATUS, &stat, &ret); + if (ret) + goto tx_reg_err; + + switch (stat) { + case TX_DONE: + tx_status = CEC_TX_STATUS_OK; + break; + + case TX_BUSY: + tx_status = CEC_TX_STATUS_ARB_LOST; + break; + + case TX_IDLE: + tx_status = CEC_TX_STATUS_LOW_DRIVE; + break; + + case TX_ERROR: + default: + tx_status = CEC_TX_STATUS_NACK; + break; + } + + /* Clear Interruption */ + writel_relaxed(CEC_INTR_TX, ao_cec->base + CEC_INTR_CLR_REG); + + /* Stop TX */ + meson_ao_cec_write(ao_cec, CEC_TX_MSG_CMD, TX_NO_OP, &ret); + if (ret) + goto tx_reg_err; + + cec_transmit_attempt_done(ao_cec->adap, tx_status); + return; + +tx_reg_err: + cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_ERROR); +} + +static void meson_ao_cec_irq_rx(struct meson_ao_cec_device *ao_cec) +{ + int i, ret = 0; + u8 reg; + + meson_ao_cec_read(ao_cec, CEC_RX_MSG_STATUS, ®, &ret); + if (reg != RX_DONE) + goto rx_out; + + meson_ao_cec_read(ao_cec, CEC_RX_NUM_MSG, ®, &ret); + if (reg != 1) + goto rx_out; + + meson_ao_cec_read(ao_cec, CEC_RX_MSG_LENGTH, ®, &ret); + + ao_cec->rx_msg.len = reg + 1; + if (ao_cec->rx_msg.len > CEC_MAX_MSG_SIZE) + ao_cec->rx_msg.len = CEC_MAX_MSG_SIZE; + + for (i = 0; i < ao_cec->rx_msg.len; i++) { + u8 byte; + + meson_ao_cec_read(ao_cec, CEC_RX_MSG_0_HEADER + i, &byte, &ret); + + ao_cec->rx_msg.msg[i] = byte; + } + + if (ret) + goto rx_out; + + cec_received_msg(ao_cec->adap, &ao_cec->rx_msg); + +rx_out: + /* Clear Interruption */ + writel_relaxed(CEC_INTR_RX, ao_cec->base + CEC_INTR_CLR_REG); + + /* Ack RX message */ + meson_ao_cec_write(ao_cec, CEC_RX_MSG_CMD, RX_ACK_CURRENT, &ret); + meson_ao_cec_write(ao_cec, CEC_RX_MSG_CMD, RX_NO_OP, &ret); + + /* Clear RX buffer */ + meson_ao_cec_write(ao_cec, CEC_RX_CLEAR_BUF, CLEAR_START, &ret); + meson_ao_cec_write(ao_cec, CEC_RX_CLEAR_BUF, CLEAR_STOP, &ret); +} + +static irqreturn_t meson_ao_cec_irq_thread(int irq, void *data) +{ + struct meson_ao_cec_device *ao_cec = data; + u32 stat = readl_relaxed(ao_cec->base + CEC_INTR_STAT_REG); + + if (stat & CEC_INTR_TX) + meson_ao_cec_irq_tx(ao_cec); + + meson_ao_cec_irq_rx(ao_cec); + + return IRQ_HANDLED; +} + +static int meson_ao_cec_set_log_addr(struct cec_adapter *adap, u8 logical_addr) +{ + struct meson_ao_cec_device *ao_cec = adap->priv; + int ret = 0; + + meson_ao_cec_write(ao_cec, CEC_LOGICAL_ADDR0, + LOGICAL_ADDR_DISABLE, &ret); + if (ret) + return ret; + + ret = meson_ao_cec_clear(ao_cec); + if (ret) + return ret; + + if (logical_addr == CEC_LOG_ADDR_INVALID) + return 0; + + meson_ao_cec_write(ao_cec, CEC_LOGICAL_ADDR0, + logical_addr & LOGICAL_ADDR_MASK, &ret); + if (ret) + return ret; + + udelay(100); + + meson_ao_cec_write(ao_cec, CEC_LOGICAL_ADDR0, + (logical_addr & LOGICAL_ADDR_MASK) | + LOGICAL_ADDR_VALID, &ret); + + return ret; +} + +static int meson_ao_cec_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct meson_ao_cec_device *ao_cec = adap->priv; + int i, ret = 0; + u8 reg; + + meson_ao_cec_read(ao_cec, CEC_TX_MSG_STATUS, ®, &ret); + if (ret) + return ret; + + if (reg == TX_BUSY) { + dev_dbg(&ao_cec->pdev->dev, "%s: busy TX: aborting\n", + __func__); + meson_ao_cec_write(ao_cec, CEC_TX_MSG_CMD, TX_ABORT, &ret); + } + + for (i = 0; i < msg->len; i++) { + meson_ao_cec_write(ao_cec, CEC_TX_MSG_0_HEADER + i, + msg->msg[i], &ret); + } + + meson_ao_cec_write(ao_cec, CEC_TX_MSG_LENGTH, msg->len - 1, &ret); + meson_ao_cec_write(ao_cec, CEC_TX_MSG_CMD, TX_REQ_CURRENT, &ret); + + return ret; +} + +static int meson_ao_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct meson_ao_cec_device *ao_cec = adap->priv; + int ret; + + meson_ao_cec_irq_setup(ao_cec, false); + + writel_bits_relaxed(CEC_GEN_CNTL_RESET, CEC_GEN_CNTL_RESET, + ao_cec->base + CEC_GEN_CNTL_REG); + + if (!enable) + return 0; + + /* Enable gated clock (Normal mode). */ + writel_bits_relaxed(CEC_GEN_CNTL_CLK_CTRL_MASK, + FIELD_PREP(CEC_GEN_CNTL_CLK_CTRL_MASK, + CEC_GEN_CNTL_CLK_ENABLE), + ao_cec->base + CEC_GEN_CNTL_REG); + + udelay(100); + + /* Release Reset */ + writel_bits_relaxed(CEC_GEN_CNTL_RESET, 0, + ao_cec->base + CEC_GEN_CNTL_REG); + + /* Clear buffers */ + ret = meson_ao_cec_clear(ao_cec); + if (ret) + return ret; + + /* CEC arbitration 3/5/7 bit time set. */ + ret = meson_ao_cec_arbit_bit_time_set(ao_cec, + CEC_SIGNAL_FREE_TIME_RETRY, + 0x118); + if (ret) + return ret; + ret = meson_ao_cec_arbit_bit_time_set(ao_cec, + CEC_SIGNAL_FREE_TIME_NEW_INITIATOR, + 0x000); + if (ret) + return ret; + ret = meson_ao_cec_arbit_bit_time_set(ao_cec, + CEC_SIGNAL_FREE_TIME_NEXT_XFER, + 0x2aa); + if (ret) + return ret; + + meson_ao_cec_irq_setup(ao_cec, true); + + return 0; +} + +static const struct cec_adap_ops meson_ao_cec_ops = { + .adap_enable = meson_ao_cec_adap_enable, + .adap_log_addr = meson_ao_cec_set_log_addr, + .adap_transmit = meson_ao_cec_transmit, +}; + +static int meson_ao_cec_probe(struct platform_device *pdev) +{ + struct meson_ao_cec_device *ao_cec; + struct device *hdmi_dev; + int ret, irq; + + hdmi_dev = cec_notifier_parse_hdmi_phandle(&pdev->dev); + + if (IS_ERR(hdmi_dev)) + return PTR_ERR(hdmi_dev); + + ao_cec = devm_kzalloc(&pdev->dev, sizeof(*ao_cec), GFP_KERNEL); + if (!ao_cec) + return -ENOMEM; + + spin_lock_init(&ao_cec->cec_reg_lock); + + ao_cec->adap = cec_allocate_adapter(&meson_ao_cec_ops, ao_cec, + "meson_ao_cec", + CEC_CAP_DEFAULTS | + CEC_CAP_CONNECTOR_INFO, + 1); /* Use 1 for now */ + if (IS_ERR(ao_cec->adap)) + return PTR_ERR(ao_cec->adap); + + ao_cec->adap->owner = THIS_MODULE; + + ao_cec->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ao_cec->base)) { + ret = PTR_ERR(ao_cec->base); + goto out_probe_adapter; + } + + irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(&pdev->dev, irq, + meson_ao_cec_irq, + meson_ao_cec_irq_thread, + 0, NULL, ao_cec); + if (ret) { + dev_err(&pdev->dev, "irq request failed\n"); + goto out_probe_adapter; + } + + ao_cec->core = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(ao_cec->core)) { + dev_err(&pdev->dev, "core clock request failed\n"); + ret = PTR_ERR(ao_cec->core); + goto out_probe_adapter; + } + + ret = clk_prepare_enable(ao_cec->core); + if (ret) { + dev_err(&pdev->dev, "core clock enable failed\n"); + goto out_probe_adapter; + } + + ret = clk_set_rate(ao_cec->core, CEC_CLK_RATE); + if (ret) { + dev_err(&pdev->dev, "core clock set rate failed\n"); + goto out_probe_clk; + } + + device_reset_optional(&pdev->dev); + + ao_cec->pdev = pdev; + platform_set_drvdata(pdev, ao_cec); + + ao_cec->notify = cec_notifier_cec_adap_register(hdmi_dev, NULL, + ao_cec->adap); + if (!ao_cec->notify) { + ret = -ENOMEM; + goto out_probe_clk; + } + + ret = cec_register_adapter(ao_cec->adap, &pdev->dev); + if (ret < 0) + goto out_probe_notify; + + /* Setup Hardware */ + writel_relaxed(CEC_GEN_CNTL_RESET, + ao_cec->base + CEC_GEN_CNTL_REG); + + return 0; + +out_probe_notify: + cec_notifier_cec_adap_unregister(ao_cec->notify, ao_cec->adap); + +out_probe_clk: + clk_disable_unprepare(ao_cec->core); + +out_probe_adapter: + cec_delete_adapter(ao_cec->adap); + + dev_err(&pdev->dev, "CEC controller registration failed\n"); + + return ret; +} + +static int meson_ao_cec_remove(struct platform_device *pdev) +{ + struct meson_ao_cec_device *ao_cec = platform_get_drvdata(pdev); + + clk_disable_unprepare(ao_cec->core); + + cec_notifier_cec_adap_unregister(ao_cec->notify, ao_cec->adap); + cec_unregister_adapter(ao_cec->adap); + + return 0; +} + +static const struct of_device_id meson_ao_cec_of_match[] = { + { .compatible = "amlogic,meson-gx-ao-cec", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, meson_ao_cec_of_match); + +static struct platform_driver meson_ao_cec_driver = { + .probe = meson_ao_cec_probe, + .remove = meson_ao_cec_remove, + .driver = { + .name = "meson-ao-cec", + .of_match_table = of_match_ptr(meson_ao_cec_of_match), + }, +}; + +module_platform_driver(meson_ao_cec_driver); + +MODULE_DESCRIPTION("Meson AO CEC Controller driver"); +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_LICENSE("GPL"); |