Re: [PATCH v2 2/2] i2c: Add Spreadtrum I2C controller driver
From: Andy Shevchenko
Date: Sun Jun 25 2017 - 11:06:56 EST
On Wed, Jun 21, 2017 at 10:23 AM, Baolin Wang
<baolin.wang@xxxxxxxxxxxxxx> wrote:
> This patch adds the I2C controller driver for Spreadtrum platform.
Needs more work.
See my comments below.
> +#include <linux/init.h>
> +#include <linux/module.h>
Since your answer to the comment about arch_initcall you perhaps need
to get rid of tristate (use bool) and drop module.h here and all
leftovers like MODULE_*() calls.
> +#include <linux/slab.h>
Should we include this still explicitly (after kernel.h)?
> +
> +#define I2C_CTL 0x0
> +#define I2C_ADDR_CFG 0x4
> +#define I2C_COUNT 0x8
> +#define I2C_RX 0xC
> +#define I2C_TX 0x10
> +#define I2C_STATUS 0x14
> +#define I2C_HSMODE_CFG 0x18
> +#define I2C_VERSION 0x1C
> +#define ADDR_DVD0 0x20
> +#define ADDR_DVD1 0x24
> +#define ADDR_STA0_DVD 0x28
> +#define ADDR_RST 0x2C
1. Please, use fixed sized values
0x00 and so on.
2. Please, low case.
> +#define SPRD_I2C_TIMEOUT (msecs_to_jiffies(1000))
Redundant parens.
> +static void sprd_i2c_dump_reg(struct sprd_i2c *i2c_dev)
If you switch your driver to regmap API, you may drop this function completely.
> + writel(tmp & (~STP_EN), i2c_dev->base + I2C_CTL);
Redundant parens.
Also pay attention to other similar places.
> +static void sprd_i2c_write_bytes(struct sprd_i2c *i2c_dev, u8 *buf, u32 len)
So, isn't better to provide a writesb(), readsb() to ia64 instead of
doing below?
> +{
> + u32 i = 0;
> +
> + for (i = 0; i < len; i++) {
> + writel(buf[i], i2c_dev->base + I2C_TX);
> + dev_dbg(&i2c_dev->adap.dev, "write bytes[%d] = %x\n", i, buf[i]);
Moreover, don't waste lines in the kernel log buffer by doing this.
Choose either
%*ph (up to 64 bytes)
or
print_hex_dump()
(Same for _read_bytes() below)
> + }
> +}
> +static void sprd_i2c_set_full_thld(struct sprd_i2c *i2c_dev, u32 full_thld)
> +{
> + unsigned int tmp = readl(i2c_dev->base + I2C_CTL);
> +
> + tmp &= ~(0xf << FIFO_AF_LVL);
Magic.
Define it using GENMASK()
> + tmp |= (full_thld << FIFO_AF_LVL);
Redundant parens.
> + tmp &= ~(0xf << FIFO_AE_LVL);
> + tmp |= (empty_thld << FIFO_AE_LVL);
Same.
> +static void sprd_i2c_opt_mode(struct sprd_i2c *i2c_dev, int rw)
> +{
> + unsigned int cmd = readl(i2c_dev->base + I2C_CTL) & (~I2C_MODE);
Redundant parens.
> + writel((cmd | rw << 3), i2c_dev->base + I2C_CTL);
Ditto.
It's a C, and not a LISP :-)
> +}
> +static void sprd_i2c_data_transfer(struct sprd_i2c *i2c_dev)
> +{
> + if ((msg->flags & I2C_M_RD)) {
Redundant parens.
> + /* Reset rx/tx fifos */
Noise.
> + /* Set device address */
Ditto.
> + /* Set I2C read or write bytes count */
Ditto.
> + if ((msg->flags & I2C_M_RD)) {
> + /* Set read operation */
> + sprd_i2c_opt_mode(i2c_dev, 1);
> + /* Last byte transmission should excute stop operation */
> + sprd_i2c_send_stop(i2c_dev, 1);
Same comments as above.
> + } else {
> + /* Set write operation */
Noise.
> + /* Last byte transmission should excute stop operation */
> + if (is_last_msg)
> + sprd_i2c_send_stop(i2c_dev, 1);
> + else
> + sprd_i2c_send_stop(i2c_dev, 0);
sprd_i2c_send_stop(i2c_dev, !!is_last_msg);
Though, consider to make is_last_msg boolean.
> +static int sprd_i2c_master_xfer(struct i2c_adapter *i2c_adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct sprd_i2c *i2c_dev = i2c_adap->algo_data;
> + int im, ret;
> +
> + ret = pm_runtime_get_sync(i2c_dev->dev);
> + if (ret < 0)
> + return ret;
> +
> + for (im = 0; im != num; im++)
im < num - 1, and...
ret = sprd_i2c_handle_msg(i2c_adap, &msgs[im], 0);
... ret = sprd_i2c_handle_msg(i2c_adap, &msgs[im++], 1);
...and we clearly see that you have messed up with returned code on
each itteration here
> + pm_runtime_mark_last_busy(i2c_dev->dev);
> + pm_runtime_put_autosuspend(i2c_dev->dev);
> + return (ret >= 0) ? im : ret;
Usual pattern is
ret < 0 ? ret : im;
> +static void sprd_i2c_set_clk(struct sprd_i2c *i2c_dev, unsigned int freq)
> +{
> + u32 apb_clk = i2c_dev->src_clk;
> + u32 i2c_dvd = apb_clk / (4 * freq) - 1;
> + u32 high = ((i2c_dvd << 1) * 2) / 5;
> + u32 low = ((i2c_dvd << 1) * 3) / 5;
> + u32 div0 = (high & 0xffff) << 16 | (low & 0xffff);
> + u32 div1 = (high & 0xffff0000) | ((low & 0xffff0000) >> 16);
Magic masks all over.
Also it needs a comment what formula is used and how.
> +
> + writel(div0, i2c_dev->base + ADDR_DVD0);
> + writel(div1, i2c_dev->base + ADDR_DVD1);
> +
> + if (freq == 400000)
> + writel((6 * apb_clk) / 10000000, i2c_dev->base + ADDR_STA0_DVD);
> + else if (freq == 100000)
> + writel((4 * apb_clk) / 1000000, i2c_dev->base + ADDR_STA0_DVD);
What about 3400000?
What about the rest of the speeds, shouldn't you return an error from here?
> +}
> +
> +static void sprd_i2c_enable(struct sprd_i2c *i2c_dev)
> +{
> + unsigned int tmp = readl(i2c_dev->base + I2C_CTL);
> +
> + tmp &= ~(I2C_EN | I2C_TRIM_OPT | (0xF << FIFO_AF_LVL) |
> + (0xF << FIFO_AE_LVL));
Magic masks (I saw them already above).
> + /* Set rx fifo full data threshold */
Drop noise comments. They don't bring any value since you have nice
function names.
> + sprd_i2c_set_full_thld(i2c_dev, I2C_FIFO_FULL_THLD);
> + /* Set tx fifo empty data threshold */
> + sprd_i2c_set_empty_thld(i2c_dev, I2C_FIFO_EMPTY_THLD);
> +
> + sprd_i2c_set_clk(i2c_dev, i2c_dev->bus_freq);
> + /* Reset rx/tx fifo */
> + sprd_i2c_reset_fifo(i2c_dev);
> + sprd_i2c_clear_irq(i2c_dev);
> +static int sprd_i2c_clk_init(struct sprd_i2c *i2c_dev)
> +{
> + struct clk *clk_i2c, *clk_parent;
> + struct device_node *np = i2c_dev->adap.dev.of_node;
> +
> + clk_i2c = of_clk_get_by_name(np, "i2c");
What the issue to use resource agnostic API here, i.e. devm_clk_get() ?
> + clk_parent = of_clk_get_by_name(np, "source");
Ditto.
> + i2c_dev->clk = of_clk_get_by_name(np, "enable");
Ditto.
> + if (!of_property_read_u32(dev->of_node, "clock-frequency", &prop))
> + i2c_dev->bus_freq = prop;
> +
> + sprd_i2c_clk_init(i2c_dev);
> + platform_set_drvdata(pdev, i2c_dev);
> +
> + ret = clk_prepare_enable(i2c_dev->clk);
> + if (ret)
> + return ret;
> +
> + sprd_i2c_enable(i2c_dev);
> +
> +error:
I would put it as
err_rpm_put:
> + pm_runtime_put_noidle(i2c_dev->dev);
> + pm_runtime_disable(i2c_dev->dev);
> + clk_disable_unprepare(i2c_dev->clk);
> + return ret;
> +}
--
With Best Regards,
Andy Shevchenko