[PATCH v4 3/3] i2c: aspeed: support ast2600 i2c new register mode driver

Jeremy Kerr jk at ozlabs.org
Thu Feb 2 17:04:37 AEDT 2023


Hi Ryan,

> Add i2c new register mode driver to support AST2600 i2c
> new register mode.

Nice!

A bit of review below - I assume your state machine & event handling is
correct, but this is more on the driver API side.

> +static int ast2600_i2c_global_probe(struct platform_device *pdev)
> +{
> +       struct ast2600_i2c_global *i2c_global;
> +       struct resource *res;
> +
> +       i2c_global = devm_kzalloc(&pdev->dev, sizeof(*i2c_global), GFP_KERNEL);
> +       if (IS_ERR(i2c_global))
> +               return PTR_ERR(i2c_global);
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       i2c_global->base = devm_ioremap_resource(&pdev->dev, res);
> +       if (IS_ERR(i2c_global->base))
> +               return PTR_ERR(i2c_global->base);
> +
> +       i2c_global->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
> +       if (IS_ERR(i2c_global->rst))
> +               return IS_ERR(i2c_global->rst);
> +
> +       reset_control_assert(i2c_global->rst);
> +       udelay(3);
> +       reset_control_deassert(i2c_global->rst);
> +
> +       writel(AST2600_GLOBAL_INIT, i2c_global->base + AST2600_I2CG_CTRL);
> +       writel(I2CCG_DIV_CTRL, i2c_global->base + AST2600_I2CG_CLK_DIV_CTRL);
> +
> +       pr_info("i2c global registered\n");

I'm not the printk police, but I don't think this message is very
useful.

> +
> +       return 0;
> +}

... so this driver is doing some common reset bits then just the two
register writes - is it possible to just use a common syscon/mfd device
instead? The core i2c driver could handle the reg writes through the
syscon regmap instead.

> +
> +static struct platform_driver ast2600_i2c_global_driver = {
> +       .probe  = ast2600_i2c_global_probe,
> +       .driver = {
> +               .name = KBUILD_MODNAME,
> +               .of_match_table = ast2600_i2c_global_of_match,
> +       },
> +};
> +
> +static int __init ast2600_i2c_global_init(void)
> +{
> +       return platform_driver_register(&ast2600_i2c_global_driver);
> +}
> +device_initcall(ast2600_i2c_global_init);

Maybe module_platform_driver() instead?

