On 2025/3/4 08:01, Alex Elder wrote:
On 3/2/25 11:30 PM, Troy Mitchell wrote:Hi, Alex. Thanks for your review.
This patch introduces basic I2C support for the SpacemiT K1 SoC,
utilizing interrupts for transfers.
The driver has been tested using i2c-tools on a Bananapi-F3 board,
and basic I2C read/write operations have been confirmed to work.
Signed-off-by: Troy Mitchell <troymitchell988@xxxxxxxxx>
I have some more comments, and some questions. I appreciate
seeing some of the changes you've made based on my feedback.
yes. I will talk it below.+static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c)
+{
+ u32 val;
+
+ /*
+ * Unmask interrupt bits for all xfer mode:
+ * bus error, arbitration loss detected.
+ * For transaction complete signal, we use master stop
+ * interrupt, so we don't need to unmask SPACEMIT_CR_TXDONEIE.
+ */
+ val = SPACEMIT_CR_BEIE | SPACEMIT_CR_ALDIE;
+
+ /*
+ * Unmask interrupt bits for interrupt xfer mode:
+ * DBR rx full.
+ * For tx empty interrupt SPACEMIT_CR_DTEIE, we only
+ * need to enable when trigger byte transfer to start
+ * data sending.
+ */
+ val |= SPACEMIT_CR_DRFIE;
+
+ /* set speed bits: default fast mode */
It is not *default* fast mode, it *is* fast mode. (There
is no other mode used in this driver, right?)
+ val |= SPACEMIT_CR_MODE_FAST;
+
+ /* disable response to general call */
+ val |= SPACEMIT_CR_GCD;
+
+ /* enable SCL clock output */
+ val |= SPACEMIT_CR_SCLE;
+
+ /* enable master stop detected */
+ val |= SPACEMIT_CR_MSDE | SPACEMIT_CR_MSDIE;
+
+ writel(val, i2c->base + SPACEMIT_ICR);
+}
+
+
+static int spacemit_i2c_xfer_core(struct spacemit_i2c_dev *i2c)
+{
+ int ret;
+
+ spacemit_i2c_reset(i2c);
I don't have a lot of experience with I2C drivers, but is it normal
to reset before every transfer?
If it is, just tell me that. But if it's not, can you explain why
it's necessary here?
My initial idea was to keep the I2C state in its initial state before each
transmission.
But after testing, this is not necessary. I will move it to `probe` function.
+
+ spacemit_i2c_calc_timeout(i2c);
+
+ spacemit_i2c_init(i2c);
+
Here too, maybe I just don't know what most I2C drivers do, but
is it necessary to only enable the I2C adapter and its interrupt
handler when performing a transfer?
It is necessary to enable before each transmission.
I have tested moving the `spacemit_i2c_enable` to the probe function.
It will cause transmission errors.
As for the `enable_irq`, I think it can be moved to the `probe` function.
+ spacemit_i2c_enable(i2c);
+ enable_irq(i2c->irq);
+
+ /* i2c wait for bus busy */
+ ret = spacemit_i2c_recover_bus_busy(i2c);
+ if (ret)
+ return ret;
+
+ ret = spacemit_i2c_xfer_msg(i2c);
+ if (ret < 0)
+ dev_dbg(i2c->dev, "i2c transfer error\n");
If you're reporting the error you might as well say what
it is.
dev_dbg(i2c->dev, "i2c transfer error: %d\n", ret);
+
+ return ret;
+}
+
+static int spacemit_i2c_xfer(struct i2c_adapter *adapt, struct i2c_msg
*msgs, int num)
+{
+ struct spacemit_i2c_dev *i2c = i2c_get_adapdata(adapt);
+ int ret;
+ u32 err = SPACEMIT_I2C_GET_ERR(i2c->status);
+
+ i2c->msgs = msgs;
+ i2c->msg_num = num;
+
+ ret = spacemit_i2c_xfer_core(i2c);
+ if (!ret)
+ spacemit_i2c_check_bus_release(i2c);
+
The enable_irq() call that matches the disable call below is
found in spacemit_i2c_xfer_core(). That's where this call
belongs.
+ disable_irq(i2c->irq);
+
Same with the next call--it should be in the same function
that its corresponding spacemit_i2c_enable() is called.
With these suggestions in mind, I think you can safely
just get rid of spacemit_i2c_xfer_core(). It is only
called in this one spot (above), and you can just do
everything within spacemit_i2c_xfer() instead.
+ spacemit_i2c_disable(i2c);
+
+ if (ret == -ETIMEDOUT || ret == -EAGAIN)
+ dev_alert(i2c->dev, "i2c transfer failed, ret %d err 0x%x\n", ret,
err);
+
+ return ret < 0 ? ret : num;
+}
+
+static u32 spacemit_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm spacemit_i2c_algo = {
+ .xfer = spacemit_i2c_xfer,
+ .functionality = spacemit_i2c_func,
+};
+
+static int spacemit_i2c_probe(struct platform_device *pdev)
+{
+ struct clk *clk;
+ struct device *dev = &pdev->dev;
+ struct device_node *of_node = pdev->dev.of_node;
+ struct spacemit_i2c_dev *i2c;
+ int ret = 0;
There is no need to initialize ret.
+
+ i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return -ENOMEM;
+
+ ret = of_property_read_u32(of_node, "clock-frequency", &i2c->clock_freq);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to read clock-frequency
property");
+
+ /* For now, this driver doesn't support high-speed. */
+ if (i2c->clock_freq < 1 || i2c->clock_freq > 400000) {
In your device tree binding, you indicate that three different
modes are supported, and that the maximum frequency is 3300000 Hz.
This says that only ranges from 1-400000 Hz are allowed.
In fact, although you look up this clock frequency in DT, I see
nothing that actually is affected by this value. I.e., no I2C
bus frequency changes, regardless of what frequency you specify.
The only place the clock_freq field is used is in calculating
the timeout for a transfer.
So two things:
- My guess is that you are relying on whatever frequency the
hardware already is using, and maybe that's 400000 Hz.
That's fine, though at some point it should be more
directly controlled (set somehow).
- Since you don't actually support any other frequency,
drop this "clock-frequency" feature for now, and add it
when you're ready to actually support it.
And I might be wrong about this, but I don't think your
(new) DTS binding should specify behavior that is not
supported by the driver.
-Alex
I will support standard mode in next version.
We just need to modify the function `spacemit_i2c_init`.
+ dev_warn(dev, "unsupport clock frequency: %d, default: 400000",
i2c->clock_freq);
+ i2c->clock_freq = 400000;
+ }
+
+ i2c->dev = &pdev->dev;
+
+ i2c->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(i2c->base))
+ return dev_err_probe(dev, PTR_ERR(i2c->base), "failed to do ioremap");
+
+ i2c->irq = platform_get_irq(pdev, 0);
+ if (i2c->irq < 0)
+ return dev_err_probe(dev, i2c->irq, "failed to get irq resource");
+
+ ret = devm_request_irq(i2c->dev, i2c->irq, spacemit_i2c_irq_handler,
+ IRQF_NO_SUSPEND | IRQF_ONESHOT, dev_name(i2c->dev), i2c);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to request irq");
+
+ disable_irq(i2c->irq);
+
+ clk = devm_clk_get_enabled(dev, "apb");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "failed to enable apb clock");
+
+ clk = devm_clk_get_enabled(dev, "twsi");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "failed to enable twsi clock");
+
+ i2c_set_adapdata(&i2c->adapt, i2c);
+ i2c->adapt.owner = THIS_MODULE;
+ i2c->adapt.algo = &spacemit_i2c_algo;
+ i2c->adapt.dev.parent = i2c->dev;
+ i2c->adapt.nr = pdev->id;
+
+ i2c->adapt.dev.of_node = of_node;
+ i2c->adapt.algo_data = i2c;
+
+ strscpy(i2c->adapt.name, "spacemit-i2c-adapter", sizeof(i2c->adapt.name));
+
+ init_completion(&i2c->complete);
+
+ platform_set_drvdata(pdev, i2c);
+
+ ret = i2c_add_numbered_adapter(&i2c->adapt);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to add i2c adapter");
+
+ return 0;
+}
+
+static void spacemit_i2c_remove(struct platform_device *pdev)
+{
+ struct spacemit_i2c_dev *i2c = platform_get_drvdata(pdev);
+
+ i2c_del_adapter(&i2c->adapt);
+}
+
+static const struct of_device_id spacemit_i2c_of_match[] = {
+ { .compatible = "spacemit,k1-i2c", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, spacemit_i2c_of_match);
+
+static struct platform_driver spacemit_i2c_driver = {
+ .probe = spacemit_i2c_probe,
+ .remove = spacemit_i2c_remove,
+ .driver = {
+ .name = "i2c-k1",
+ .of_match_table = spacemit_i2c_of_match,
+ },
+};
+module_platform_driver(spacemit_i2c_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("I2C bus driver for SpacemiT K1 SoC");