// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 
 | 
/* Copyright (c) 2016-2018 Mellanox Technologies. All rights reserved */ 
 | 
  
 | 
#include <linux/err.h> 
 | 
#include <linux/i2c.h> 
 | 
#include <linux/init.h> 
 | 
#include <linux/jiffies.h> 
 | 
#include <linux/kernel.h> 
 | 
#include <linux/mutex.h> 
 | 
#include <linux/module.h> 
 | 
#include <linux/mod_devicetable.h> 
 | 
#include <linux/slab.h> 
 | 
  
 | 
#include "cmd.h" 
 | 
#include "core.h" 
 | 
#include "i2c.h" 
 | 
#include "resources.h" 
 | 
  
 | 
#define MLXSW_I2C_CIR2_BASE        0x72000 
 | 
#define MLXSW_I2C_CIR_STATUS_OFF    0x18 
 | 
#define MLXSW_I2C_CIR2_OFF_STATUS    (MLXSW_I2C_CIR2_BASE + \ 
 | 
                     MLXSW_I2C_CIR_STATUS_OFF) 
 | 
#define MLXSW_I2C_OPMOD_SHIFT        12 
 | 
#define MLXSW_I2C_EVENT_BIT_SHIFT    22 
 | 
#define MLXSW_I2C_GO_BIT_SHIFT        23 
 | 
#define MLXSW_I2C_CIR_CTRL_STATUS_SHIFT    24 
 | 
#define MLXSW_I2C_EVENT_BIT        BIT(MLXSW_I2C_EVENT_BIT_SHIFT) 
 | 
#define MLXSW_I2C_GO_BIT        BIT(MLXSW_I2C_GO_BIT_SHIFT) 
 | 
#define MLXSW_I2C_GO_OPMODE        BIT(MLXSW_I2C_OPMOD_SHIFT) 
 | 
#define MLXSW_I2C_SET_IMM_CMD        (MLXSW_I2C_GO_OPMODE | \ 
 | 
                     MLXSW_CMD_OPCODE_QUERY_FW) 
 | 
#define MLXSW_I2C_PUSH_IMM_CMD        (MLXSW_I2C_GO_BIT | \ 
 | 
                     MLXSW_I2C_SET_IMM_CMD) 
 | 
#define MLXSW_I2C_SET_CMD        (MLXSW_CMD_OPCODE_ACCESS_REG) 
 | 
#define MLXSW_I2C_PUSH_CMD        (MLXSW_I2C_GO_BIT | MLXSW_I2C_SET_CMD) 
 | 
#define MLXSW_I2C_TLV_HDR_SIZE        0x10 
 | 
#define MLXSW_I2C_ADDR_WIDTH        4 
 | 
#define MLXSW_I2C_PUSH_CMD_SIZE        (MLXSW_I2C_ADDR_WIDTH + 4) 
 | 
#define MLXSW_I2C_SET_EVENT_CMD        (MLXSW_I2C_EVENT_BIT) 
 | 
#define MLXSW_I2C_PUSH_EVENT_CMD    (MLXSW_I2C_GO_BIT | \ 
 | 
                     MLXSW_I2C_SET_EVENT_CMD) 
 | 
#define MLXSW_I2C_READ_SEMA_SIZE    4 
 | 
#define MLXSW_I2C_PREP_SIZE        (MLXSW_I2C_ADDR_WIDTH + 28) 
 | 
#define MLXSW_I2C_MBOX_SIZE        20 
 | 
#define MLXSW_I2C_MBOX_OUT_PARAM_OFF    12 
 | 
#define MLXSW_I2C_MBOX_OFFSET_BITS    20 
 | 
#define MLXSW_I2C_MBOX_SIZE_BITS    12 
 | 
#define MLXSW_I2C_ADDR_BUF_SIZE        4 
 | 
#define MLXSW_I2C_BLK_DEF        32 
 | 
#define MLXSW_I2C_RETRY            5 
 | 
#define MLXSW_I2C_TIMEOUT_MSECS        5000 
 | 
#define MLXSW_I2C_MAX_DATA_SIZE        256 
 | 
  
 | 
/** 
 | 
 * struct mlxsw_i2c - device private data: 
 | 
 * @cmd: command attributes; 
 | 
 * @cmd.mb_size_in: input mailbox size; 
 | 
 * @cmd.mb_off_in: input mailbox offset in register space; 
 | 
 * @cmd.mb_size_out: output mailbox size; 
 | 
 * @cmd.mb_off_out: output mailbox offset in register space; 
 | 
 * @cmd.lock: command execution lock; 
 | 
 * @dev: I2C device; 
 | 
 * @core: switch core pointer; 
 | 
 * @bus_info: bus info block; 
 | 
 * @block_size: maximum block size allowed to pass to under layer; 
 | 
 */ 
 | 
