#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>
/* 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
* @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;
u32 fmp_creg;
u32 fmp_mask;
bool wakeup_src;
+ bool smbus_mode;
+ struct i2c_client *host_notify_client;
};
/*
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;
}
}
+fail:
dev_err(dev, "Slave 0x%x could not be registered\n", slave->addr);
return -EINVAL;
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;
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;
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;
}
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);
}
&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 = {
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);
return 0;
+i2c_adapter_remove:
+ i2c_del_adapter(adap);
+
pm_disable:
pm_runtime_put_noidle(i2c_dev->dev);
pm_runtime_disable(i2c_dev->dev);
{
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);