i2c: stm32f7: Add SMBus Host-Notify protocol support
authorAlain Volmat <alain.volmat@st.com>
Mon, 3 Aug 2020 05:17:56 +0000 (07:17 +0200)
committerWolfram Sang <wsa@kernel.org>
Fri, 18 Sep 2020 21:02:02 +0000 (23:02 +0200)
Rely on the core functions to implement the host-notify
protocol via the a I2C slave device.

Signed-off-by: Alain Volmat <alain.volmat@st.com>
Reviewed-by: Pierre-Yves MORDRET <pierre-yves.mordret@st.com>
Signed-off-by: Wolfram Sang <wsa@kernel.org>
drivers/i2c/busses/Kconfig
drivers/i2c/busses/i2c-stm32f7.c

index 38639d53a171ac72b6935562dbc68bc46da651b1..46a24faef35253c3abaee72bf0d3c17822103c62 100644 (file)
@@ -1025,6 +1025,7 @@ config I2C_STM32F7
        tristate "STMicroelectronics STM32F7 I2C support"
        depends on ARCH_STM32 || COMPILE_TEST
        select I2C_SLAVE
+       select I2C_SMBUS
        help
          Enable this option to add support for STM32 I2C controller embedded
          in STM32F7 SoCs.
index bff3479fe122a08fa494dbb72635b7a2fcaf5cac..425eff5b18e0bfc2a48098dffb7e86da36cd2d31 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/i2c.h>
+#include <linux/i2c-smbus.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/iopoll.h>
@@ -50,6 +51,7 @@
 
 /* STM32F7 I2C control 1 */
 #define STM32F7_I2C_CR1_PECEN                  BIT(23)
+#define STM32F7_I2C_CR1_SMBHEN                 BIT(20)
 #define STM32F7_I2C_CR1_WUPEN                  BIT(18)
 #define STM32F7_I2C_CR1_SBC                    BIT(16)
 #define STM32F7_I2C_CR1_RXDMAEN                        BIT(15)
 
 #define STM32F7_I2C_MAX_LEN                    0xff
 #define STM32F7_I2C_DMA_LEN_MIN                        0x16
-#define STM32F7_I2C_MAX_SLAVE                  0x2
+#define STM32F7_I2C_MAX_SLAVE                  0x3
 
 #define STM32F7_I2C_DNF_DEFAULT                        0
 #define STM32F7_I2C_DNF_MAX                    16