struct mlxsw_i2c { 
 | 
    struct { 
 | 
        u32 mb_size_in; 
 | 
        u32 mb_off_in; 
 | 
        u32 mb_size_out; 
 | 
        u32 mb_off_out; 
 | 
        struct mutex lock; 
 | 
    } cmd; 
 | 
    struct device *dev; 
 | 
    struct mlxsw_core *core; 
 | 
    struct mlxsw_bus_info bus_info; 
 | 
    u16 block_size; 
 | 
}; 
 | 
  
 | 
#define MLXSW_I2C_READ_MSG(_client, _addr_buf, _buf, _len) {    \ 
 | 
    { .addr = (_client)->addr,                \ 
 | 
      .buf = (_addr_buf),                    \ 
 | 
      .len = MLXSW_I2C_ADDR_BUF_SIZE,            \ 
 | 
      .flags = 0 },                        \ 
 | 
    { .addr = (_client)->addr,                \ 
 | 
      .buf = (_buf),                    \ 
 | 
      .len = (_len),                    \ 
 | 
      .flags = I2C_M_RD } } 
 | 
  
 | 
#define MLXSW_I2C_WRITE_MSG(_client, _buf, _len)        \ 
 | 
    { .addr = (_client)->addr,                \ 
 | 
      .buf = (u8 *)(_buf),                    \ 
 | 
      .len = (_len),                    \ 
 | 
      .flags = 0 } 
 | 
  
 | 
/* Routine converts in and out mail boxes offset and size. */ 
 | 
static inline void 
 | 
mlxsw_i2c_convert_mbox(struct mlxsw_i2c *mlxsw_i2c, u8 *buf) 
 | 
{ 
 | 
    u32 tmp; 
 | 
  
 | 
    /* Local in/out mailboxes: 20 bits for offset, 12 for size */ 
 | 
    tmp = be32_to_cpup((__be32 *) buf); 
 | 
    mlxsw_i2c->cmd.mb_off_in = tmp & 
 | 
                   GENMASK(MLXSW_I2C_MBOX_OFFSET_BITS - 1, 0); 
 | 
    mlxsw_i2c->cmd.mb_size_in = (tmp & GENMASK(31, 
 | 
                    MLXSW_I2C_MBOX_OFFSET_BITS)) >> 
 | 
                    MLXSW_I2C_MBOX_OFFSET_BITS; 
 | 
  
 | 
    tmp = be32_to_cpup((__be32 *) (buf + MLXSW_I2C_ADDR_WIDTH)); 
 | 
    mlxsw_i2c->cmd.mb_off_out = tmp & 
 | 
                    GENMASK(MLXSW_I2C_MBOX_OFFSET_BITS - 1, 0); 
 | 
    mlxsw_i2c->cmd.mb_size_out = (tmp & GENMASK(31, 
 | 
                    MLXSW_I2C_MBOX_OFFSET_BITS)) >> 
 | 
                    MLXSW_I2C_MBOX_OFFSET_BITS; 
 | 
} 
 | 
  
 | 
/* Routine obtains register size from mail box buffer. */ 
 | 
static inline int mlxsw_i2c_get_reg_size(u8 *in_mbox) 
 | 
{ 
 | 
    u16  tmp = be16_to_cpup((__be16 *) (in_mbox + MLXSW_I2C_TLV_HDR_SIZE)); 
 | 
  
 | 
    return (tmp & 0x7ff) * 4 + MLXSW_I2C_TLV_HDR_SIZE; 
 | 
} 
 | 
  
 | 
/* Routine sets I2C device internal offset in the transaction buffer. */ 
 | 
static inline void mlxsw_i2c_set_slave_addr(u8 *buf, u32 off) 
 | 
{ 
 | 
    __be32 *val = (__be32 *) buf; 
 | 
  
 | 
    *val = htonl(off); 
 | 
} 
 | 
  
 | 
/* Routine waits until go bit is cleared. */ 
 | 
