// 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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_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_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; }; 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); unsigned long flags; int ret = 0; spin_lock_irqsave(&ao_cec->cec_reg_lock, flags); ret = regmap_write(ao_cec->regmap, CECB_RW_REG, reg); if (ret) goto read_out; ret = regmap_read_poll_timeout(ao_cec->regmap, CECB_RW_REG, reg, !(reg & CECB_RW_BUS_BUSY), 5, 1000); if (ret) goto read_out; ret = regmap_read(ao_cec->regmap, CECB_RW_REG, ®); *data = FIELD_GET(CECB_RW_RD_DATA, reg); read_out: spin_unlock_irqrestore(&ao_cec->cec_reg_lock, flags); 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; unsigned long flags; u32 reg = FIELD_PREP(CECB_RW_ADDR, addr) | FIELD_PREP(CECB_RW_WR_DATA, data) | CECB_RW_WRITE_EN; int ret = 0; spin_lock_irqsave(&ao_cec->cec_reg_lock, flags); ret = regmap_write(ao_cec->regmap, CECB_RW_REG, reg); spin_unlock_irqrestore(&ao_cec->cec_reg_lock, flags); return ret; } 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, .fast_io = true, }; 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); 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; struct resource *res; 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; spin_lock_init(&ao_cec->cec_reg_lock); ao_cec->pdev = pdev; ao_cec->notify = cec_notifier_get(hdmi_dev); if (!ao_cec->notify) return -ENOMEM; ao_cec->adap = cec_allocate_adapter(&meson_ao_cec_g12a_ops, ao_cec, "meson_g12a_ao_cec", CEC_CAP_DEFAULTS, CEC_MAX_LOG_ADDRS); if (IS_ERR(ao_cec->adap)) { ret = PTR_ERR(ao_cec->adap); goto out_probe_notify; } ao_cec->adap->owner = THIS_MODULE; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); 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); ret = cec_register_adapter(ao_cec->adap, &pdev->dev); if (ret < 0) { cec_notifier_put(ao_cec->notify); goto out_probe_core_clk; } /* Setup Hardware */ regmap_write(ao_cec->regmap, CECB_GEN_CNTL_REG, CECB_GEN_CNTL_RESET); cec_register_cec_notifier(ao_cec->adap, ao_cec->notify); return 0; out_probe_core_clk: clk_disable_unprepare(ao_cec->core); out_probe_adapter: cec_delete_adapter(ao_cec->adap); out_probe_notify: cec_notifier_put(ao_cec->notify); 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_unregister_adapter(ao_cec->adap); cec_notifier_put(ao_cec->notify); return 0; } static const struct of_device_id meson_ao_cec_g12a_of_match[] = { { .compatible = "amlogic,meson-g12a-ao-cec", }, { /* 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 "); MODULE_LICENSE("GPL");