> +enum xfer_mode {
> +       BYTE_MODE = 0,
> +       BUFF_MODE,
> +       DMA_MODE,
> +};
> +
> +struct ast2600_i2c_bus {
> +       struct i2c_adapter      adap;
> +       struct device           *dev;
> +       void __iomem            *reg_base;
> +       struct regmap           *global_reg;
> +       int                                     irq;
> +       /* 0: dma, 1: pool, 2:byte */
> +       enum xfer_mode          mode;

The comment doesn't match the enum.

> +       /* 0: old mode, 1: new mode */
> +       int                                     clk_div_mode;

... and if you're using an enum for xfer_mode, may as well do the same
for clk_div_mode, which will make some of the conditionals below more
obvious.

> +static u32 ast2600_select_i2c_clock(struct ast2600_i2c_bus *i2c_bus)
> +{
> +       unsigned long base_clk1;
> +       unsigned long base_clk2;
> +       unsigned long base_clk3;
> +       unsigned long base_clk4;
> +       int baseclk_idx;
> +       u32 clk_div_reg;
> +       u32 scl_low;
> +       u32 scl_high;
> +       int divisor;
> +       int inc = 0;
> +       u32 data;
> +       int i;
> +
> +       if (i2c_bus->clk_div_mode) {
> +               regmap_read(i2c_bus->global_reg, AST2600_I2CG_CLK_DIV_CTRL, &clk_div_reg);
> +               base_clk1 = (i2c_bus->apb_clk * 10) / ((((clk_div_reg & 0xff) + 2) * 10) / 2);
> +               base_clk2 = (i2c_bus->apb_clk * 10) /
> +                               (((((clk_div_reg >> 8) & 0xff) + 2) * 10) / 2);
> +               base_clk3 = (i2c_bus->apb_clk * 10) /
> +                               (((((clk_div_reg >> 16) & 0xff) + 2) * 10) / 2);
> +               base_clk4 = (i2c_bus->apb_clk * 10) /
> +                               (((((clk_div_reg >> 24) & 0xff) + 2) * 10) / 2);
> +
> +               if ((i2c_bus->apb_clk / i2c_bus->bus_frequency) <= 32) {
> +                       baseclk_idx = 0;
> +                       divisor = DIV_ROUND_UP(i2c_bus->apb_clk, i2c_bus->bus_frequency);
> +               } else if ((base_clk1 / i2c_bus->bus_frequency) <= 32) {
> +                       baseclk_idx = 1;
> +                       divisor = DIV_ROUND_UP(base_clk1, i2c_bus->bus_frequency);
> +               } else if ((base_clk2 / i2c_bus->bus_frequency) <= 32) {
> +                       baseclk_idx = 2;
> +                       divisor = DIV_ROUND_UP(base_clk2, i2c_bus->bus_frequency);
> +               } else if ((base_clk3 / i2c_bus->bus_frequency) <= 32) {
> +                       baseclk_idx = 3;
> +                       divisor = DIV_ROUND_UP(base_clk3, i2c_bus->bus_frequency);
> +               } else {
> +                       baseclk_idx = 4;
> +                       divisor = DIV_ROUND_UP(base_clk4, i2c_bus->bus_frequency);
> +                       inc = 0;
> +                       while ((divisor + inc) > 32) {
> +                               inc |= divisor & 0x1;
> +                               divisor >>= 1;
> +                               baseclk_idx++;
> +                       }
> +                       divisor += inc;
> +               }
> +               divisor = min_t(int, divisor, 32);
> +               baseclk_idx &= 0xf;
> +               scl_low = ((divisor * 9) / 16) - 1;
> +               scl_low = min_t(u32, scl_low, 0xf);
> +               scl_high = (divisor - scl_low - 2) & 0xf;
> +               /* Divisor : Base Clock : tCKHighMin : tCK High : tCK Low  */
> +               data = ((scl_high - 1) << 20) | (scl_high << 16) | (scl_low << 12) | (baseclk_idx);
> +               if (i2c_bus->timeout_enable) {
> +                       data |= AST2600_I2CC_TOUTBASECLK(AST_I2C_TIMEOUT_CLK);
> +                       data |= AST2600_I2CC_TTIMEOUT(AST_I2C_TIMEOUT_COUNT);
> +               }
> +       } else {
> +               for (i = 0; i < ARRAY_SIZE(i2c_legacy_timing_table); i++) {
> +                       if ((i2c_bus->apb_clk / i2c_legacy_timing_table[i].divisor) <
> +                           i2c_bus->bus_frequency) {
> +                               break;
> +                       }
> +               }
> +               data = i2c_legacy_timing_table[i].timing;

If you don't match a timing, this will overflow i2c_legacy_timing_table.

> +static int ast2600_i2c_do_start(struct ast2600_i2c_bus *i2c_bus)
> +{
> +       struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index];
> +       int xfer_len = 0;
> +       int i = 0;
> +       u32 cmd;
> +
> +       cmd = AST2600_I2CM_PKT_EN | AST2600_I2CM_PKT_ADDR(msg->addr) | AST2600_I2CM_START_CMD;
> +
> +       dev_dbg(i2c_bus->dev, "[%d] %sing %d byte%s %s 0x%02x\n",
> +               i2c_bus->msgs_index, msg->flags & I2C_M_RD ? "read" : "write",
> +               msg->len, msg->len > 1 ? "s" : "",
> +               msg->flags & I2C_M_RD ? "from" : "to", msg->addr);
> +
> +       i2c_bus->master_xfer_cnt = 0;
> +       i2c_bus->buf_index = 0;
> +
> +       if (msg->flags & I2C_M_RD) {
> +               cmd |= AST2600_I2CM_RX_CMD;
> +               if (i2c_bus->mode == DMA_MODE) {
                        [...]
> +               } else if (i2c_bus->mode == BUFF_MODE) {
                        [...]
> +               } else {
                        [...]
> +               }
> +       } else {
> +               if (i2c_bus->mode == DMA_MODE) {
                        [...]
> +               } else if (i2c_bus->mode == BUFF_MODE) {
                        [...]
> +               } else {
                        [...]
> +               }
> +       }
> +       dev_dbg(i2c_bus->dev, "len %d , cmd %x\n", xfer_len, cmd);
> +       writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS);
> +       return 0;
> +}

The core parts of this function are essentially split into three,
depending on the bus mode. Maybe it would be better to refactor those
into separate functions for buffer handling?

> +
> +static int ast2600_i2c_is_irq_error(u32 irq_status)
> +{
> +       if (irq_status & AST2600_I2CM_ARBIT_LOSS)
> +               return -EAGAIN;
> +       if (irq_status & (AST2600_I2CM_SDA_DL_TO | AST2600_I2CM_SCL_LOW_TO))
> +               return -EBUSY;
> +       if (irq_status & (AST2600_I2CM_ABNORMAL))
> +               return -EPROTO;
> +
> +       return 0;
> +}
> +
> +static void ast2600_i2c_master_package_irq(struct ast2600_i2c_bus *i2c_bus, u32 sts)

What does 'package' refer to here?

Same as above with this one - there's a lot of inner conditionals on the
bus modes.

> +static int ast2600_i2c_master_irq(struct ast2600_i2c_bus *i2c_bus)
> +{
> +       u32 sts = readl(i2c_bus->reg_base + AST2600_I2CM_ISR);
> +       u32 ier = readl(i2c_bus->reg_base + AST2600_I2CM_IER);
> +       u32 ctrl = 0;
> +
> +       dev_dbg(i2c_bus->dev, "M sts %x\n", sts);
> +       if (!i2c_bus->alert_enable)
> +               sts &= ~AST2600_I2CM_SMBUS_ALT;
> +
> +       if (AST2600_I2CM_BUS_RECOVER_FAIL & sts) {
> +               dev_dbg(i2c_bus->dev, "M clear isr: AST2600_I2CM_BUS_RECOVER_FAIL= %x\n", sts);
> +               writel(AST2600_I2CM_BUS_RECOVER_FAIL, i2c_bus->reg_base + AST2600_I2CM_ISR);
> +               ctrl = readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL);
> +               writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL);
> +               writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL);
> +               i2c_bus->cmd_err = -EPROTO;
> +               complete(&i2c_bus->cmd_complete);
> +               return 1;
> +       }
> +
> +       if (AST2600_I2CM_BUS_RECOVER & sts) {
> +               dev_dbg(i2c_bus->dev, "M clear isr: AST2600_I2CM_BUS_RECOVER= %x\n", sts);
> +               writel(AST2600_I2CM_BUS_RECOVER, i2c_bus->reg_base + AST2600_I2CM_ISR);
> +               i2c_bus->cmd_err = 0;
> +               complete(&i2c_bus->cmd_complete);
> +               return 1;
> +       }
> +
> +       if (AST2600_I2CM_SMBUS_ALT & sts) {
> +               if (ier & AST2600_I2CM_SMBUS_ALT) {
> +                       dev_dbg(i2c_bus->dev, "M clear isr: AST2600_I2CM_SMBUS_ALT= %x\n", sts);
> +                       /* Disable ALT INT */
> +                       writel(ier & ~AST2600_I2CM_SMBUS_ALT, i2c_bus->reg_base + AST2600_I2CM_IER);
> +                       i2c_handle_smbus_alert(i2c_bus->ara);
> +                       writel(AST2600_I2CM_SMBUS_ALT, i2c_bus->reg_base + AST2600_I2CM_ISR);
> +                       dev_err(i2c_bus->dev,
> +                               "ast2600_master_alert_recv bus id %d, Disable Alt, Please Imple\n",
> +                               i2c_bus->adap.nr);
> +                       return 1;
> +               }
> +       }
> +
> +       i2c_bus->cmd_err = ast2600_i2c_is_irq_error(sts);
> +       if (i2c_bus->cmd_err) {
> +               dev_dbg(i2c_bus->dev, "received error interrupt: 0x%02x\n", sts);
> +               writel(AST2600_I2CM_PKT_DONE, i2c_bus->reg_base + AST2600_I2CM_ISR);
> +               complete(&i2c_bus->cmd_complete);
> +               return 1;
> +       }
> +
> +       if (AST2600_I2CM_PKT_DONE & sts) {
> +               ast2600_i2c_master_package_irq(i2c_bus, sts);
> +               return 1;
> +       }
> +
> +       if (readl(i2c_bus->reg_base + AST2600_I2CM_ISR)) {
> +               dev_dbg(i2c_bus->dev, "master TODO care sts %x\n",
> +                       readl(i2c_bus->reg_base + AST2600_I2CM_ISR));

You're reading the same register twice here, is that intentional?

> +               writel(readl(i2c_bus->reg_base + AST2600_I2CM_ISR),
> +                               i2c_bus->reg_base + AST2600_I2CM_ISR);
>

make that three times :)

