588 lines
14 KiB
C
588 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Maxim Dual GMSL Deserializer I2C read/write driver
|
|
*
|
|
* Copyright (C) 2023 Rockchip Electronics Co., Ltd.
|
|
*
|
|
* Author: Cai Wenzhong <cwz@rock-chips.com>
|
|
*
|
|
*/
|
|
#include <linux/i2c.h>
|
|
#include <linux/i2c-dev.h>
|
|
#include <linux/i2c-mux.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include "maxim2c_api.h"
|
|
|
|
/* Write registers up to 4 at a time */
|
|
int maxim2c_i2c_write(struct i2c_client *client,
|
|
u16 reg_addr, u16 reg_len, u32 val_len, u32 reg_val)
|
|
{
|
|
u32 buf_i, val_i;
|
|
u8 buf[6];
|
|
u8 *val_p;
|
|
__be32 val_be;
|
|
|
|
dev_info(&client->dev, "i2c addr(0x%02x) write: 0x%04x (%d) = 0x%08x (%d)\n",
|
|
client->addr, reg_addr, reg_len, reg_val, val_len);
|
|
|
|
if (val_len > 4)
|
|
return -EINVAL;
|
|
|
|
if (reg_len == 2) {
|
|
buf[0] = reg_addr >> 8;
|
|
buf[1] = reg_addr & 0xff;
|
|
|
|
buf_i = 2;
|
|
} else {
|
|
buf[0] = reg_addr & 0xff;
|
|
|
|
buf_i = 1;
|
|
}
|
|
|
|
val_be = cpu_to_be32(reg_val);
|
|
val_p = (u8 *)&val_be;
|
|
val_i = 4 - val_len;
|
|
|
|
while (val_i < 4)
|
|
buf[buf_i++] = val_p[val_i++];
|
|
|
|
if (i2c_master_send(client, buf, (val_len + reg_len)) != (val_len + reg_len)) {
|
|
dev_err(&client->dev,
|
|
"%s: writing register 0x%04x from 0x%02x failed\n",
|
|
__func__, reg_addr, client->addr);
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_write);
|
|
|
|
/* Read registers up to 4 at a time */
|
|
int maxim2c_i2c_read(struct i2c_client *client,
|
|
u16 reg_addr, u16 reg_len, u32 val_len, u32 *reg_val)
|
|
{
|
|
struct i2c_msg msgs[2];
|
|
u8 *data_be_p;
|
|
__be32 data_be = 0;
|
|
__be16 reg_addr_be = cpu_to_be16(reg_addr);
|
|
u8 *reg_be_p;
|
|
int ret;
|
|
|
|
if (val_len > 4 || !val_len)
|
|
return -EINVAL;
|
|
|
|
data_be_p = (u8 *)&data_be;
|
|
reg_be_p = (u8 *)®_addr_be;
|
|
|
|
/* Write register address */
|
|
msgs[0].addr = client->addr;
|
|
msgs[0].flags = 0;
|
|
msgs[0].len = reg_len;
|
|
msgs[0].buf = ®_be_p[2 - reg_len];
|
|
|
|
/* Read data from register */
|
|
msgs[1].addr = client->addr;
|
|
msgs[1].flags = I2C_M_RD;
|
|
msgs[1].len = val_len;
|
|
msgs[1].buf = &data_be_p[4 - val_len];
|
|
|
|
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
|
|
if (ret != ARRAY_SIZE(msgs)) {
|
|
dev_err(&client->dev,
|
|
"%s: reading register 0x%04x from 0x%02x failed\n",
|
|
__func__, reg_addr, client->addr);
|
|
return -EIO;
|
|
}
|
|
|
|
*reg_val = be32_to_cpu(data_be);
|
|
|
|
#if 0
|
|
dev_info(&client->dev, "i2c addr(0x%02x) read: 0x%04x (%d) = 0x%08x (%d)\n",
|
|
client->addr, reg_addr, reg_len, *reg_val, val_len);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_read);
|
|
|
|
/* Update registers up to 4 at a time */
|
|
int maxim2c_i2c_update(struct i2c_client *client,
|
|
u16 reg_addr, u16 reg_len, u32 val_len, u32 val_mask, u32 reg_val)
|
|
{
|
|
u32 value;
|
|
int ret;
|
|
|
|
ret = maxim2c_i2c_read(client, reg_addr, reg_len, val_len, &value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
value &= ~val_mask;
|
|
value |= (reg_val & val_mask);
|
|
ret = maxim2c_i2c_write(client, reg_addr, reg_len, val_len, value);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_update);
|
|
|
|
int maxim2c_i2c_write_reg(struct i2c_client *client,
|
|
u16 reg_addr, u8 reg_val)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = maxim2c_i2c_write(client,
|
|
reg_addr, MAXIM2C_I2C_REG_ADDR_16BITS,
|
|
MAXIM2C_I2C_REG_VALUE_08BITS, reg_val);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_write_reg);
|
|
|
|
int maxim2c_i2c_read_reg(struct i2c_client *client,
|
|
u16 reg_addr, u8 *reg_val)
|
|
{
|
|
int ret = 0;
|
|
u32 value = 0;
|
|
u8 *value_be_p = (u8 *)&value;
|
|
|
|
ret = maxim2c_i2c_read(client,
|
|
reg_addr, MAXIM2C_I2C_REG_ADDR_16BITS,
|
|
MAXIM2C_I2C_REG_VALUE_08BITS, &value);
|
|
|
|
*reg_val = *value_be_p;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_read_reg);
|
|
|
|
int maxim2c_i2c_update_reg(struct i2c_client *client,
|
|
u16 reg_addr, u8 val_mask, u8 reg_val)
|
|
{
|
|
u8 value;
|
|
int ret;
|
|
|
|
ret = maxim2c_i2c_read_reg(client, reg_addr, &value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
value &= ~val_mask;
|
|
value |= (reg_val & val_mask);
|
|
ret = maxim2c_i2c_write_reg(client, reg_addr, value);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_update_reg);
|
|
|
|
int maxim2c_i2c_write_array(struct i2c_client *client,
|
|
const struct maxim2c_i2c_regval *regs)
|
|
{
|
|
u32 i = 0;
|
|
int ret = 0;
|
|
|
|
for (i = 0; (ret == 0) && (regs[i].reg_addr != MAXIM2C_REG_NULL); i++) {
|
|
if (regs[i].val_mask != 0)
|
|
ret = maxim2c_i2c_update(client,
|
|
regs[i].reg_addr, regs[i].reg_len,
|
|
regs[i].val_len, regs[i].val_mask, regs[i].reg_val);
|
|
else
|
|
ret = maxim2c_i2c_write(client,
|
|
regs[i].reg_addr, regs[i].reg_len,
|
|
regs[i].val_len, regs[i].reg_val);
|
|
|
|
if (regs[i].delay != 0)
|
|
usleep_range(regs[i].delay * 1000, regs[i].delay * 1000 + 100);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_write_array);
|
|
|
|
static int maxim2c_i2c_parse_init_seq(struct device *dev,
|
|
const u8 *seq_data, int data_len, struct maxim2c_i2c_init_seq *init_seq)
|
|
{
|
|
struct maxim2c_i2c_regval *reg_val = NULL;
|
|
u8 *data_buf = NULL, *d8 = NULL;
|
|
u32 i = 0;
|
|
|
|
if ((seq_data == NULL) || (init_seq == NULL)) {
|
|
dev_err(dev, "%s: input parameter = NULL\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((init_seq->seq_item_size == 0)
|
|
|| (data_len == 0)
|
|
|| (init_seq->reg_len == 0)
|
|
|| (init_seq->val_len == 0)) {
|
|
dev_err(dev, "%s: input parameter size zero\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
// data_len = seq_item_size * N
|
|
if (data_len % init_seq->seq_item_size) {
|
|
dev_err(dev, "%s: data_len or seq_item_size error\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
// seq_item_size = reg_len + val_len * 2 + 1
|
|
if (init_seq->seq_item_size !=
|
|
(init_seq->reg_len + init_seq->val_len * 2 + 1)) {
|
|
dev_err(dev, "%s: seq_item_size or reg_len or val_len error\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
data_buf = devm_kmemdup(dev, seq_data, data_len, GFP_KERNEL);
|
|
if (!data_buf) {
|
|
dev_err(dev, "%s data buf error\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
d8 = data_buf;
|
|
|
|
init_seq->reg_seq_size = data_len / init_seq->seq_item_size;
|
|
init_seq->reg_seq_size += 1; // add 1 for end register setting
|
|
|
|
init_seq->reg_init_seq = devm_kcalloc(dev, init_seq->reg_seq_size,
|
|
sizeof(struct maxim2c_i2c_regval), GFP_KERNEL);
|
|
if (!init_seq->reg_init_seq) {
|
|
dev_err(dev, "%s init seq buffer error\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < init_seq->reg_seq_size - 1; i++) {
|
|
reg_val = &init_seq->reg_init_seq[i];
|
|
|
|
reg_val->reg_len = init_seq->reg_len;
|
|
reg_val->val_len = init_seq->val_len;
|
|
|
|
reg_val->reg_addr = 0;
|
|
switch (init_seq->reg_len) {
|
|
case 4:
|
|
reg_val->reg_addr |= (*d8 << 24);
|
|
d8 += 1;
|
|
fallthrough;
|
|
case 3:
|
|
reg_val->reg_addr |= (*d8 << 16);
|
|
d8 += 1;
|
|
fallthrough;
|
|
case 2:
|
|
reg_val->reg_addr |= (*d8 << 8);
|
|
d8 += 1;
|
|
fallthrough;
|
|
case 1:
|
|
reg_val->reg_addr |= (*d8 << 0);
|
|
d8 += 1;
|
|
break;
|
|
}
|
|
|
|
reg_val->reg_val = 0;
|
|
switch (init_seq->val_len) {
|
|
case 4:
|
|
reg_val->reg_val |= (*d8 << 24);
|
|
d8 += 1;
|
|
fallthrough;
|
|
case 3:
|
|
reg_val->reg_val |= (*d8 << 16);
|
|
d8 += 1;
|
|
fallthrough;
|
|
case 2:
|
|
reg_val->reg_val |= (*d8 << 8);
|
|
d8 += 1;
|
|
fallthrough;
|
|
case 1:
|
|
reg_val->reg_val |= (*d8 << 0);
|
|
d8 += 1;
|
|
break;
|
|
}
|
|
|
|
reg_val->val_mask = 0;
|
|
switch (init_seq->val_len) {
|
|
case 4:
|
|
reg_val->val_mask |= (*d8 << 24);
|
|
d8 += 1;
|
|
fallthrough;
|
|
case 3:
|
|
reg_val->val_mask |= (*d8 << 16);
|
|
d8 += 1;
|
|
fallthrough;
|
|
case 2:
|
|
reg_val->val_mask |= (*d8 << 8);
|
|
d8 += 1;
|
|
fallthrough;
|
|
case 1:
|
|
reg_val->val_mask |= (*d8 << 0);
|
|
d8 += 1;
|
|
break;
|
|
}
|
|
|
|
reg_val->delay = *d8;
|
|
d8 += 1;
|
|
}
|
|
|
|
// End register setting
|
|
init_seq->reg_init_seq[init_seq->reg_seq_size - 1].reg_len = init_seq->reg_len;
|
|
init_seq->reg_init_seq[init_seq->reg_seq_size - 1].reg_addr = MAXIM2C_REG_NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int maxim2c_i2c_load_init_seq(struct device *dev,
|
|
struct device_node *node, struct maxim2c_i2c_init_seq *init_seq)
|
|
{
|
|
const void *init_seq_data = NULL;
|
|
u32 seq_data_len = 0, value = 0;
|
|
int ret = 0;
|
|
|
|
if ((node == NULL) || (init_seq == NULL)) {
|
|
dev_err(dev, "%s input parameter error\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
init_seq->reg_init_seq = NULL;
|
|
init_seq->reg_seq_size = 0;
|
|
|
|
if (!of_device_is_available(node)) {
|
|
dev_info(dev, "%pOF is disabled\n", node);
|
|
|
|
return 0;
|
|
}
|
|
|
|
init_seq_data = of_get_property(node, "init-sequence", &seq_data_len);
|
|
if (!init_seq_data) {
|
|
dev_err(dev, "failed to get property init-sequence\n");
|
|
return -EINVAL;
|
|
}
|
|
if (seq_data_len == 0) {
|
|
dev_err(dev, "init-sequence date is empty\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "seq-item-size", &value);
|
|
if (ret) {
|
|
dev_err(dev, "failed to get property seq-item-size\n");
|
|
return -EINVAL;
|
|
} else {
|
|
dev_info(dev, "seq-item-size property: %d", value);
|
|
init_seq->seq_item_size = value;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "reg-addr-len", &value);
|
|
if (ret) {
|
|
dev_err(dev, "failed to get property reg-addr-len\n");
|
|
return -EINVAL;
|
|
} else {
|
|
dev_info(dev, "reg-addr-len property: %d", value);
|
|
init_seq->reg_len = value;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "reg-val-len", &value);
|
|
if (ret) {
|
|
dev_err(dev, "failed to get property reg-val-len\n");
|
|
return -EINVAL;
|
|
} else {
|
|
dev_info(dev, "reg-val-len property: %d", value);
|
|
init_seq->val_len = value;
|
|
}
|
|
|
|
ret = maxim2c_i2c_parse_init_seq(dev,
|
|
init_seq_data, seq_data_len, init_seq);
|
|
if (ret) {
|
|
dev_err(dev, "failed to parse init-sequence\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_load_init_seq);
|
|
|
|
int maxim2c_i2c_run_init_seq(struct i2c_client *client,
|
|
struct maxim2c_i2c_init_seq *init_seq)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (init_seq == NULL || init_seq->reg_init_seq == NULL)
|
|
return 0;
|
|
|
|
ret = maxim2c_i2c_write_array(client,
|
|
init_seq->reg_init_seq);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_run_init_seq);
|
|
|
|
static int __maybe_unused maxim2c_i2c_mux_select(struct i2c_mux_core *muxc, u32 chan)
|
|
{
|
|
maxim2c_t *maxim2c = i2c_mux_priv(muxc);
|
|
struct i2c_client *client = maxim2c->client;
|
|
struct device *dev = &client->dev;
|
|
int ret = 0;
|
|
|
|
dev_dbg(dev, "maxim2c i2c mux select chan = %d\n", chan);
|
|
|
|
/* Channel select is disabled when configured in the disabled state. */
|
|
if (maxim2c->i2c_mux.mux_disable) {
|
|
dev_err(dev, "maxim2c i2c mux is disabled, select error\n");
|
|
return 0;
|
|
}
|
|
|
|
if (maxim2c->i2c_mux.mux_channel == chan)
|
|
return 0;
|
|
|
|
maxim2c->i2c_mux.mux_channel = chan;
|
|
|
|
ret = maxim2c_link_select_remote_control(maxim2c, BIT(chan));
|
|
if (ret) {
|
|
dev_err(dev, "maxim2c link select remote control error\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused maxim2c_i2c_mux_deselect(struct i2c_mux_core *muxc, u32 chan)
|
|
{
|
|
maxim2c_t *maxim2c = i2c_mux_priv(muxc);
|
|
struct i2c_client *client = maxim2c->client;
|
|
struct device *dev = &client->dev;
|
|
int ret = 0;
|
|
|
|
dev_dbg(dev, "maxim2c i2c mux deselect chan = %d\n", chan);
|
|
|
|
/* Channel deselect is disabled when configured in the disabled state. */
|
|
if (maxim2c->i2c_mux.mux_disable) {
|
|
dev_err(dev, "maxim2c i2c mux is disabled, deselect error\n");
|
|
return 0;
|
|
}
|
|
|
|
ret = maxim2c_link_select_remote_control(maxim2c, 0);
|
|
if (ret) {
|
|
dev_err(dev, "maxim2c link select remote control error\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int maxim2c_i2c_mux_disable(maxim2c_t *maxim2c)
|
|
{
|
|
struct device *dev = &maxim2c->client->dev;
|
|
int ret = 0;
|
|
|
|
dev_info(dev, "maxim2c i2c mux disable\n");
|
|
|
|
ret = maxim2c_link_select_remote_control(maxim2c, 0xff);
|
|
if (ret) {
|
|
dev_err(dev, "maxim2c link select remote control error\n");
|
|
return ret;
|
|
}
|
|
|
|
maxim2c->i2c_mux.mux_disable = true;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_mux_disable);
|
|
|
|
int maxim2c_i2c_mux_enable(maxim2c_t *maxim2c, u8 def_mask)
|
|
{
|
|
struct device *dev = &maxim2c->client->dev;
|
|
int ret = 0;
|
|
|
|
dev_info(dev, "maxim2c i2c mux enable, mask = 0x%02x\n", def_mask);
|
|
|
|
ret = maxim2c_link_select_remote_control(maxim2c, def_mask);
|
|
if (ret) {
|
|
dev_err(dev, "maxim2c link select remote control error\n");
|
|
return ret;
|
|
}
|
|
|
|
maxim2c->i2c_mux.mux_disable = false;
|
|
maxim2c->i2c_mux.mux_channel = -1;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_mux_enable);
|
|
|
|
static u32 maxim2c_i2c_mux_mask(maxim2c_t *maxim2c)
|
|
{
|
|
struct device *dev = &maxim2c->client->dev;
|
|
struct device_node *i2c_mux;
|
|
struct device_node *node = NULL;
|
|
u32 i2c_mux_mask = 0;
|
|
|
|
/* Balance the of_node_put() performed by of_find_node_by_name(). */
|
|
of_node_get(dev->of_node);
|
|
i2c_mux = of_find_node_by_name(dev->of_node, "i2c-mux");
|
|
if (!i2c_mux) {
|
|
dev_err(dev, "Failed to find i2c-mux node\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Identify which i2c-mux channels are enabled */
|
|
for_each_child_of_node(i2c_mux, node) {
|
|
u32 id = 0;
|
|
|
|
of_property_read_u32(node, "reg", &id);
|
|
if (id >= MAXIM2C_LINK_ID_MAX)
|
|
continue;
|
|
|
|
if (!of_device_is_available(node)) {
|
|
dev_dbg(dev, "Skipping disabled I2C bus port %u\n", id);
|
|
continue;
|
|
}
|
|
|
|
i2c_mux_mask |= BIT(id);
|
|
}
|
|
of_node_put(node);
|
|
of_node_put(i2c_mux);
|
|
|
|
return i2c_mux_mask;
|
|
}
|
|
|
|
int maxim2c_i2c_mux_init(maxim2c_t *maxim2c)
|
|
{
|
|
struct i2c_client *client = maxim2c->client;
|
|
struct device *dev = &client->dev;
|
|
u32 i2c_mux_mask = 0;
|
|
int i = 0;
|
|
int ret = 0;
|
|
|
|
dev_info(dev, "maxim2c i2c mux init\n");
|
|
|
|
if (!i2c_check_functionality(client->adapter,
|
|
I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
|
|
return -ENODEV;
|
|
|
|
maxim2c->i2c_mux.muxc = i2c_mux_alloc(client->adapter, dev,
|
|
MAXIM2C_LINK_ID_MAX, 0, I2C_MUX_LOCKED,
|
|
maxim2c_i2c_mux_select, NULL);
|
|
if (!maxim2c->i2c_mux.muxc)
|
|
return -ENOMEM;
|
|
maxim2c->i2c_mux.muxc->priv = maxim2c;
|
|
|
|
for (i = 0; i < MAXIM2C_LINK_ID_MAX; i++) {
|
|
ret = i2c_mux_add_adapter(maxim2c->i2c_mux.muxc, 0, i, 0);
|
|
if (ret) {
|
|
i2c_mux_del_adapters(maxim2c->i2c_mux.muxc);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
i2c_mux_mask = maxim2c_i2c_mux_mask(maxim2c);
|
|
maxim2c->i2c_mux.i2c_mux_mask = i2c_mux_mask;
|
|
dev_info(dev, "maxim2c i2c mux mask = 0x%x\n", i2c_mux_mask);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_mux_init);
|
|
|
|
int maxim2c_i2c_mux_deinit(maxim2c_t *maxim2c)
|
|
{
|
|
if (maxim2c->i2c_mux.muxc)
|
|
i2c_mux_del_adapters(maxim2c->i2c_mux.muxc);
|
|
|
|
maxim2c->i2c_mux.i2c_mux_mask = 0;
|
|
maxim2c->i2c_mux.mux_disable = false;
|
|
maxim2c->i2c_mux.mux_channel = -1;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(maxim2c_i2c_mux_deinit);
|