#include <linux/mutex.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
+#include <linux/unaligned.h>
 
 enum rtl9300_bus_freq {
        RTL9300_I2C_STD_FREQ,
        struct mutex lock;
 };
 
+enum rtl9300_i2c_xfer_type {
+       RTL9300_I2C_XFER_BYTE,
+       RTL9300_I2C_XFER_WORD,
+       RTL9300_I2C_XFER_BLOCK,
+};
+
+struct rtl9300_i2c_xfer {
+       enum rtl9300_i2c_xfer_type type;
+       u16 dev_addr;
+       u8 reg_addr;
+       u8 reg_addr_len;
+       u8 *data;
+       u8 data_len;
+       bool write;
+};
+
 #define RTL9300_I2C_MST_CTRL1                          0x0
 #define RTL9300_I2C_MST_CTRL2                          0x4
 #define RTL9300_I2C_MST_DATA_WORD0                     0x8
        return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
 }
 
-static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
+static int rtl9300_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
 {
        struct rtl9300_i2c_drv_data *drv_data;
        int ret;
 
-       drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
+       if (i2c->sda_num == chan->sda_num)
+               return 0;
 
-       ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(chan->sda_num),
-                                      BIT(chan->sda_num));
+       ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
        if (ret)
                return ret;
 
-       ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], chan->sda_num);
+       drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
+       ret = drv_data->select_scl(i2c, 0);
        if (ret)
                return ret;
 
-       ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
+       ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(chan->sda_num),
+                                      BIT(chan->sda_num));
        if (ret)
                return ret;
 
-       return drv_data->select_scl(i2c, 0);
-}
-
-static int rtl9300_i2c_config_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan,
-                                  u16 addr, u16 len)
-{
-       int ret;
-
-       if (len < 1 || len > 16)
-               return -EINVAL;
-
-       ret = regmap_field_write(i2c->fields[F_DEV_ADDR], addr);
+       ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], chan->sda_num);
        if (ret)
                return ret;
 
-       return regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
+       i2c->sda_num = chan->sda_num;
+       return 0;
 }
 
-static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
+static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, u8 len)
 {
        u32 vals[4] = {};
        int i, ret;
        return 0;
 }
 
-static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, int len)
+static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, u8 len)
 {
        u32 vals[4] = {};
        int i;
        return regmap_write(i2c->regmap, i2c->data_reg, data);
 }
 