> +static int ast2600_i2c_probe(struct platform_device *pdev)
> +{
> +       struct ast2600_i2c_bus *i2c_bus;
> +       const struct of_device_id *match;
> +       struct resource *res;
> +       u32 global_ctrl;
> +       int ret = 0;
> +
> +       i2c_bus = devm_kzalloc(&pdev->dev, sizeof(*i2c_bus), GFP_KERNEL);
> +       if (!i2c_bus)
> +               return -ENOMEM;
> +
> +       i2c_bus->global_reg = syscon_regmap_lookup_by_compatible("aspeed,ast2600-i2c-global");
> +       if (IS_ERR(i2c_bus->global_reg)) {
> +               dev_err(&pdev->dev, "failed to find ast2600 i2c global regmap\n");
> +               ret = -ENOMEM;
> +               goto free_mem;
> +       }
> +
> +       regmap_read(i2c_bus->global_reg, AST2600_I2CG_CTRL, &global_ctrl);
> +
> +       if (global_ctrl & AST2600_I2CG_CTRL_NEW_CLK_DIV)
> +               i2c_bus->clk_div_mode = 1;
> +
> +       if (!(global_ctrl & AST2600_I2CG_CTRL_NEW_REG)) {
> +               ret = -ENOENT;
> +               dev_err(&pdev->dev, "Expect I2CG0C[2] = 1 (new reg mode)\n");
> +               goto free_mem;
> +       }
> +
> +       i2c_bus->mode = DMA_MODE;
> +       i2c_bus->slave_operate = 0;
> +
> +       if (of_property_read_bool(pdev->dev.of_node, "byte-mode"))
> +               i2c_bus->mode = BYTE_MODE;
> +
> +       if (of_property_read_bool(pdev->dev.of_node, "buff-mode")) {
> +               res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +               if (res && resource_size(res) >= 2)
> +                       i2c_bus->buf_base = devm_ioremap_resource(&pdev->dev, res);
> +
> +               if (!IS_ERR_OR_NULL(i2c_bus->buf_base))
> +                       i2c_bus->buf_size = resource_size(res) / 2;
> +
> +               i2c_bus->mode = BUFF_MODE;
> +       }

The buff-mode and byte-mode properties do not appear in your OF binding
document. Using booleans to indicate mutually-exclusive modes seems a
bit odd.

> +
> +       if (of_property_read_bool(pdev->dev.of_node, "timeout"))
> +               i2c_bus->timeout_enable = 1;

'timeout' is also not described in the binding.

> +
> +       i2c_bus->dev = &pdev->dev;
> +       init_completion(&i2c_bus->cmd_complete);
> +
> +       i2c_bus->reg_base = devm_platform_ioremap_resource(pdev, 0);
> +       if (IS_ERR(i2c_bus->reg_base)) {
> +               ret = PTR_ERR(i2c_bus->reg_base);
> +               goto free_mem;
> +       }
> +
> +       i2c_bus->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
> +       if (i2c_bus->irq < 0) {
> +               dev_err(&pdev->dev, "no irq specified\n");
> +               ret = -i2c_bus->irq;
> +               goto free_irq;
> +       }
> +
> +       match = of_match_node(ast2600_i2c_bus_of_table, pdev->dev.of_node);
> +       if (!match) {
> +               ret = -ENOENT;
> +               goto free_irq;
> +       }

You don't use 'match' after this.

> +
> +       platform_set_drvdata(pdev, i2c_bus);
> +
> +       i2c_bus->clk = devm_clk_get(i2c_bus->dev, NULL);
> +       if (IS_ERR(i2c_bus->clk)) {
> +               dev_err(i2c_bus->dev, "no clock defined\n");
> +               ret = -ENODEV;
> +               goto free_irq;
> +       }
> +       i2c_bus->apb_clk = clk_get_rate(i2c_bus->clk);
> +       dev_dbg(i2c_bus->dev, "i2c_bus->apb_clk %d\n", i2c_bus->apb_clk);
> +
> +       ret = of_property_read_u32(pdev->dev.of_node, "bus-frequency", &i2c_bus->bus_frequency);
> +       if (ret < 0) {
> +               dev_err(&pdev->dev, "Could not read bus-frequency property\n");
> +               i2c_bus->bus_frequency = 100000;
> +       }
> +
> +       /* Initialize the I2C adapter */
> +       i2c_bus->adap.owner = THIS_MODULE;
> +       i2c_bus->adap.algo = &i2c_ast2600_algorithm;
> +       i2c_bus->adap.retries = 0;
> +       i2c_bus->adap.dev.parent = i2c_bus->dev;
> +       i2c_bus->adap.dev.of_node = pdev->dev.of_node;
> +       i2c_bus->adap.algo_data = i2c_bus;
> +       strscpy(i2c_bus->adap.name, pdev->name, sizeof(i2c_bus->adap.name));
> +       i2c_set_adapdata(&i2c_bus->adap, i2c_bus);
> +
> +       ast2600_i2c_init(i2c_bus);
> +
> +       ret = devm_request_irq(&pdev->dev, i2c_bus->irq, ast2600_i2c_bus_irq, 0,
> +                              dev_name(&pdev->dev), i2c_bus);
> +       if (ret < 0)
> +               goto unmap;
> +
> +       if (of_property_read_bool(pdev->dev.of_node, "smbus-alert")) {
> +               i2c_bus->alert_enable = 1;
> +               i2c_bus->ara = i2c_new_smbus_alert_device(&i2c_bus->adap, &i2c_bus->alert_data);
> +               if (!i2c_bus->ara)
> +                       dev_warn(i2c_bus->dev, "Failed to register ARA client\n");
> +
> +               writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER | AST2600_I2CM_SMBUS_ALT,
> +                      i2c_bus->reg_base + AST2600_I2CM_IER);
> +       } else {
> +               i2c_bus->alert_enable = 0;
> +               /* Set interrupt generation of I2C master controller */
> +               writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER,
> +                               i2c_bus->reg_base + AST2600_I2CM_IER);
> +       }
> +
> +       ret = i2c_add_adapter(&i2c_bus->adap);
> +       if (ret < 0)
> +               goto unmap;
> +
> +       dev_info(i2c_bus->dev, "%s [%d]: adapter [%d khz] mode [%d]\n",
> +                pdev->dev.of_node->name, i2c_bus->adap.nr, i2c_bus->bus_frequency / 1000,
> +                i2c_bus->mode);
> +
> +       return 0;
> +
> +unmap:
> +       free_irq(i2c_bus->irq, i2c_bus);
> +free_irq:
> +       devm_iounmap(&pdev->dev, i2c_bus->reg_base);

Something looks off in these goto labels.

> +free_mem:
> +       kfree(i2c_bus);

you devm_kzalloc()ed above, but are kfree()ing here (as well as in the
_remove function). Same with devm_request_irq(). You could just drop
these?

Cheers,


Jeremy


More information about the Linux-aspeed mailing list