@@ -301,6 +303,8 @@ struct stm32f7_i2c_msg {
  * @fmp_creg: register address for clearing Fast Mode Plus bits
  * @fmp_mask: mask for Fast Mode Plus bits in set register
  * @wakeup_src: boolean to know if the device is a wakeup source
+ * @smbus_mode: states that the controller is configured in SMBus mode
+ * @host_notify_client: SMBus host-notify client
  */
 struct stm32f7_i2c_dev {
        struct i2c_adapter adap;
@@ -327,6 +331,8 @@ struct stm32f7_i2c_dev {
        u32 fmp_creg;
        u32 fmp_mask;
        bool wakeup_src;
+       bool smbus_mode;
+       struct i2c_client *host_notify_client;
 };
 
 /*
@@ -1321,11 +1327,19 @@ static int stm32f7_i2c_get_free_slave_id(struct stm32f7_i2c_dev *i2c_dev,
        int i;
 
        /*
-        * slave[0] supports 7-bit and 10-bit slave address
-        * slave[1] supports 7-bit slave address only
+        * slave[0] support only SMBus Host address (0x8)
+        * slave[1] supports 7-bit and 10-bit slave address
+        * slave[2] supports 7-bit slave address only
         */
-       for (i = STM32F7_I2C_MAX_SLAVE - 1; i >= 0; i--) {
-               if (i == 1 && (slave->flags & I2C_CLIENT_TEN))
+       if (i2c_dev->smbus_mode && (slave->addr == 0x08)) {
+               if (i2c_dev->slave[0])
+                       goto fail;
+               *id = 0;
+               return 0;
+       }
+
+       for (i = STM32F7_I2C_MAX_SLAVE - 1; i > 0; i--) {
+               if (i == 2 && (slave->flags & I2C_CLIENT_TEN))
                        continue;
                if (!i2c_dev->slave[i]) {
                        *id = i;
@@ -1333,6 +1347,7 @@ static int stm32f7_i2c_get_free_slave_id(struct stm32f7_i2c_dev *i2c_dev,
                }
        }
 
+fail:
        dev_err(dev, "Slave 0x%x could not be registered\n", slave->addr);
 
        return -EINVAL;
@@ -1776,7 +1791,13 @@ static int stm32f7_i2c_reg_slave(struct i2c_client *slave)
        if (!stm32f7_i2c_is_slave_registered(i2c_dev))
                stm32f7_i2c_enable_wakeup(i2c_dev, true);
 
-       if (id == 0) {
+       switch (id) {
+       case 0:
+               /* Slave SMBus Host */
+               i2c_dev->slave[id] = slave;
+               break;
+
+       case 1:
                /* Configure Own Address 1 */
                oar1 = readl_relaxed(i2c_dev->base + STM32F7_I2C_OAR1);
                oar1 &= ~STM32F7_I2C_OAR1_MASK;
@@ -1789,7 +1810,9 @@ static int stm32f7_i2c_reg_slave(struct i2c_client *slave)
                oar1 |= STM32F7_I2C_OAR1_OA1EN;
                i2c_dev->slave[id] = slave;
                writel_relaxed(oar1, i2c_dev->base + STM32F7_I2C_OAR1);
-       } else if (id == 1) {
+               break;
+
+       case 2:
                /* Configure Own Address 2 */
                oar2 = readl_relaxed(i2c_dev->base + STM32F7_I2C_OAR2);
                oar2 &= ~STM32F7_I2C_OAR2_MASK;
@@ -1802,7 +1825,10 @@ static int stm32f7_i2c_reg_slave(struct i2c_client *slave)
                oar2 |= STM32F7_I2C_OAR2_OA2EN;
                i2c_dev->slave[id] = slave;
                writel_relaxed(oar2, i2c_dev->base + STM32F7_I2C_OAR2);
-       } else {
+               break;
+
+       default:
+               dev_err(dev, "I2C slave id not supported\n");
                ret = -ENODEV;
                goto pm_free;
        }
@@ -1843,10 +1869,10 @@ static int stm32f7_i2c_unreg_slave(struct i2c_client *slave)
        if (ret < 0)
                return ret;
 
-       if (id == 0) {
+       if (id == 1) {
                mask = STM32F7_I2C_OAR1_OA1EN;
                stm32f7_i2c_clr_bits(base + STM32F7_I2C_OAR1, mask);
-       } else {
+       } else if (id == 2) {
                mask = STM32F7_I2C_OAR2_OA2EN;
                stm32f7_i2c_clr_bits(base + STM32F7_I2C_OAR2, mask);
        }
@@ -1911,14 +1937,51 @@ static int stm32f7_i2c_setup_fm_plus_bits(struct platform_device *pdev,
                                          &i2c_dev->fmp_mask);
 }
 
+static int stm32f7_i2c_enable_smbus_host(struct stm32f7_i2c_dev *i2c_dev)
+{
+       struct i2c_adapter *adap = &i2c_dev->adap;
+       void __iomem *base = i2c_dev->base;
+       struct i2c_client *client;
+
+       client = i2c_new_slave_host_notify_device(adap);
+       if (IS_ERR(client))
+               return PTR_ERR(client);
+
+       i2c_dev->host_notify_client = client;
+
+       /* Enable SMBus Host address */
+       stm32f7_i2c_set_bits(base + STM32F7_I2C_CR1, STM32F7_I2C_CR1_SMBHEN);
+
+       return 0;
+}
+
+static void stm32f7_i2c_disable_smbus_host(struct stm32f7_i2c_dev *i2c_dev)
+{
+       void __iomem *base = i2c_dev->base;
+
+       if (i2c_dev->host_notify_client) {
+               /* Disable SMBus Host address */
+               stm32f7_i2c_clr_bits(base + STM32F7_I2C_CR1,
+                                    STM32F7_I2C_CR1_SMBHEN);
+               i2c_free_slave_host_notify_device(i2c_dev->host_notify_client);
+       }
+}
+
 static u32 stm32f7_i2c_func(struct i2c_adapter *adap)
 {
-       return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SLAVE |
-               I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
-               I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
-               I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
-               I2C_FUNC_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_PEC |
-               I2C_FUNC_SMBUS_I2C_BLOCK;
+       struct stm32f7_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
+
+       u32 func = I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SLAVE |
+                  I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
+                  I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
+                  I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
+                  I2C_FUNC_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_PEC |
+                  I2C_FUNC_SMBUS_I2C_BLOCK;
+
+       if (i2c_dev->smbus_mode)
+               func |= I2C_FUNC_SMBUS_HOST_NOTIFY;
+
+       return func;
 }
 
 static const struct i2c_algorithm stm32f7_i2c_algo = {
@@ -2084,10 +2147,22 @@ static int stm32f7_i2c_probe(struct platform_device *pdev)
 
        stm32f7_i2c_hw_config(i2c_dev);
 
+       i2c_dev->smbus_mode = of_property_read_bool(pdev->dev.of_node, "smbus");
+
        ret = i2c_add_adapter(adap);
        if (ret)
                goto pm_disable;
 
+       if (i2c_dev->smbus_mode) {
+               ret = stm32f7_i2c_enable_smbus_host(i2c_dev);
+               if (ret) {
+                       dev_err(i2c_dev->dev,
+                               "failed to enable SMBus Host-Notify protocol (%d)\n",
+                               ret);
+                       goto i2c_adapter_remove;
+               }
+       }
+
        dev_info(i2c_dev->dev, "STM32F7 I2C-%d bus adapter\n", adap->nr);
 
        pm_runtime_mark_last_busy(i2c_dev->dev);
@@ -2095,6 +2170,9 @@ static int stm32f7_i2c_probe(struct platform_device *pdev)
 
        return 0;
 
+i2c_adapter_remove:
+       i2c_del_adapter(adap);
+
 pm_disable:
        pm_runtime_put_noidle(i2c_dev->dev);
        pm_runtime_disable(i2c_dev->dev);
@@ -2126,6 +2204,8 @@ static int stm32f7_i2c_remove(struct platform_device *pdev)
 {
        struct stm32f7_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
 
+       stm32f7_i2c_disable_smbus_host(i2c_dev);
+
        i2c_del_adapter(&i2c_dev->adap);
        pm_runtime_get_sync(i2c_dev->dev);