static int mlxsw_i2c_wait_go_bit(struct i2c_client *client, 
 | 
                 struct mlxsw_i2c *mlxsw_i2c, u8 *p_status) 
 | 
{ 
 | 
    u8 addr_buf[MLXSW_I2C_ADDR_BUF_SIZE]; 
 | 
    u8 buf[MLXSW_I2C_READ_SEMA_SIZE]; 
 | 
    int len = MLXSW_I2C_READ_SEMA_SIZE; 
 | 
    struct i2c_msg read_sema[] = 
 | 
        MLXSW_I2C_READ_MSG(client, addr_buf, buf, len); 
 | 
    bool wait_done = false; 
 | 
    unsigned long end; 
 | 
    int i = 0, err; 
 | 
  
 | 
    mlxsw_i2c_set_slave_addr(addr_buf, MLXSW_I2C_CIR2_OFF_STATUS); 
 | 
  
 | 
    end = jiffies + msecs_to_jiffies(MLXSW_I2C_TIMEOUT_MSECS); 
 | 
    do { 
 | 
        u32 ctrl; 
 | 
  
 | 
        err = i2c_transfer(client->adapter, read_sema, 
 | 
                   ARRAY_SIZE(read_sema)); 
 | 
  
 | 
        ctrl = be32_to_cpu(*(__be32 *) buf); 
 | 
        if (err == ARRAY_SIZE(read_sema)) { 
 | 
            if (!(ctrl & MLXSW_I2C_GO_BIT)) { 
 | 
                wait_done = true; 
 | 
                *p_status = ctrl >> 
 | 
                        MLXSW_I2C_CIR_CTRL_STATUS_SHIFT; 
 | 
                break; 
 | 
            } 
 | 
        } 
 | 
        cond_resched(); 
 | 
    } while ((time_before(jiffies, end)) || (i++ < MLXSW_I2C_RETRY)); 
 | 
  
 | 
    if (wait_done) { 
 | 
        if (*p_status) 
 | 
            err = -EIO; 
 | 
    } else { 
 | 
        return -ETIMEDOUT; 
 | 
    } 
 | 
  
 | 
    return err > 0 ? 0 : err; 
 | 
} 
 | 
  
 | 
/* Routine posts a command to ASIC through mail box. */ 
 | 