-static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
-                                   int size, union i2c_smbus_data *data, int len)
+static int rtl9300_i2c_prepare_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer *xfer)
 {
-       u32 val;
        int ret;
 
-       ret = regmap_field_write(i2c->fields[F_RWOP], read_write == I2C_SMBUS_WRITE);
+       if (xfer->data_len < 1 || xfer->data_len > 16)
+               return -EINVAL;
+
+       ret = regmap_field_write(i2c->fields[F_DEV_ADDR], xfer->dev_addr);
+       if (ret)
+               return ret;
+
+       ret = rtl9300_i2c_reg_addr_set(i2c, xfer->reg_addr, xfer->reg_addr_len);
+       if (ret)
+               return ret;
+
+       ret = regmap_field_write(i2c->fields[F_RWOP], xfer->write);
+       if (ret)
+               return ret;
+
+       ret = regmap_field_write(i2c->fields[F_DATA_WIDTH], (xfer->data_len - 1) & 0xf);
        if (ret)
                return ret;
 
+       if (xfer->write) {
+               switch (xfer->type) {
+               case RTL9300_I2C_XFER_BYTE:
+                       ret = rtl9300_i2c_writel(i2c, *xfer->data);
+                       break;
+               case RTL9300_I2C_XFER_WORD:
+                       ret = rtl9300_i2c_writel(i2c, get_unaligned((const u16 *)xfer->data));
+                       break;
+               default:
+                       ret = rtl9300_i2c_write(i2c, xfer->data, xfer->data_len);
+                       break;
+               }
+       }
+
+       return ret;
+}
+
+static int rtl9300_i2c_do_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer *xfer)
+{
+       u32 val;
+       int ret;
+
        ret = regmap_field_write(i2c->fields[F_I2C_TRIG], 1);
        if (ret)
                return ret;
        if (val)
                return -EIO;
 
-       if (read_write == I2C_SMBUS_READ) {
-               switch (size) {
-               case I2C_SMBUS_BYTE:
-               case I2C_SMBUS_BYTE_DATA:
+       if (!xfer->write) {
+               switch (xfer->type) {
+               case RTL9300_I2C_XFER_BYTE:
                        ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
                        if (ret)
                                return ret;
-                       data->byte = val & 0xff;
+
+                       *xfer->data = val & 0xff;
                        break;
-               case I2C_SMBUS_WORD_DATA:
+               case RTL9300_I2C_XFER_WORD:
                        ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
                        if (ret)
                                return ret;
-                       data->word = val & 0xffff;
-                       break;
-               case I2C_SMBUS_I2C_BLOCK_DATA:
-                       ret = rtl9300_i2c_read(i2c, &data->block[1], len);
-                       if (ret)
-                               return ret;
+
+                       put_unaligned(val & 0xffff, (u16*)xfer->data);
                        break;
                default:
-                       ret = rtl9300_i2c_read(i2c, &data->block[0], len);
+                       ret = rtl9300_i2c_read(i2c, xfer->data, xfer->data_len);
                        if (ret)
                                return ret;
                        break;
 {
        struct rtl9300_i2c_chan *chan = i2c_get_adapdata(adap);
        struct rtl9300_i2c *i2c = chan->i2c;
-       int len = 0, ret;
+       struct rtl9300_i2c_xfer xfer = {0};
+       int ret;
+
+       if (addr > 0x7f)
+               return -EINVAL;
 
        mutex_lock(&i2c->lock);
-       if (chan->sda_num != i2c->sda_num) {
-               ret = rtl9300_i2c_config_io(i2c, chan);
-               if (ret)
-                       goto out_unlock;
-               i2c->sda_num = chan->sda_num;
-       }
+
+       ret = rtl9300_i2c_config_chan(i2c, chan);
+       if (ret)
+               goto out_unlock;
+
+       xfer.dev_addr = addr & 0x7f;
+       xfer.write = (read_write == I2C_SMBUS_WRITE);
+       xfer.reg_addr = command;
+       xfer.reg_addr_len = 1;
 
        switch (size) {
        case I2C_SMBUS_BYTE:
-               if (read_write == I2C_SMBUS_WRITE) {
-                       ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
-                       if (ret)
-                               goto out_unlock;
-                       ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
-                       if (ret)
-                               goto out_unlock;
-               } else {
-                       ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
-                       if (ret)
-                               goto out_unlock;
-                       ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
-                       if (ret)
-                               goto out_unlock;
-               }
+               xfer.data = (read_write == I2C_SMBUS_READ) ? &data->byte : &command;
+               xfer.data_len = 1;
+               xfer.reg_addr = 0;
+               xfer.reg_addr_len = 0;
+               xfer.type = RTL9300_I2C_XFER_BYTE;
                break;
-
        case I2C_SMBUS_BYTE_DATA:
-               ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
-               if (ret)
-                       goto out_unlock;
-               ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
-               if (ret)
-                       goto out_unlock;
-               if (read_write == I2C_SMBUS_WRITE) {
-                       ret = rtl9300_i2c_writel(i2c, data->byte);
-                       if (ret)
-                               goto out_unlock;
-               }
+               xfer.data = &data->byte;
+               xfer.data_len = 1;
+               xfer.type = RTL9300_I2C_XFER_BYTE;
                break;
-
        case I2C_SMBUS_WORD_DATA:
-               ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
-               if (ret)
-                       goto out_unlock;
-               ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 2);
-               if (ret)
-                       goto out_unlock;
-               if (read_write == I2C_SMBUS_WRITE) {
-                       ret = rtl9300_i2c_writel(i2c, data->word);
-                       if (ret)
-                               goto out_unlock;
-               }
+               xfer.data = (u8 *)&data->word;
+               xfer.data_len = 2;
+               xfer.type = RTL9300_I2C_XFER_WORD;
                break;
-
        case I2C_SMBUS_BLOCK_DATA:
-               ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
-               if (ret)
-                       goto out_unlock;
-               if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
-                       ret = -EINVAL;
-                       goto out_unlock;
-               }
-               ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0] + 1);
-               if (ret)
-                       goto out_unlock;
-               if (read_write == I2C_SMBUS_WRITE) {
-                       ret = rtl9300_i2c_write(i2c, &data->block[0], data->block[0] + 1);
-                       if (ret)
-                               goto out_unlock;
-               }
-               len = data->block[0] + 1;
+               xfer.data = &data->block[0];
+               xfer.data_len = data->block[0] + 1;
+               xfer.type = RTL9300_I2C_XFER_BLOCK;
                break;
-
        case I2C_SMBUS_I2C_BLOCK_DATA:
-               ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
-               if (ret)
-                       goto out_unlock;
-               if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
-                       ret = -EINVAL;
-                       goto out_unlock;
-               }
-               ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
-               if (ret)
-                       goto out_unlock;
-               if (read_write == I2C_SMBUS_WRITE) {
-                       ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
-                       if (ret)
-                               goto out_unlock;
-               }
-               len = data->block[0];
+               xfer.data = &data->block[1];
+               xfer.data_len = data->block[0];
+               xfer.type = RTL9300_I2C_XFER_BLOCK;
                break;
-
        default:
                dev_err(&adap->dev, "Unsupported transaction %d\n", size);
                ret = -EOPNOTSUPP;
                goto out_unlock;
        }
 
-       ret = rtl9300_i2c_execute_xfer(i2c, read_write, size, data, len);
+       ret = rtl9300_i2c_prepare_xfer(i2c, &xfer);
+       if (ret)
+               goto out_unlock;
+
+       ret = rtl9300_i2c_do_xfer(i2c, &xfer);
 
 out_unlock:
        mutex_unlock(&i2c->lock);