| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. | |
| * Copyright (c) 2024, Linaro Limited | |
| * Author: Neil Armstrong <neil.armstrong@linaro.org> | |
| * | |
| * Based on Linux driver: drivers/i2c/busses/i2c-qcom-geni.c | |
| */ | |
| #include <log.h> | |
| #include <dm/device.h> | |
| #include <dm/read.h> | |
| #include <dm/device_compat.h> | |
| #include <linux/delay.h> | |
| #include <linux/errno.h> | |
| #include <linux/err.h> | |
| #include <linux/bitops.h> | |
| #include <asm/io.h> | |
| #include <i2c.h> | |
| #include <fdtdec.h> | |
| #include <clk.h> | |
| #include <reset.h> | |
| #include <time.h> | |
| #include <soc/qcom/geni-se.h> | |
| #define SE_I2C_TX_TRANS_LEN 0x26c | |
| #define SE_I2C_RX_TRANS_LEN 0x270 | |
| #define SE_I2C_SCL_COUNTERS 0x278 | |
| #define SE_I2C_ERR (M_CMD_OVERRUN_EN | M_ILLEGAL_CMD_EN | M_CMD_FAILURE_EN |\ | |
| M_GP_IRQ_1_EN | M_GP_IRQ_3_EN | M_GP_IRQ_4_EN) | |
| #define SE_I2C_ABORT BIT(1) | |
| /* M_CMD OP codes for I2C */ | |
| #define I2C_WRITE 0x1 | |
| #define I2C_READ 0x2 | |
| #define I2C_WRITE_READ 0x3 | |
| #define I2C_ADDR_ONLY 0x4 | |
| #define I2C_BUS_CLEAR 0x6 | |
| #define I2C_STOP_ON_BUS 0x7 | |
| /* M_CMD params for I2C */ | |
| #define PRE_CMD_DELAY BIT(0) | |
| #define TIMESTAMP_BEFORE BIT(1) | |
| #define STOP_STRETCH BIT(2) | |
| #define TIMESTAMP_AFTER BIT(3) | |
| #define POST_COMMAND_DELAY BIT(4) | |
| #define IGNORE_ADD_NACK BIT(6) | |
| #define READ_FINISHED_WITH_ACK BIT(7) | |
| #define BYPASS_ADDR_PHASE BIT(8) | |
| #define SLV_ADDR_MSK GENMASK(15, 9) | |
| #define SLV_ADDR_SHFT 9 | |
| /* I2C SCL COUNTER fields */ | |
| #define HIGH_COUNTER_MSK GENMASK(29, 20) | |
| #define HIGH_COUNTER_SHFT 20 | |
| #define LOW_COUNTER_MSK GENMASK(19, 10) | |
| #define LOW_COUNTER_SHFT 10 | |
| #define CYCLE_COUNTER_MSK GENMASK(9, 0) | |
| #define I2C_PACK_TX BIT(0) | |
| #define I2C_PACK_RX BIT(1) | |
| #define PACKING_BYTES_PW 4 | |
| #define GENI_I2C_IS_MASTER_HUB BIT(0) | |
| #define I2C_TIMEOUT_MS 100 | |
| struct geni_i2c_clk_fld { | |
| u32 clk_freq_out; | |
| u8 clk_div; | |
| u8 t_high_cnt; | |
| u8 t_low_cnt; | |
| u8 t_cycle_cnt; | |
| }; | |
| struct geni_i2c_priv { | |
| fdt_addr_t wrapper; | |
| phys_addr_t base; | |
| struct clk core; | |
| struct clk se; | |
| u32 tx_wm; | |
| bool is_master_hub; | |
| const struct geni_i2c_clk_fld *clk_fld; | |
| }; | |
| /* | |
| * Hardware uses the underlying formula to calculate time periods of | |
| * SCL clock cycle. Firmware uses some additional cycles excluded from the | |
| * below formula and it is confirmed that the time periods are within | |
| * specification limits. | |
| * | |
| * time of high period of SCL: t_high = (t_high_cnt * clk_div) / source_clock | |
| * time of low period of SCL: t_low = (t_low_cnt * clk_div) / source_clock | |
| * time of full period of SCL: t_cycle = (t_cycle_cnt * clk_div) / source_clock | |
| * clk_freq_out = t / t_cycle | |
| * source_clock = 19.2 MHz | |
| */ | |
| static const struct geni_i2c_clk_fld geni_i2c_clk_map[] = { | |
| {I2C_SPEED_STANDARD_RATE, 7, 10, 11, 26}, | |
| {I2C_SPEED_FAST_RATE, 2, 5, 12, 24}, | |
| {I2C_SPEED_FAST_PLUS_RATE, 1, 3, 9, 18}, | |
| }; | |
| static int geni_i2c_clk_map_idx(struct geni_i2c_priv *geni, unsigned int clk_freq) | |
| { | |
| const struct geni_i2c_clk_fld *itr = geni_i2c_clk_map; | |
| int i; | |
| for (i = 0; i < ARRAY_SIZE(geni_i2c_clk_map); i++, itr++) { | |
| if (itr->clk_freq_out == clk_freq) { | |
| geni->clk_fld = itr; | |
| return 0; | |
| } | |
| } | |
| return -EINVAL; | |
| } | |
| static void geni_i2c_setup_m_cmd(struct geni_i2c_priv *geni, u32 cmd, u32 params) | |
| { | |
| u32 m_cmd; | |
| m_cmd = (cmd << M_OPCODE_SHFT) | (params & M_PARAMS_MSK); | |
| writel(m_cmd, geni->base + SE_GENI_M_CMD0); | |
| } | |
| static void qcom_geni_i2c_conf(struct geni_i2c_priv *geni) | |
| { | |
| const struct geni_i2c_clk_fld *itr = geni->clk_fld; | |
| u32 val; | |
| writel(0, geni->base + SE_GENI_CLK_SEL); | |
| val = (itr->clk_div << CLK_DIV_SHFT) | SER_CLK_EN; | |
| writel(val, geni->base + GENI_SER_M_CLK_CFG); | |
| val = itr->t_high_cnt << HIGH_COUNTER_SHFT; | |
| val |= itr->t_low_cnt << LOW_COUNTER_SHFT; | |
| val |= itr->t_cycle_cnt; | |
| writel(val, geni->base + SE_I2C_SCL_COUNTERS); | |
| writel(0xffffffff, geni->base + SE_GENI_M_IRQ_CLEAR); | |
| } | |
| static int geni_i2c_fifo_tx_fill(struct geni_i2c_priv *geni, struct i2c_msg *msg) | |
| { | |
| ulong start = get_timer(0); | |
| ulong cur_xfer = 0; | |
| int i; | |
| while (get_timer(start) < I2C_TIMEOUT_MS) { | |
| u32 status = readl(geni->base + SE_GENI_M_IRQ_STATUS); | |
| if (status & (M_CMD_ABORT_EN | | |
| M_CMD_OVERRUN_EN | | |
| M_ILLEGAL_CMD_EN | | |
| M_CMD_FAILURE_EN | | |
| M_GP_IRQ_1_EN | | |
| M_GP_IRQ_3_EN | | |
| M_GP_IRQ_4_EN)) { | |
| writel(status, geni->base + SE_GENI_M_IRQ_CLEAR); | |
| writel(0, geni->base + SE_GENI_TX_WATERMARK_REG); | |
| return -EREMOTEIO; | |
| } | |
| if ((status & M_TX_FIFO_WATERMARK_EN) == 0) { | |
| udelay(1); | |
| goto skip_fill; | |
| } | |
| for (i = 0; i < geni->tx_wm; i++) { | |
| u32 temp, tx = 0; | |
| unsigned int p = 0; | |
| while (cur_xfer < msg->len && p < sizeof(tx)) { | |
| temp = msg->buf[cur_xfer++]; | |
| tx |= temp << (p * 8); | |
| p++; | |
| } | |
| writel(tx, geni->base + SE_GENI_TX_FIFOn); | |
| if (cur_xfer == msg->len) { | |
| writel(0, geni->base + SE_GENI_TX_WATERMARK_REG); | |
| break; | |
| } | |
| } | |
| skip_fill: | |
| writel(status, geni->base + SE_GENI_M_IRQ_CLEAR); | |
| if (status & M_CMD_DONE_EN) | |
| return 0; | |
| } | |
| return -ETIMEDOUT; | |
| } | |
| static int geni_i2c_fifo_rx_drain(struct geni_i2c_priv *geni, struct i2c_msg *msg) | |
| { | |
| ulong start = get_timer(0); | |
| ulong cur_xfer = 0; | |
| int i; | |
| while (get_timer(start) < I2C_TIMEOUT_MS) { | |
| u32 status = readl(geni->base + SE_GENI_M_IRQ_STATUS); | |
| u32 rxstatus = readl(geni->base + SE_GENI_RX_FIFO_STATUS); | |
| u32 rxcnt = rxstatus & RX_FIFO_WC_MSK; | |
| if (status & (M_CMD_ABORT_EN | | |
| M_CMD_FAILURE_EN | | |
| M_CMD_OVERRUN_EN | | |
| M_ILLEGAL_CMD_EN | | |
| M_GP_IRQ_1_EN | | |
| M_GP_IRQ_3_EN | | |
| M_GP_IRQ_4_EN)) { | |
| writel(status, geni->base + SE_GENI_M_IRQ_CLEAR); | |
| return -EREMOTEIO; | |
| } | |
| if ((status & (M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN)) == 0) { | |
| udelay(1); | |
| goto skip_drain; | |
| } | |
| for (i = 0; cur_xfer < msg->len && i < rxcnt; i++) { | |
| u32 rx = readl(geni->base + SE_GENI_RX_FIFOn); | |
| unsigned int p = 0; | |
| while (cur_xfer < msg->len && p < sizeof(rx)) { | |
| msg->buf[cur_xfer++] = rx & 0xff; | |
| rx >>= 8; | |
| p++; | |
| } | |
| } | |
| skip_drain: | |
| writel(status, geni->base + SE_GENI_M_IRQ_CLEAR); | |
| if (status & M_CMD_DONE_EN) | |
| return 0; | |
| } | |
| return -ETIMEDOUT; | |
| } | |
| static int geni_i2c_xfer_tx(struct geni_i2c_priv *geni, struct i2c_msg *msg, u32 params) | |
| { | |
| writel(msg->len, geni->base + SE_I2C_TX_TRANS_LEN); | |
| geni_i2c_setup_m_cmd(geni, I2C_WRITE, params); | |
| writel(1, geni->base + SE_GENI_TX_WATERMARK_REG); | |
| return geni_i2c_fifo_tx_fill(geni, msg); | |
| } | |
| static int geni_i2c_xfer_rx(struct geni_i2c_priv *geni, struct i2c_msg *msg, u32 params) | |
| { | |
| writel(msg->len, geni->base + SE_I2C_RX_TRANS_LEN); | |
| geni_i2c_setup_m_cmd(geni, I2C_READ, params); | |
| return geni_i2c_fifo_rx_drain(geni, msg); | |
| } | |
| static int geni_i2c_xfer(struct udevice *bus, struct i2c_msg msgs[], int num) | |
| { | |
| struct geni_i2c_priv *geni = dev_get_priv(bus); | |
| int i, ret = 0; | |
| qcom_geni_i2c_conf(geni); | |
| for (i = 0; i < num; i++) { | |
| struct i2c_msg *msg = &msgs[i]; | |
| u32 m_param = i < (num - 1) ? STOP_STRETCH : 0; | |
| m_param |= ((msg->addr << SLV_ADDR_SHFT) & SLV_ADDR_MSK); | |
| if (msg->flags & I2C_M_RD) | |
| ret = geni_i2c_xfer_rx(geni, msg, m_param); | |
| else | |
| ret = geni_i2c_xfer_tx(geni, msg, m_param); | |
| if (ret) | |
| break; | |
| } | |
| if (ret) { | |
| if (ret == -ETIMEDOUT) { | |
| u32 status; | |
| writel(M_GENI_CMD_ABORT, geni->base + SE_GENI_M_CMD_CTRL_REG); | |
| /* Wait until Abort has finished */ | |
| do { | |
| status = readl(geni->base + SE_GENI_M_IRQ_STATUS); | |
| } while ((status & M_CMD_ABORT_EN) == 0); | |
| writel(status, geni->base + SE_GENI_M_IRQ_STATUS); | |
| } | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static int geni_i2c_enable_clocks(struct udevice *dev, struct geni_i2c_priv *geni) | |
| { | |
| int ret; | |
| if (geni->is_master_hub) { | |
| ret = clk_enable(&geni->core); | |
| if (ret) { | |
| dev_err(dev, "clk_enable core failed %d\n", ret); | |
| return ret; | |
| } | |
| } | |
| ret = clk_enable(&geni->se); | |
| if (ret) { | |
| dev_err(dev, "clk_enable se failed %d\n", ret); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static int geni_i2c_disable_clocks(struct udevice *dev, struct geni_i2c_priv *geni) | |
| { | |
| int ret; | |
| if (geni->is_master_hub) { | |
| ret = clk_disable(&geni->core); | |
| if (ret) { | |
| dev_err(dev, "clk_enable core failed %d\n", ret); | |
| return ret; | |
| } | |
| } | |
| ret = clk_disable(&geni->se); | |
| if (ret) { | |
| dev_err(dev, "clk_enable se failed %d\n", ret); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| #define NUM_PACKING_VECTORS 4 | |
| #define PACKING_START_SHIFT 5 | |
| #define PACKING_DIR_SHIFT 4 | |
| #define PACKING_LEN_SHIFT 1 | |
| #define PACKING_STOP_BIT BIT(0) | |
| #define PACKING_VECTOR_SHIFT 10 | |
| static void geni_i2c_config_packing(struct geni_i2c_priv *geni, int bpw, | |
| int pack_words, bool msb_to_lsb, | |
| bool tx_cfg, bool rx_cfg) | |
| { | |
| u32 cfg0, cfg1, cfg[NUM_PACKING_VECTORS] = {0}; | |
| int len; | |
| int temp_bpw = bpw; | |
| int idx_start = msb_to_lsb ? bpw - 1 : 0; | |
| int idx = idx_start; | |
| int idx_delta = msb_to_lsb ? -BITS_PER_BYTE : BITS_PER_BYTE; | |
| int ceil_bpw = ALIGN(bpw, BITS_PER_BYTE); | |
| int iter = (ceil_bpw * pack_words) / BITS_PER_BYTE; | |
| int i; | |
| if (iter <= 0 || iter > NUM_PACKING_VECTORS) | |
| return; | |
| for (i = 0; i < iter; i++) { | |
| len = min_t(int, temp_bpw, BITS_PER_BYTE) - 1; | |
| cfg[i] = idx << PACKING_START_SHIFT; | |
| cfg[i] |= msb_to_lsb << PACKING_DIR_SHIFT; | |
| cfg[i] |= len << PACKING_LEN_SHIFT; | |
| if (temp_bpw <= BITS_PER_BYTE) { | |
| idx = ((i + 1) * BITS_PER_BYTE) + idx_start; | |
| temp_bpw = bpw; | |
| } else { | |
| idx = idx + idx_delta; | |
| temp_bpw = temp_bpw - BITS_PER_BYTE; | |
| } | |
| } | |
| cfg[iter - 1] |= PACKING_STOP_BIT; | |
| cfg0 = cfg[0] | (cfg[1] << PACKING_VECTOR_SHIFT); | |
| cfg1 = cfg[2] | (cfg[3] << PACKING_VECTOR_SHIFT); | |
| if (tx_cfg) { | |
| writel(cfg0, geni->base + SE_GENI_TX_PACKING_CFG0); | |
| writel(cfg1, geni->base + SE_GENI_TX_PACKING_CFG1); | |
| } | |
| if (rx_cfg) { | |
| writel(cfg0, geni->base + SE_GENI_RX_PACKING_CFG0); | |
| writel(cfg1, geni->base + SE_GENI_RX_PACKING_CFG1); | |
| } | |
| /* | |
| * Number of protocol words in each FIFO entry | |
| * 0 - 4x8, four words in each entry, max word size of 8 bits | |
| * 1 - 2x16, two words in each entry, max word size of 16 bits | |
| * 2 - 1x32, one word in each entry, max word size of 32 bits | |
| * 3 - undefined | |
| */ | |
| if (pack_words || bpw == 32) | |
| writel(bpw / 16, geni->base + SE_GENI_BYTE_GRAN); | |
| } | |
| static void geni_i2c_init(struct geni_i2c_priv *geni, unsigned int tx_depth) | |
| { | |
| u32 val; | |
| writel(0, geni->base + SE_GSI_EVENT_EN); | |
| writel(0xffffffff, geni->base + SE_GENI_M_IRQ_CLEAR); | |
| writel(0xffffffff, geni->base + SE_GENI_S_IRQ_CLEAR); | |
| writel(0xffffffff, geni->base + SE_IRQ_EN); | |
| val = readl(geni->base + GENI_CGC_CTRL); | |
| val |= DEFAULT_CGC_EN; | |
| writel(val, geni->base + GENI_CGC_CTRL); | |
| writel(DEFAULT_IO_OUTPUT_CTRL_MSK, geni->base + GENI_OUTPUT_CTRL); | |
| writel(FORCE_DEFAULT, geni->base + GENI_FORCE_DEFAULT_REG); | |
| val = readl(geni->base + SE_IRQ_EN); | |
| val |= GENI_M_IRQ_EN | GENI_S_IRQ_EN; | |
| writel(val, geni->base + SE_IRQ_EN); | |
| val = readl(geni->base + SE_GENI_DMA_MODE_EN); | |
| val &= ~GENI_DMA_MODE_EN; | |
| writel(val, geni->base + SE_GENI_DMA_MODE_EN); | |
| writel(0, geni->base + SE_GSI_EVENT_EN); | |
| writel(tx_depth - 1, geni->base + SE_GENI_RX_WATERMARK_REG); | |
| writel(tx_depth, geni->base + SE_GENI_RX_RFR_WATERMARK_REG); | |
| val = readl(geni->base + SE_GENI_M_IRQ_EN); | |
| val |= M_COMMON_GENI_M_IRQ_EN; | |
| val |= M_CMD_DONE_EN | M_TX_FIFO_WATERMARK_EN; | |
| val |= M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN; | |
| writel(val, geni->base + SE_GENI_M_IRQ_EN); | |
| val = readl(geni->base + SE_GENI_S_IRQ_EN); | |
| val |= S_COMMON_GENI_S_IRQ_EN; | |
| writel(val, geni->base + SE_GENI_S_IRQ_EN); | |
| } | |
| static u32 geni_i2c_get_tx_fifo_depth(struct geni_i2c_priv *geni) | |
| { | |
| u32 val, hw_version, hw_major, hw_minor, tx_fifo_depth_mask; | |
| hw_version = readl(geni->wrapper + QUP_HW_VER_REG); | |
| hw_major = GENI_SE_VERSION_MAJOR(hw_version); | |
| hw_minor = GENI_SE_VERSION_MINOR(hw_version); | |
| if ((hw_major == 3 && hw_minor >= 10) || hw_major > 3) | |
| tx_fifo_depth_mask = TX_FIFO_DEPTH_MSK_256_BYTES; | |
| else | |
| tx_fifo_depth_mask = TX_FIFO_DEPTH_MSK; | |
| val = readl(geni->base + SE_HW_PARAM_0); | |
| return (val & tx_fifo_depth_mask) >> TX_FIFO_DEPTH_SHFT; | |
| } | |
| static int geni_i2c_probe(struct udevice *dev) | |
| { | |
| ofnode parent_node = ofnode_get_parent(dev_ofnode(dev)); | |
| struct geni_i2c_priv *geni = dev_get_priv(dev); | |
| u32 proto, tx_depth, fifo_disable; | |
| int ret; | |
| geni->is_master_hub = dev_get_driver_data(dev) & GENI_I2C_IS_MASTER_HUB; | |
| geni->wrapper = ofnode_get_addr(parent_node); | |
| if (geni->wrapper == FDT_ADDR_T_NONE) | |
| return -EINVAL; | |
| geni->base = (phys_addr_t)dev_read_addr_ptr(dev); | |
| if (!geni->base) | |
| return -EINVAL; | |
| if (geni->is_master_hub) { | |
| ret = clk_get_by_name(dev, "core", &geni->core); | |
| if (ret) { | |
| dev_err(dev, "clk_get_by_name(core) failed: %d\n", ret); | |
| return ret; | |
| } | |
| } | |
| ret = clk_get_by_name(dev, "se", &geni->se); | |
| if (ret) { | |
| dev_err(dev, "clk_get_by_name(se) failed: %d\n", ret); | |
| return ret; | |
| } | |
| geni_i2c_enable_clocks(dev, geni); | |
| proto = readl(geni->base + GENI_FW_REVISION_RO); | |
| proto &= FW_REV_PROTOCOL_MSK; | |
| proto >>= FW_REV_PROTOCOL_SHFT; | |
| if (proto != GENI_SE_I2C) { | |
| dev_err(dev, "Invalid proto %d\n", proto); | |
| geni_i2c_disable_clocks(dev, geni); | |
| return -ENXIO; | |
| } | |
| fifo_disable = readl(geni->base + GENI_IF_DISABLE_RO) & FIFO_IF_DISABLE; | |
| if (fifo_disable) { | |
| geni_i2c_disable_clocks(dev, geni); | |
| dev_err(dev, "FIFO mode disabled, DMA mode unsupported\n"); | |
| return -ENXIO; | |
| } | |
| if (!geni->is_master_hub) { | |
| tx_depth = geni_i2c_get_tx_fifo_depth(geni); | |
| if (!tx_depth) { | |
| geni_i2c_disable_clocks(dev, geni); | |
| dev_err(dev, "Invalid TX FIFO depth\n"); | |
| return -ENXIO; | |
| } | |
| } else { | |
| tx_depth = 16; | |
| } | |
| geni->tx_wm = tx_depth - 1; | |
| geni_i2c_init(geni, tx_depth); | |
| geni_i2c_config_packing(geni, BITS_PER_BYTE, | |
| PACKING_BYTES_PW, true, true, true); | |
| /* Setup for standard rate */ | |
| return geni_i2c_clk_map_idx(geni, I2C_SPEED_STANDARD_RATE); | |
| } | |
| static int geni_i2c_set_bus_speed(struct udevice *dev, unsigned int clk_freq) | |
| { | |
| struct geni_i2c_priv *geni = dev_get_priv(dev); | |
| return geni_i2c_clk_map_idx(geni, clk_freq); | |
| } | |
| static const struct dm_i2c_ops geni_i2c_ops = { | |
| .xfer = geni_i2c_xfer, | |
| .set_bus_speed = geni_i2c_set_bus_speed, | |
| }; | |
| static const struct udevice_id geni_i2c_ids[] = { | |
| { .compatible = "qcom,geni-i2c" }, | |
| { .compatible = "qcom,geni-i2c-master-hub", .data = GENI_I2C_IS_MASTER_HUB}, | |
| {} | |
| }; | |
| U_BOOT_DRIVER(i2c_geni) = { | |
| .name = "i2c_geni", | |
| .id = UCLASS_I2C, | |
| .of_match = geni_i2c_ids, | |
| .probe = geni_i2c_probe, | |
| .priv_auto = sizeof(struct geni_i2c_priv), | |
| .ops = &geni_i2c_ops, | |
| }; | |
| static const struct udevice_id geni_i2c_master_hub_ids[] = { | |
| { .compatible = "qcom,geni-se-i2c-master-hub" }, | |
| { } | |
| }; | |
| U_BOOT_DRIVER(geni_i2c_master_hub) = { | |
| .name = "geni-se-master-hub", | |
| .id = UCLASS_NOP, | |
| .of_match = geni_i2c_master_hub_ids, | |
| .bind = dm_scan_fdt_dev, | |
| .flags = DM_FLAG_PRE_RELOC | DM_FLAG_DEFAULT_PD_CTRL_OFF, | |
| }; |