static int mlxsw_i2c_write_cmd(struct i2c_client *client, 
 | 
                   struct mlxsw_i2c *mlxsw_i2c, 
 | 
                   int immediate) 
 | 
{ 
 | 
    __be32 push_cmd_buf[MLXSW_I2C_PUSH_CMD_SIZE / 4] = { 
 | 
        0, cpu_to_be32(MLXSW_I2C_PUSH_IMM_CMD) 
 | 
    }; 
 | 
    __be32 prep_cmd_buf[MLXSW_I2C_PREP_SIZE / 4] = { 
 | 
        0, 0, 0, 0, 0, 0, 
 | 
        cpu_to_be32(client->adapter->nr & 0xffff), 
 | 
        cpu_to_be32(MLXSW_I2C_SET_IMM_CMD) 
 | 
    }; 
 | 
    struct i2c_msg push_cmd = 
 | 
        MLXSW_I2C_WRITE_MSG(client, push_cmd_buf, 
 | 
                    MLXSW_I2C_PUSH_CMD_SIZE); 
 | 
    struct i2c_msg prep_cmd = 
 | 
        MLXSW_I2C_WRITE_MSG(client, prep_cmd_buf, MLXSW_I2C_PREP_SIZE); 
 | 
    int err; 
 | 
  
 | 
    if (!immediate) { 
 | 
        push_cmd_buf[1] = cpu_to_be32(MLXSW_I2C_PUSH_CMD); 
 | 
        prep_cmd_buf[7] = cpu_to_be32(MLXSW_I2C_SET_CMD); 
 | 
    } 
 | 
    mlxsw_i2c_set_slave_addr((u8 *)prep_cmd_buf, 
 | 
                 MLXSW_I2C_CIR2_BASE); 
 | 
    mlxsw_i2c_set_slave_addr((u8 *)push_cmd_buf, 
 | 
                 MLXSW_I2C_CIR2_OFF_STATUS); 
 | 
  
 | 
    /* Prepare Command Interface Register for transaction */ 
 | 
    err = i2c_transfer(client->adapter, &prep_cmd, 1); 
 | 
    if (err < 0) 
 | 
        return err; 
 | 
    else if (err != 1) 
 | 
        return -EIO; 
 | 
  
 | 
    /* Write out Command Interface Register GO bit to push transaction */ 
 | 
    err = i2c_transfer(client->adapter, &push_cmd, 1); 
 | 
    if (err < 0) 
 | 
        return err; 
 | 
    else if (err != 1) 
 | 
        return -EIO; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
/* Routine posts initialization command to ASIC through mail box. */ 
 | 
static int 
 | 
mlxsw_i2c_write_init_cmd(struct i2c_client *client, 
 | 
             struct mlxsw_i2c *mlxsw_i2c, u16 opcode, u32 in_mod) 
 | 
{ 
 | 
    __be32 push_cmd_buf[MLXSW_I2C_PUSH_CMD_SIZE / 4] = { 
 | 
        0, cpu_to_be32(MLXSW_I2C_PUSH_EVENT_CMD) 
 | 
    }; 
 | 
    __be32 prep_cmd_buf[MLXSW_I2C_PREP_SIZE / 4] = { 
 | 
        0, 0, 0, 0, 0, 0, 
 | 
        cpu_to_be32(client->adapter->nr & 0xffff), 
 | 
        cpu_to_be32(MLXSW_I2C_SET_EVENT_CMD) 
 | 
    }; 
 | 
    struct i2c_msg push_cmd = 
 | 
        MLXSW_I2C_WRITE_MSG(client, push_cmd_buf, 
 | 
                    MLXSW_I2C_PUSH_CMD_SIZE); 
 | 
    struct i2c_msg prep_cmd = 
 | 
        MLXSW_I2C_WRITE_MSG(client, prep_cmd_buf, MLXSW_I2C_PREP_SIZE); 
 | 
    u8 status; 
 | 
    int err; 
 | 
  
 | 
    push_cmd_buf[1] = cpu_to_be32(MLXSW_I2C_PUSH_EVENT_CMD | opcode); 
 | 
    prep_cmd_buf[3] = cpu_to_be32(in_mod); 
 | 
    prep_cmd_buf[7] = cpu_to_be32(MLXSW_I2C_GO_BIT | opcode); 
 | 
    mlxsw_i2c_set_slave_addr((u8 *)prep_cmd_buf, 
 | 
                 MLXSW_I2C_CIR2_BASE); 
 | 
    mlxsw_i2c_set_slave_addr((u8 *)push_cmd_buf, 
 | 
                 MLXSW_I2C_CIR2_OFF_STATUS); 
 | 
  
 | 
    /* Prepare Command Interface Register for transaction */ 
 | 
    err = i2c_transfer(client->adapter, &prep_cmd, 1); 
 | 
    if (err < 0) 
 | 
        return err; 
 | 
    else if (err != 1) 
 | 
        return -EIO; 
 | 
  
 | 
    /* Write out Command Interface Register GO bit to push transaction */ 
 | 
    err = i2c_transfer(client->adapter, &push_cmd, 1); 
 | 
    if (err < 0) 
 | 
        return err; 
 | 
    else if (err != 1) 
 | 
        return -EIO; 
 | 
  
 | 
    /* Wait until go bit is cleared. */ 
 | 
    err = mlxsw_i2c_wait_go_bit(client, mlxsw_i2c, &status); 
 | 
    if (err) { 
 | 
        dev_err(&client->dev, "HW semaphore is not released"); 
 | 
        return err; 
 | 
    } 
 | 
  
 | 
    /* Validate transaction completion status. */ 
 | 
    if (status) { 
 | 
        dev_err(&client->dev, "Bad transaction completion status %x\n", 
 | 
            status); 
 | 
        return -EIO; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
/* Routine obtains mail box offsets from ASIC register space. */ 
 | 
static int mlxsw_i2c_get_mbox(struct i2c_client *client, 
 | 
                  struct mlxsw_i2c *mlxsw_i2c) 
 | 
{ 
 | 
    u8 addr_buf[MLXSW_I2C_ADDR_BUF_SIZE]; 
 | 
    u8 buf[MLXSW_I2C_MBOX_SIZE]; 
 | 
    struct i2c_msg mbox_cmd[] = 
 | 
        MLXSW_I2C_READ_MSG(client, addr_buf, buf, MLXSW_I2C_MBOX_SIZE); 
 | 
    int err; 
 | 
  
 | 
    /* Read mail boxes offsets. */ 
 | 
    mlxsw_i2c_set_slave_addr(addr_buf, MLXSW_I2C_CIR2_BASE); 
 | 
    err = i2c_transfer(client->adapter, mbox_cmd, 2); 
 | 
    if (err != 2) { 
 | 
        dev_err(&client->dev, "Could not obtain mail boxes\n"); 
 | 
        if (!err) 
 | 
            return -EIO; 
 | 
        else 
 | 
            return err; 
 | 
    } 
 | 
  
 | 
    /* Convert mail boxes. */ 
 | 
    mlxsw_i2c_convert_mbox(mlxsw_i2c, &buf[MLXSW_I2C_MBOX_OUT_PARAM_OFF]); 
 | 
  
 | 
    return err; 
 | 
} 
 | 
  
 | 
/* Routine sends I2C write transaction to ASIC device. */ 
 | 
static int 
 | 
mlxsw_i2c_write(struct device *dev, size_t in_mbox_size, u8 *in_mbox, int num, 
 | 
        u8 *p_status) 
 | 
{ 
 | 
    struct i2c_client *client = to_i2c_client(dev); 
 | 
    struct mlxsw_i2c *mlxsw_i2c = i2c_get_clientdata(client); 
 | 
    unsigned long timeout = msecs_to_jiffies(MLXSW_I2C_TIMEOUT_MSECS); 
 | 
    int off = mlxsw_i2c->cmd.mb_off_in, chunk_size, i, j; 
 | 
    unsigned long end; 
 | 
    u8 *tran_buf; 
 | 
    struct i2c_msg write_tran = 
 | 
        MLXSW_I2C_WRITE_MSG(client, NULL, MLXSW_I2C_PUSH_CMD_SIZE); 
 | 
    int err; 
 | 
  
 | 
    tran_buf = kmalloc(mlxsw_i2c->block_size + MLXSW_I2C_ADDR_BUF_SIZE, 
 | 
               GFP_KERNEL); 
 | 
    if (!tran_buf) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    write_tran.buf = tran_buf; 
 | 
    for (i = 0; i < num; i++) { 
 | 
        chunk_size = (in_mbox_size > mlxsw_i2c->block_size) ? 
 | 
                 mlxsw_i2c->block_size : in_mbox_size; 
 | 
        write_tran.len = MLXSW_I2C_ADDR_WIDTH + chunk_size; 
 | 
        mlxsw_i2c_set_slave_addr(tran_buf, off); 
 | 
        memcpy(&tran_buf[MLXSW_I2C_ADDR_BUF_SIZE], in_mbox + 
 | 
               mlxsw_i2c->block_size * i, chunk_size); 
 | 
  
 | 
        j = 0; 
 | 
        end = jiffies + timeout; 
 | 
        do { 
 | 
            err = i2c_transfer(client->adapter, &write_tran, 1); 
 | 
            if (err == 1) 
 | 
                break; 
 | 
  
 | 
            cond_resched(); 
 | 
        } while ((time_before(jiffies, end)) || 
 | 
             (j++ < MLXSW_I2C_RETRY)); 
 | 
  
 | 
        if (err != 1) { 
 | 
            if (!err) { 
 | 
                err = -EIO; 
 | 
                goto mlxsw_i2c_write_exit; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        off += chunk_size; 
 | 
        in_mbox_size -= chunk_size; 
 | 
    } 
 | 
  
 | 
    /* Prepare and write out Command Interface Register for transaction. */ 
 | 
    err = mlxsw_i2c_write_cmd(client, mlxsw_i2c, 0); 
 | 
    if (err) { 
 | 
        dev_err(&client->dev, "Could not start transaction"); 
 | 
        err = -EIO; 
 | 
        goto mlxsw_i2c_write_exit; 
 | 
    } 
 | 
  
 | 
    /* Wait until go bit is cleared. */ 
 | 
    err = mlxsw_i2c_wait_go_bit(client, mlxsw_i2c, p_status); 
 | 
    if (err) { 
 | 
        dev_err(&client->dev, "HW semaphore is not released"); 
 | 
        goto mlxsw_i2c_write_exit; 
 | 
    } 
 | 
  
 | 
    /* Validate transaction completion status. */ 
 | 
    if (*p_status) { 
 | 
        dev_err(&client->dev, "Bad transaction completion status %x\n", 
 | 
            *p_status); 
 | 
        err = -EIO; 
 | 
    } 
 | 
  
 | 
mlxsw_i2c_write_exit: 
 | 
    kfree(tran_buf); 
 | 
    return err; 
 | 
} 
 | 
  
 | 
/* Routine executes I2C command. */ 
 | 
static int 
 | 
mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size, 
 | 
          u8 *in_mbox, size_t out_mbox_size, u8 *out_mbox, u8 *status) 
 | 
{ 
 | 
    struct i2c_client *client = to_i2c_client(dev); 
 | 
    struct mlxsw_i2c *mlxsw_i2c = i2c_get_clientdata(client); 
 | 
    unsigned long timeout = msecs_to_jiffies(MLXSW_I2C_TIMEOUT_MSECS); 
 | 
    u8 tran_buf[MLXSW_I2C_ADDR_BUF_SIZE]; 
 | 
    int num, chunk_size, reg_size, i, j; 
 | 
    int off = mlxsw_i2c->cmd.mb_off_out; 
 | 
    unsigned long end; 
 | 
    struct i2c_msg read_tran[] = 
 | 
        MLXSW_I2C_READ_MSG(client, tran_buf, NULL, 0); 
 | 
    int err; 
 | 
  
 | 
    WARN_ON(in_mbox_size % sizeof(u32) || out_mbox_size % sizeof(u32)); 
 | 
  
 | 
    if (in_mbox) { 
 | 
        reg_size = mlxsw_i2c_get_reg_size(in_mbox); 
 | 
        num = reg_size / mlxsw_i2c->block_size; 
 | 
        if (reg_size % mlxsw_i2c->block_size) 
 | 
            num++; 
 | 
  
 | 
        if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) { 
 | 
            dev_err(&client->dev, "Could not acquire lock"); 
 | 
            return -EINVAL; 
 | 
        } 
 | 
  
 | 
        err = mlxsw_i2c_write(dev, reg_size, in_mbox, num, status); 
 | 
        if (err) 
 | 
            goto cmd_fail; 
 | 
  
 | 
        /* No out mailbox is case of write transaction. */ 
 | 
        if (!out_mbox) { 
 | 
            mutex_unlock(&mlxsw_i2c->cmd.lock); 
 | 
            return 0; 
 | 
        } 
 | 
    } else { 
 | 
        /* No input mailbox is case of initialization query command. */ 
 | 
        reg_size = MLXSW_I2C_MAX_DATA_SIZE; 
 | 
        num = reg_size / mlxsw_i2c->block_size; 
 | 
  
 | 
        if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) { 
 | 
            dev_err(&client->dev, "Could not acquire lock"); 
 | 
            return -EINVAL; 
 | 
        } 
 | 
  
 | 
        err = mlxsw_i2c_write_init_cmd(client, mlxsw_i2c, opcode, 
 | 
                           in_mod); 
 | 
        if (err) 
 | 
            goto cmd_fail; 
 | 
    } 
 | 
  
 | 
    /* Send read transaction to get output mailbox content. */ 
 | 
    read_tran[1].buf = out_mbox; 
 | 
    for (i = 0; i < num; i++) { 
 | 
        chunk_size = (reg_size > mlxsw_i2c->block_size) ? 
 | 
                 mlxsw_i2c->block_size : reg_size; 
 | 
        read_tran[1].len = chunk_size; 
 | 
        mlxsw_i2c_set_slave_addr(tran_buf, off); 
 | 
  
 | 
        j = 0; 
 | 
        end = jiffies + timeout; 
 | 
        do { 
 | 
            err = i2c_transfer(client->adapter, read_tran, 
 | 
                       ARRAY_SIZE(read_tran)); 
 | 
            if (err == ARRAY_SIZE(read_tran)) 
 | 
                break; 
 | 
  
 | 
            cond_resched(); 
 | 
        } while ((time_before(jiffies, end)) || 
 | 
             (j++ < MLXSW_I2C_RETRY)); 
 | 
  
 | 
        if (err != ARRAY_SIZE(read_tran)) { 
 | 
            if (!err) 
 | 
                err = -EIO; 
 | 
  
 | 
            goto cmd_fail; 
 | 
        } 
 | 
  
 | 
        off += chunk_size; 
 | 
        reg_size -= chunk_size; 
 | 
        read_tran[1].buf += chunk_size; 
 | 
    } 
 | 
  
 | 
    mutex_unlock(&mlxsw_i2c->cmd.lock); 
 | 
  
 | 
    return 0; 
 | 
  
 | 
cmd_fail: 
 | 
    mutex_unlock(&mlxsw_i2c->cmd.lock); 
 | 
    return err; 
 | 
} 
 | 
  
 | 
static int mlxsw_i2c_cmd_exec(void *bus_priv, u16 opcode, u8 opcode_mod, 
 | 
                  u32 in_mod, bool out_mbox_direct, 
 | 
                  char *in_mbox, size_t in_mbox_size, 
 | 
                  char *out_mbox, size_t out_mbox_size, 
 | 
                  u8 *status) 
 | 
{ 
 | 
    struct mlxsw_i2c *mlxsw_i2c = bus_priv; 
 | 
  
 | 
    return mlxsw_i2c_cmd(mlxsw_i2c->dev, opcode, in_mod, in_mbox_size, 
 | 
                 in_mbox, out_mbox_size, out_mbox, status); 
 | 
} 
 | 
  
 | 
static bool mlxsw_i2c_skb_transmit_busy(void *bus_priv, 
 | 
                    const struct mlxsw_tx_info *tx_info) 
 | 
{ 
 | 
    return false; 
 | 
} 
 | 
  
 | 
static int mlxsw_i2c_skb_transmit(void *bus_priv, struct sk_buff *skb, 
 | 
                  const struct mlxsw_tx_info *tx_info) 
 | 
{ 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int 
 | 
mlxsw_i2c_init(void *bus_priv, struct mlxsw_core *mlxsw_core, 
 | 
           const struct mlxsw_config_profile *profile, 
 | 
           struct mlxsw_res *res) 
 | 
{ 
 | 
    struct mlxsw_i2c *mlxsw_i2c = bus_priv; 
 | 
    char *mbox; 
 | 
    int err; 
 | 
  
 | 
    mlxsw_i2c->core = mlxsw_core; 
 | 
  
 | 
    mbox = mlxsw_cmd_mbox_alloc(); 
 | 
    if (!mbox) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    err = mlxsw_cmd_query_fw(mlxsw_core, mbox); 
 | 
    if (err) 
 | 
        goto mbox_put; 
 | 
  
 | 
    mlxsw_i2c->bus_info.fw_rev.major = 
 | 
        mlxsw_cmd_mbox_query_fw_fw_rev_major_get(mbox); 
 | 
    mlxsw_i2c->bus_info.fw_rev.minor = 
 | 
        mlxsw_cmd_mbox_query_fw_fw_rev_minor_get(mbox); 
 | 
    mlxsw_i2c->bus_info.fw_rev.subminor = 
 | 
        mlxsw_cmd_mbox_query_fw_fw_rev_subminor_get(mbox); 
 | 
  
 | 
    err = mlxsw_core_resources_query(mlxsw_core, mbox, res); 
 | 
  
 | 
mbox_put: 
 | 
    mlxsw_cmd_mbox_free(mbox); 
 | 
    return err; 
 | 
} 
 | 
  
 | 
static void mlxsw_i2c_fini(void *bus_priv) 
 | 
{ 
 | 
    struct mlxsw_i2c *mlxsw_i2c = bus_priv; 
 | 
  
 | 
    mlxsw_i2c->core = NULL; 
 | 
} 
 | 
  
 | 
static const struct mlxsw_bus mlxsw_i2c_bus = { 
 | 
    .kind            = "i2c", 
 | 
    .init            = mlxsw_i2c_init, 
 | 
    .fini            = mlxsw_i2c_fini, 
 | 
    .skb_transmit_busy    = mlxsw_i2c_skb_transmit_busy, 
 | 
    .skb_transmit        = mlxsw_i2c_skb_transmit, 
 | 
    .cmd_exec        = mlxsw_i2c_cmd_exec, 
 | 
}; 
 | 
  
 | 
static int mlxsw_i2c_probe(struct i2c_client *client, 
 | 
               const struct i2c_device_id *id) 
 | 
{ 
 | 
    const struct i2c_adapter_quirks *quirks = client->adapter->quirks; 
 | 
    struct mlxsw_i2c *mlxsw_i2c; 
 | 
    u8 status; 
 | 
    int err; 
 | 
  
 | 
    mlxsw_i2c = devm_kzalloc(&client->dev, sizeof(*mlxsw_i2c), GFP_KERNEL); 
 | 
    if (!mlxsw_i2c) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    if (quirks) { 
 | 
        if ((quirks->max_read_len && 
 | 
             quirks->max_read_len < MLXSW_I2C_BLK_DEF) || 
 | 
            (quirks->max_write_len && 
 | 
             quirks->max_write_len < MLXSW_I2C_BLK_DEF)) { 
 | 
            dev_err(&client->dev, "Insufficient transaction buffer length\n"); 
 | 
            return -EOPNOTSUPP; 
 | 
        } 
 | 
  
 | 
        mlxsw_i2c->block_size = max_t(u16, MLXSW_I2C_BLK_DEF, 
 | 
                          min_t(u16, quirks->max_read_len, 
 | 
                            quirks->max_write_len)); 
 | 
    } else { 
 | 
        mlxsw_i2c->block_size = MLXSW_I2C_BLK_DEF; 
 | 
    } 
 | 
  
 | 
    i2c_set_clientdata(client, mlxsw_i2c); 
 | 
    mutex_init(&mlxsw_i2c->cmd.lock); 
 | 
  
 | 
    /* In order to use mailboxes through the i2c, special area is reserved 
 | 
     * on the i2c address space that can be used for input and output 
 | 
     * mailboxes. Such mailboxes are called local mailboxes. When using a 
 | 
     * local mailbox, software should specify 0 as the Input/Output 
 | 
     * parameters. The location of the Local Mailbox addresses on the i2c 
 | 
     * space can be retrieved through the QUERY_FW command. 
 | 
     * For this purpose QUERY_FW is to be issued with opcode modifier equal 
 | 
     * 0x01. For such command the output parameter is an immediate value. 
 | 
     * Here QUERY_FW command is invoked for ASIC probing and for getting 
 | 
     * local mailboxes addresses from immedate output parameters. 
 | 
     */ 
 | 
  
 | 
    /* Prepare and write out Command Interface Register for transaction */ 
 | 
    err = mlxsw_i2c_write_cmd(client, mlxsw_i2c, 1); 
 | 
    if (err) { 
 | 
        dev_err(&client->dev, "Could not start transaction"); 
 | 
        goto errout; 
 | 
    } 
 | 
  
 | 
    /* Wait until go bit is cleared. */ 
 | 
    err = mlxsw_i2c_wait_go_bit(client, mlxsw_i2c, &status); 
 | 
    if (err) { 
 | 
        dev_err(&client->dev, "HW semaphore is not released"); 
 | 
        goto errout; 
 | 
    } 
 | 
  
 | 
    /* Validate transaction completion status. */ 
 | 
    if (status) { 
 | 
        dev_err(&client->dev, "Bad transaction completion status %x\n", 
 | 
            status); 
 | 
        err = -EIO; 
 | 
        goto errout; 
 | 
    } 
 | 
  
 | 
    /* Get mailbox offsets. */ 
 | 
    err = mlxsw_i2c_get_mbox(client, mlxsw_i2c); 
 | 
    if (err < 0) { 
 | 
        dev_err(&client->dev, "Fail to get mailboxes\n"); 
 | 
        goto errout; 
 | 
    } 
 | 
  
 | 
    dev_info(&client->dev, "%s mb size=%x off=0x%08x out mb size=%x off=0x%08x\n", 
 | 
         id->name, mlxsw_i2c->cmd.mb_size_in, 
 | 
         mlxsw_i2c->cmd.mb_off_in, mlxsw_i2c->cmd.mb_size_out, 
 | 
         mlxsw_i2c->cmd.mb_off_out); 
 | 
  
 | 
    /* Register device bus. */ 
 | 
    mlxsw_i2c->bus_info.device_kind = id->name; 
 | 
    mlxsw_i2c->bus_info.device_name = client->name; 
 | 
    mlxsw_i2c->bus_info.dev = &client->dev; 
 | 
    mlxsw_i2c->bus_info.low_frequency = true; 
 | 
    mlxsw_i2c->dev = &client->dev; 
 | 
  
 | 
    err = mlxsw_core_bus_device_register(&mlxsw_i2c->bus_info, 
 | 
                         &mlxsw_i2c_bus, mlxsw_i2c, false, 
 | 
                         NULL, NULL); 
 | 
    if (err) { 
 | 
        dev_err(&client->dev, "Fail to register core bus\n"); 
 | 
        return err; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
  
 | 
errout: 
 | 
    mutex_destroy(&mlxsw_i2c->cmd.lock); 
 | 
    i2c_set_clientdata(client, NULL); 
 | 
  
 | 
    return err; 
 | 
} 
 | 
  
 | 
static int mlxsw_i2c_remove(struct i2c_client *client) 
 | 
{ 
 | 
    struct mlxsw_i2c *mlxsw_i2c = i2c_get_clientdata(client); 
 | 
  
 | 
    mlxsw_core_bus_device_unregister(mlxsw_i2c->core, false); 
 | 
    mutex_destroy(&mlxsw_i2c->cmd.lock); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int mlxsw_i2c_driver_register(struct i2c_driver *i2c_driver) 
 | 
{ 
 | 
    i2c_driver->probe = mlxsw_i2c_probe; 
 | 
    i2c_driver->remove = mlxsw_i2c_remove; 
 | 
    return i2c_add_driver(i2c_driver); 
 | 
} 
 | 
EXPORT_SYMBOL(mlxsw_i2c_driver_register); 
 | 
  
 | 
void mlxsw_i2c_driver_unregister(struct i2c_driver *i2c_driver) 
 | 
{ 
 | 
    i2c_del_driver(i2c_driver); 
 | 
} 
 | 
EXPORT_SYMBOL(mlxsw_i2c_driver_unregister); 
 | 
  
 | 
MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); 
 | 
MODULE_DESCRIPTION("Mellanox switch I2C interface driver"); 
 | 
MODULE_LICENSE("Dual BSD/GPL"); 
 |