Merge branch 'i2c/for-5.13' of git://git.kernel.org/pub/scm/linux/kernel/git/wsa...
[linux-2.6-microblaze.git] / drivers / i2c / busses / i2c-designware-master.c
index 873ef38..13be1d6 100644 (file)
 
 #include "i2c-designware-core.h"
 
+#define AMD_TIMEOUT_MIN_US     25
+#define AMD_TIMEOUT_MAX_US     250
+#define AMD_MASTERCFG_MASK     GENMASK(15, 0)
+
 static void i2c_dw_configure_fifo_master(struct dw_i2c_dev *dev)
 {
        /* Configure Tx/Rx FIFO threshold levels */
@@ -35,10 +39,10 @@ static void i2c_dw_configure_fifo_master(struct dw_i2c_dev *dev)
 
 static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
 {
-       const char *mode_str, *fp_str = "";
        u32 comp_param1;
        u32 sda_falling_time, scl_falling_time;
        struct i2c_timings *t = &dev->timings;
+       const char *fp_str = "";
        u32 ic_clk;
        int ret;
 
@@ -78,7 +82,7 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
         * difference is the timing parameter values since the registers are
         * the same.
         */
-       if (t->bus_freq_hz == 1000000) {
+       if (t->bus_freq_hz == I2C_MAX_FAST_MODE_PLUS_FREQ) {
                /*
                 * Check are Fast Mode Plus parameters available. Calculate
                 * SCL timing parameters for Fast Mode Plus if not set.
@@ -154,22 +158,10 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
 
        ret = i2c_dw_set_sda_hold(dev);
        if (ret)
-               goto out;
-
-       switch (dev->master_cfg & DW_IC_CON_SPEED_MASK) {
-       case DW_IC_CON_SPEED_STD:
-               mode_str = "Standard Mode";
-               break;
-       case DW_IC_CON_SPEED_HIGH:
-               mode_str = "High Speed Mode";
-               break;
-       default:
-               mode_str = "Fast Mode";
-       }
-       dev_dbg(dev->dev, "Bus speed: %s%s\n", mode_str, fp_str);
+               return ret;
 
-out:
-       return ret;
+       dev_dbg(dev->dev, "Bus speed: %s\n", i2c_freq_mode_string(t->bus_freq_hz));
+       return 0;
 }
 
 /**
@@ -260,6 +252,108 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
        regmap_write(dev->map, DW_IC_INTR_MASK, DW_IC_INTR_MASTER_MASK);
 }
 
+static int i2c_dw_check_stopbit(struct dw_i2c_dev *dev)
+{
+       u32 val;
+       int ret;
+
+       ret = regmap_read_poll_timeout(dev->map, DW_IC_INTR_STAT, val,
+                                      !(val & DW_IC_INTR_STOP_DET),
+                                       1100, 20000);
+       if (ret)
+               dev_err(dev->dev, "i2c timeout error %d\n", ret);
+
+       return ret;
+}
+
+static int i2c_dw_status(struct dw_i2c_dev *dev)
+{
+       int status;
+
+       status = i2c_dw_wait_bus_not_busy(dev);
+       if (status)
+               return status;
+
+       return i2c_dw_check_stopbit(dev);
+}
+
+/*
+ * Initiate and continue master read/write transaction with polling
+ * based transfer routine afterward write messages into the Tx buffer.
+ */
+static int amd_i2c_dw_xfer_quirk(struct i2c_adapter *adap, struct i2c_msg *msgs, int num_msgs)
+{
+       struct dw_i2c_dev *dev = i2c_get_adapdata(adap);
+       int msg_wrt_idx, msg_itr_lmt, buf_len, data_idx;
+       int cmd = 0, status;
+       u8 *tx_buf;
+       u32 val;
+
+       /*
+        * In order to enable the interrupt for UCSI i.e. AMD NAVI GPU card,
+        * it is mandatory to set the right value in specific register
+        * (offset:0x474) as per the hardware IP specification.
+        */
+       regmap_write(dev->map, AMD_UCSI_INTR_REG, AMD_UCSI_INTR_EN);
+
+       dev->msgs = msgs;
+       dev->msgs_num = num_msgs;
+       i2c_dw_xfer_init(dev);
+       i2c_dw_disable_int(dev);
+
+       /* Initiate messages read/write transaction */
+       for (msg_wrt_idx = 0; msg_wrt_idx < num_msgs; msg_wrt_idx++) {
+               tx_buf = msgs[msg_wrt_idx].buf;
+               buf_len = msgs[msg_wrt_idx].len;
+
+               if (!(msgs[msg_wrt_idx].flags & I2C_M_RD))
+                       regmap_write(dev->map, DW_IC_TX_TL, buf_len - 1);
+               /*
+                * Initiate the i2c read/write transaction of buffer length,
+                * and poll for bus busy status. For the last message transfer,
+                * update the command with stopbit enable.
+                */
+               for (msg_itr_lmt = buf_len; msg_itr_lmt > 0; msg_itr_lmt--) {
+                       if (msg_wrt_idx == num_msgs - 1 && msg_itr_lmt == 1)
+                               cmd |= BIT(9);
+
+                       if (msgs[msg_wrt_idx].flags & I2C_M_RD) {
+                               /* Due to hardware bug, need to write the same command twice. */
+                               regmap_write(dev->map, DW_IC_DATA_CMD, 0x100);
+                               regmap_write(dev->map, DW_IC_DATA_CMD, 0x100 | cmd);
+                               if (cmd) {
+                                       regmap_write(dev->map, DW_IC_TX_TL, 2 * (buf_len - 1));
+                                       regmap_write(dev->map, DW_IC_RX_TL, 2 * (buf_len - 1));
+                                       /*
+                                        * Need to check the stop bit. However, it cannot be
+                                        * detected from the registers so we check it always
+                                        * when read/write the last byte.
+                                        */
+                                       status = i2c_dw_status(dev);
+                                       if (status)
+                                               return status;
+
+                                       for (data_idx = 0; data_idx < buf_len; data_idx++) {
+                                               regmap_read(dev->map, DW_IC_DATA_CMD, &val);
+                                               tx_buf[data_idx] = val;
+                                       }
+                                       status = i2c_dw_check_stopbit(dev);
+                                       if (status)
+                                               return status;
+                               }
+                       } else {
+                               regmap_write(dev->map, DW_IC_DATA_CMD, *tx_buf++ | cmd);
+                               usleep_range(AMD_TIMEOUT_MIN_US, AMD_TIMEOUT_MAX_US);
+                       }
+               }
+               status = i2c_dw_check_stopbit(dev);
+               if (status)
+                       return status;
+       }
+
+       return 0;
+}
+
 /*
  * Initiate (and continue) low level master read/write transaction.
  * This function is only called from i2c_dw_isr, and pumping i2c_msg
@@ -463,6 +557,16 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
 
        pm_runtime_get_sync(dev->dev);
 
+       /*
+        * Initiate I2C message transfer when AMD NAVI GPU card is enabled,
+        * As it is polling based transfer mechanism, which does not support
+        * interrupt based functionalities of existing DesignWare driver.
+        */
+       if ((dev->flags & MODEL_MASK) == MODEL_AMD_NAVI_GPU) {
+               ret = amd_i2c_dw_xfer_quirk(adap, msgs, num);
+               goto done_nolock;
+       }
+
        if (dev_WARN_ONCE(dev->dev, dev->suspended, "Transfer while suspended\n")) {
                ret = -ESHUTDOWN;
                goto done_nolock;
@@ -739,6 +843,20 @@ static int i2c_dw_init_recovery_info(struct dw_i2c_dev *dev)
        return 0;
 }
 
+static int amd_i2c_adap_quirk(struct dw_i2c_dev *dev)
+{
+       struct i2c_adapter *adap = &dev->adapter;
+       int ret;
+
+       pm_runtime_get_noresume(dev->dev);
+       ret = i2c_add_numbered_adapter(adap);
+       if (ret)
+               dev_err(dev->dev, "Failed to add adapter: %d\n", ret);
+       pm_runtime_put_noidle(dev->dev);
+
+       return ret;
+}
+
 int i2c_dw_probe_master(struct dw_i2c_dev *dev)
 {
        struct i2c_adapter *adap = &dev->adapter;
@@ -775,6 +893,9 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev)
        adap->dev.parent = dev->dev;
        i2c_set_adapdata(adap, dev);
 
+       if ((dev->flags & MODEL_MASK) == MODEL_AMD_NAVI_GPU)
+               return amd_i2c_adap_quirk(dev);
+
        if (dev->flags & ACCESS_NO_IRQ_SUSPEND) {
                irq_flags = IRQF_NO_SUSPEND;
        } else {