Hi Daire, thanks for your driver! Some issues, but nothing major. some checkpatch errors first: ERROR: do not set execute permissions for source files #47: FILE: drivers/i2c/busses/i2c-microchip.c This must be fixed. WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? Are you interested in maintaining the driver? Then, please add a section to MAINTAINERS as well. WARNING: From:/Signed-off-by: email address mismatch: 'From: Daire McNamara <Daire.McNamara@xxxxxxxxxxxxx>' != 'Signed-off-by: Daire McNamara <daire.mcnamara@xxxxxxxxxxxxx>' Would be nice to have this consistent as well! > +config I2C_MICROCHIP > + tristate "Microchip I2C" > + help > + If you say yes to this option, support will be included for the > + Microchip I2C interface. > + Document the module name? > @@ -0,0 +1,519 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Microchip I2C controller driver > + * > + * Copyright (c) 2018 - 2021 Microchip Corporation. All rights reserved. > + * > + * Author: Daire McNamara <daire.mcnamara@xxxxxxxxxxxxx> > + */ > +#include <linux/clk.h> > +#include <linux/clkdev.h> > +#include <linux/err.h> > +#include <linux/i2c.h> > +#include <linux/iopoll.h> > +#include <linux/interrupt.h> > +#include <linux/module.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/platform_device.h> > + > +#define MICROCHIP_I2C_TIMEOUT (msecs_to_jiffies(1000)) > + > +#define MPFS_I2C_CTRL (0x00) > +#define CTRL_CR0 (0x00) > +#define CTRL_CR1 (0x01) > +#define CTRL_AA (0x02) > +#define CTRL_SI (0x03) > +#define CTRL_STO (0x04) > +#define CTRL_STA (0x05) > +#define CTRL_ENS1 (0x06) > +#define CTRL_CR2 (0x07) Using the BIT() macros, this would remove a lot of "1 << " from the driver and make it quite more readable, I'd say. > +static int mpfs_i2c_init(struct mpfs_i2c_dev *idev) > +{ > + u32 clk_rate = clk_get_rate(idev->i2c_clk); > + u32 divisor = clk_rate / idev->bus_clk_rate; Not protected against division by zero, or? > + u8 clkval; > + int ret; > + u8 ctrl = readl(idev->base + MPFS_I2C_CTRL); > + > + ctrl &= ~CLK_MASK; > + > + ret = mpfs_generate_divisor(divisor, &clkval); > + > + if (ret) > + return -1; -EINVAL instead of -1? In mpfs_generate_divisor as well. > + > + ctrl |= clkval; > + > + writel(ctrl, idev->base + MPFS_I2C_CTRL); > + > + ctrl = readl(idev->base + MPFS_I2C_CTRL); > + > + /* Reset controller */ Very obvious comment :) > + mpfs_i2c_reset(idev); > + > + return 0; > +} > +static irqreturn_t mpfs_i2c_handle_isr(int irq, void *_dev) > +{ > + bool read, finish = false; drivers/i2c/busses/i2c-microchip.c: In function ‘mpfs_i2c_handle_isr’: drivers/i2c/busses/i2c-microchip.c:244:7: warning: variable ‘read’ set but not used [-Wunused-but-set-variable] > + struct mpfs_i2c_dev *idev = _dev; > + u32 status = idev->isr_status; > + u8 ctrl; > + > + if (!idev->buf) { > + dev_warn(idev->dev, "unexpected interrupt\n"); > + return IRQ_HANDLED; > + } > + > + read = idev->msg_read ? 1 : 0; > + > + switch (status) { > + case STATUS_M_START_SENT: > + case STATUS_M_REPEATED_START_SENT: > + ctrl = readl(idev->base + MPFS_I2C_CTRL); > + ctrl &= ~(1 << CTRL_STA); > + writel(idev->addr, idev->base + MPFS_I2C_DATA); > + writel(ctrl, idev->base + MPFS_I2C_CTRL); > + if (idev->msg_len <= 0) CPPCHECK drivers/i2c/busses/i2c-microchip.c:263:21: warning: Checking if unsigned expression 'idev->msg_len' is less than zero. [unsignedLessThanZero] ... > +static int mpfs_i2c_xfer_msg(struct mpfs_i2c_dev *idev, struct i2c_msg *msg) > +{ > + u8 ctrl; > + unsigned long time_left; > + > + if (msg->len == 0) > + return -EINVAL; Use a 'struct i2c_adapter_quirks' with the I2C_AQ_NO_ZERO_LEN flag and the core will check that for you. > +static u32 mpfs_i2c_func(struct i2c_adapter *adap) > +{ > + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; > +} If you can't do zero lenght transfers, you'll need (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK). > +static int mpfs_i2c_probe(struct platform_device *pdev) > +{ > + struct mpfs_i2c_dev *idev = NULL; Initialization not needed. > + idev->dev = &pdev->dev; > + init_completion(&idev->msg_complete); > + spin_lock_init(&idev->lock); This spinlock is not used anywhere? It seems that everything spinlock related can go? > + > + val = device_property_read_u32(idev->dev, "clock-frequency", > + &idev->bus_clk_rate); > + if (val) { > + dev_info(&pdev->dev, "default to 100kHz\n"); > + idev->bus_clk_rate = 100000; /* default clock rate */ > + } > + > + if (idev->bus_clk_rate > 400000) { > + dev_err(&pdev->dev, "invalid clock-frequency %d\n", > + idev->bus_clk_rate); > + return -EINVAL; > + } > + > + ret = devm_request_irq(&pdev->dev, irq, mpfs_i2c_isr, > + IRQF_SHARED, pdev->name, idev); > + if (ret) { > + dev_err(&pdev->dev, "failed to claim irq %d\n", irq); > + return ret; > + } > + > + ret = clk_prepare_enable(idev->i2c_clk); > + if (ret) { > + dev_err(&pdev->dev, "failed to enable clock\n"); > + return ret; > + } > + > + ret = mpfs_i2c_init(idev); > + if (ret) { > + dev_err(&pdev->dev, "failed to program clock divider\n"); > + return ret; > + } > + > + i2c_set_adapdata(&idev->adapter, idev); > + snprintf(idev->adapter.name, sizeof(idev->adapter.name), > + "Microchip I2C hw bus"); Most people add the base address of the registers here to be able to differentiate multiple adapters in one system. > + idev->adapter.owner = THIS_MODULE; > + idev->adapter.algo = &mpfs_i2c_algo; > + idev->adapter.dev.parent = &pdev->dev; > + idev->adapter.dev.of_node = pdev->dev.of_node; > + > + platform_set_drvdata(pdev, idev); > + > + ret = i2c_add_adapter(&idev->adapter); > + if (ret) { > + clk_disable_unprepare(idev->i2c_clk); > + return ret; > + } > + > + dev_info(&pdev->dev, "Microchip I2C Probe Complete\n"); > + > + return 0; > +} But this is all pretty minor stuff. It looks quite good actually! Happy hacking, Wolfram
Attachment:
signature.asc
Description: PGP signature