drivers: media: CDI: Add IO Expander support

Add IO Expander support in CDI Dev to control the FRSYNC multiplexer
with the speicific platform

Bug 3582660

Change-Id: I3ee376e7a0bccabcfa16efb228879d4df0d65748
Signed-off-by: Junghyun Kim <juskim@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvidia/+/2727256
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2824551
Reviewed-by: Frank Chen <frankc@nvidia.com>
Reviewed-by: Shiva Dubey <sdubey@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
This commit is contained in:
Junghyun Kim
2022-06-09 07:10:42 -07:00
committed by mobile promotions
parent eb1d8b3b58
commit 9269dac3ec
6 changed files with 329 additions and 109 deletions

View File

@@ -5,6 +5,7 @@
#define __CDI_DEV_PRIV_H__
#include <linux/cdev.h>
#include "cdi-tca-priv.h"
#define MAX_POWER_LINKS_PER_BLOCK (4U)
@@ -32,6 +33,8 @@ struct cdi_dev_info {
u8 cam_pwr_method;
u8 cam_pwr_i2c_addr;
struct max20087_priv max20087;
struct tca9539_priv tca9539;
u8 cim_ver; /* 1 - P3714 A01, 2 - P3714 A02/A03 */
};
int cdi_dev_raw_rd(struct cdi_dev_info *info, unsigned int offset,

View File

@@ -6,6 +6,7 @@
#include <linux/cdev.h>
#include <media/cdi-mgr.h>
#include "cdi-tca-priv.h"
#define CDI_MGR_STOP_GPIO_INTR_EVENT_WAIT (~(0u))
#define CDI_MGR_TCA9539_REGISTER_COUNT (8)
@@ -24,17 +25,6 @@ struct cam_gpio_config {
int gpio_intr_irq;
};
struct tca9539_priv {
struct i2c_adapter *adap;
int bus;
u32 addr;
u32 reg_len;
u32 dat_len;
u8 init_val[12];
u32 power_port;
bool enable;
};
struct cdi_mgr_priv {
struct device *pdev; /* parent device */
struct device *dev; /* this device */
@@ -65,7 +55,7 @@ struct cdi_mgr_priv {
uint32_t gpio_count;
uint32_t err_irq_recvd_status_mask;
bool stop_err_irq_wait;
u8 cim_ver; /* 1 - P3714 A01, 2 - P3714 A02 */
u8 cim_ver; /* 1 - P3714 A01, 2 - P3714 A02/A03 */
u32 cim_frsync[3]; /* FRSYNC source selection for each muxer */
u8 pre_suspend_tca9539_regvals[CDI_MGR_TCA9539_REGISTER_COUNT];
};

View File

@@ -0,0 +1,122 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#ifndef __CDI_TCA9539_PRIV_H__
#define __CDI_TCA9539_PRIV_H__
/* i2c payload size is only 12 bit */
#define MAX_MSG_SIZE (0xFFF - 1)
struct tca9539_priv {
struct i2c_adapter *adap;
int bus;
u32 addr;
u32 reg_len;
u32 dat_len;
u8 init_val[12];
u32 power_port;
bool enable;
};
static int tca9539_raw_wr(
struct device *dev, struct tca9539_priv *tca9539, unsigned int offset, u8 val)
{
int ret = -ENODEV;
u8 *buf_start = NULL;
struct i2c_msg *i2cmsg;
unsigned int num_msgs = 0, total_size, i;
u8 data[3];
size_t size = 1;
dev_dbg(dev, "%s\n", __func__);
if (tca9539->reg_len == 2) {
data[0] = (u8)((offset >> 8) & 0xff);
data[1] = (u8)(offset & 0xff);
data[2] = val;
size += 2;
} else if (tca9539->reg_len == 1) {
data[0] = (u8)(offset & 0xff);
data[1] = val;
size += 1;
} else if ((tca9539->reg_len == 0) ||
(tca9539->reg_len > 3)) {
return 0;
}
num_msgs = size / MAX_MSG_SIZE;
num_msgs += (size % MAX_MSG_SIZE) ? 1 : 0;
i2cmsg = kzalloc((sizeof(struct i2c_msg)*num_msgs), GFP_KERNEL);
if (!i2cmsg) {
return -ENOMEM;
}
buf_start = data;
total_size = size;
dev_dbg(dev, "%s: num_msgs: %d\n", __func__, num_msgs);
for (i = 0; i < num_msgs; i++) {
i2cmsg[i].addr = tca9539->addr;
i2cmsg[i].buf = (__u8 *)buf_start;
if (i > 0)
i2cmsg[i].flags = I2C_M_NOSTART;
else
i2cmsg[i].flags = 0;
if (total_size > MAX_MSG_SIZE) {
i2cmsg[i].len = MAX_MSG_SIZE;
buf_start += MAX_MSG_SIZE;
total_size -= MAX_MSG_SIZE;
} else {
i2cmsg[i].len = total_size;
}
dev_dbg(dev, "%s: addr:%x buf:%p, flags:%u len:%u\n",
__func__, i2cmsg[i].addr, (void *)i2cmsg[i].buf,
i2cmsg[i].flags, i2cmsg[i].len);
}
ret = i2c_transfer(tca9539->adap, i2cmsg, num_msgs);
if (ret > 0)
ret = 0;
kfree(i2cmsg);
return ret;
}
static int tca9539_raw_rd(
struct device *dev, struct tca9539_priv *tca9539, unsigned int offset, u8 *val)
{
int ret = -ENODEV;
u8 data[2];
size_t size = 1;
struct i2c_msg i2cmsg[2];
dev_dbg(dev, "%s\n", __func__);
if (tca9539->reg_len == 2) {
data[0] = (u8)((offset >> 8) & 0xff);
data[1] = (u8)(offset & 0xff);
} else if (tca9539->reg_len == 1)
data[0] = (u8)(offset & 0xff);
i2cmsg[0].addr = tca9539->addr;
i2cmsg[0].len = tca9539->reg_len;
i2cmsg[0].buf = (__u8 *)data;
i2cmsg[0].flags = I2C_M_NOSTART;
i2cmsg[1].addr = tca9539->addr;
i2cmsg[1].flags = I2C_M_RD;
i2cmsg[1].len = size;
i2cmsg[1].buf = (__u8 *)val;
ret = i2c_transfer(tca9539->adap, i2cmsg, 2);
if (ret > 0)
ret = 0;
return ret;
}
#endif /* __CDI_TCA9539_PRIV_H__ */

View File

@@ -394,6 +394,81 @@ static int cdi_dev_get_pwr_info(
return 0;
}
static int tca9539_wr(
struct cdi_dev_info *info, unsigned int offset, u8 val)
{
int ret = -ENODEV;
dev_dbg(info->dev, "%s\n", __func__);
mutex_lock(&info->mutex);
ret = tca9539_raw_wr(info->dev, &info->tca9539, offset, val);
mutex_unlock(&info->mutex);
return ret;
}
static int tca9539_rd(
struct cdi_dev_info *info, unsigned int offset, u8 *val)
{
int ret = -ENODEV;
dev_dbg(info->dev, "%s\n", __func__);
mutex_lock(&info->mutex);
ret = tca9539_raw_rd(info->dev, &info->tca9539, offset, val);
mutex_unlock(&info->mutex);
return ret;
}
static int cdi_dev_set_fsync_mux(
struct cdi_dev_info *info,
void __user *arg)
{
u8 val, shift;
struct cdi_dev_fsync_mux fsync_mux;
if (copy_from_user(&fsync_mux, arg, sizeof(fsync_mux))) {
dev_err(info->dev,
"%s: failed to copy from user\n", __func__);
return -EFAULT;
}
if (info->cim_ver == 2U) {
/* P01:P00 for the camera group B. cam_grp 1.
* P03:P02 for the camera group C. cam_grp 2.
* P05:P04 for the camera group D. cam_grp 3.
*/
if ((fsync_mux.cam_grp > 0U) && (fsync_mux.cam_grp < 4U)) {
if (tca9539_rd(info, 0x02, &val) != 0)
return -EFAULT;
switch (fsync_mux.cam_grp) {
case 1U:
shift = 0U;
break;
case 2U:
shift = 2U;
break;
case 3U:
shift = 4U;
break;
default:
shift = 0U;
break;
}
val &= ~(0x3 << shift);
val |= (fsync_mux.mux_sel << shift);
if (tca9539_wr(info, 0x02, val) != 0)
return -EFAULT;
}
}
return 0;
}
static long cdi_dev_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
@@ -411,6 +486,9 @@ static long cdi_dev_ioctl(struct file *file,
case CDI_DEV_IOCTL_GET_PWR_INFO:
err = cdi_dev_get_pwr_info(info, (void __user *)arg);
break;
case CDI_DEV_IOCTL_FRSYNC_MUX:
err = cdi_dev_set_fsync_mux(info, (void __user *)arg);
break;
default:
dev_dbg(info->dev, "%s: invalid cmd %x\n", __func__, cmd);
return -EINVAL;
@@ -453,6 +531,38 @@ static const struct file_operations cdi_dev_fileops = {
.release = cdi_dev_release,
};
static void cdi_dev_get_cim_ver(struct device_node *np, struct cdi_dev_info *info)
{
int err = 0;
struct device_node *child = NULL;
struct device_node *cim = NULL;
const char *cim_ver;
child = of_get_parent(np);
if (child != NULL) {
cim = of_get_compatible_child(child,
"nvidia,cim_ver");
if (cim != NULL) {
err = of_property_read_string(cim,
"cim_ver",
&cim_ver);
if (!err) {
if (!strncmp(cim_ver,
"cim_ver_a01",
sizeof("cim_ver_a01"))) {
dev_info(info->dev,
"CIM A01\n");
info->cim_ver = 1U;
} else {
dev_info(info->dev,
"CIM A02\n");
info->cim_ver = 2U;
}
}
}
}
}
static int cdi_dev_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -460,6 +570,7 @@ static int cdi_dev_probe(struct i2c_client *client,
struct cdi_mgr_priv *cdi_mgr = NULL;
struct device *pdev;
struct device_node *child = NULL, *child_max20087 = NULL;
struct device_node *child_tca9539 = NULL;
int err;
int numLinks = 0;
int i;
@@ -483,7 +594,10 @@ static int cdi_dev_probe(struct i2c_client *client,
dev_notice(&client->dev, "%s NO platform data\n", __func__);
return -ENODEV;
}
if (info->pdata->np != NULL) {
cdi_dev_get_cim_ver(info->pdata->np, info);
child = of_get_child_by_name(info->pdata->np,
"pwr_ctrl");
if (child != NULL) {
@@ -553,6 +667,68 @@ static int cdi_dev_probe(struct i2c_client *client,
}
}
}
if (info->cim_ver == 2U) {
/* get the I/O expander information */
child_tca9539 = of_get_child_by_name(child, "tca9539");
if (child_tca9539 != NULL) {
err = of_property_read_u32(child_tca9539, "i2c-bus",
&info->tca9539.bus);
if (err) {
dev_err(info->dev,
"%s: Failed to get I2C bus number, ERROR %d\n",
__func__, err);
return -ENODEV;
}
err = of_property_read_u32(child_tca9539, "addr",
&info->tca9539.addr);
if (err || !info->tca9539.addr) {
dev_err(info->dev,
"%s: ERROR %d addr = %d\n",
__func__, err,
info->tca9539.addr);
return -ENODEV;
}
err = of_property_read_u32(child_tca9539, "reg_len",
&info->tca9539.reg_len);
if (err || !info->tca9539.reg_len) {
dev_err(info->dev,
"%s: ERROR %d reg_len = %d\n",
__func__, err,
info->tca9539.reg_len);
return -ENODEV;
}
err = of_property_read_u32(child_tca9539, "dat_len",
&info->tca9539.dat_len);
if (err || !info->tca9539.dat_len) {
dev_err(info->dev,
"%s: ERROR %d dat_len = %d\n",
__func__, err,
info->tca9539.dat_len);
return -ENODEV;
}
err = of_property_read_u32(child_tca9539->parent,
"power_port",
&info->tca9539.power_port);
if (err) {
dev_err(info->dev,
"%s: ERROR %d power_port = %d\n",
__func__, err,
info->tca9539.power_port);
return -ENODEV;
}
info->tca9539.reg_len /= 8;
info->tca9539.dat_len /= 8;
info->tca9539.enable = 1;
info->tca9539.adap = i2c_get_adapter(info->tca9539.bus);
if (!info->tca9539.adap) {
dev_err(info->dev, "%s no such i2c bus %d\n",
__func__, info->tca9539.bus);
return -ENODEV;
}
}
}
}
if (info->pdata->reg_bits)

View File

@@ -146,107 +146,30 @@ static int pwr_off_set(void *data, u64 val)
DEFINE_SIMPLE_ATTRIBUTE(pwr_off_fops, pwr_off_get, pwr_off_set, "0x%02llx\n");
static int tca9539_raw_wr(
static int tca9539_wr(
struct cdi_mgr_priv *info, unsigned int offset, u8 val)
{
int ret = -ENODEV;
u8 *buf_start = NULL;
struct i2c_msg *i2cmsg;
unsigned int num_msgs = 0, total_size, i;
u8 data[3];
size_t size = 1;
dev_dbg(info->dev, "%s\n", __func__);
mutex_lock(&info->mutex);
if (info->tca9539.reg_len == 2) {
data[0] = (u8)((offset >> 8) & 0xff);
data[1] = (u8)(offset & 0xff);
data[2] = val;
size += 2;
} else if (info->tca9539.reg_len == 1) {
data[0] = (u8)(offset & 0xff);
data[1] = val;
size += 1;
} else if ((info->tca9539.reg_len == 0) ||
(info->tca9539.reg_len > 3)) {
mutex_unlock(&info->mutex);
return 0;
}
ret = tca9539_raw_wr(info->dev, &info->tca9539, offset, val);
num_msgs = size / MAX_MSG_SIZE;
num_msgs += (size % MAX_MSG_SIZE) ? 1 : 0;
i2cmsg = kzalloc((sizeof(struct i2c_msg)*num_msgs), GFP_KERNEL);
if (!i2cmsg) {
mutex_unlock(&info->mutex);
return -ENOMEM;
}
buf_start = data;
total_size = size;
dev_dbg(info->dev, "%s: num_msgs: %d\n", __func__, num_msgs);
for (i = 0; i < num_msgs; i++) {
i2cmsg[i].addr = info->tca9539.addr;
i2cmsg[i].buf = (__u8 *)buf_start;
if (i > 0)
i2cmsg[i].flags = I2C_M_NOSTART;
else
i2cmsg[i].flags = 0;
if (total_size > MAX_MSG_SIZE) {
i2cmsg[i].len = MAX_MSG_SIZE;
buf_start += MAX_MSG_SIZE;
total_size -= MAX_MSG_SIZE;
} else {
i2cmsg[i].len = total_size;
}
dev_dbg(info->dev, "%s: addr:%x buf:%p, flags:%u len:%u\n",
__func__, i2cmsg[i].addr, (void *)i2cmsg[i].buf,
i2cmsg[i].flags, i2cmsg[i].len);
}
ret = i2c_transfer(info->tca9539.adap, i2cmsg, num_msgs);
if (ret > 0)
ret = 0;
kfree(i2cmsg);
mutex_unlock(&info->mutex);
return ret;
}
static int tca9539_raw_rd(
static int tca9539_rd(
struct cdi_mgr_priv *info, unsigned int offset, u8 *val)
{
int ret = -ENODEV;
u8 data[2];
size_t size = 1;
struct i2c_msg i2cmsg[2];
dev_dbg(info->dev, "%s\n", __func__);
mutex_lock(&info->mutex);
if (info->tca9539.reg_len == 2) {
data[0] = (u8)((offset >> 8) & 0xff);
data[1] = (u8)(offset & 0xff);
} else if (info->tca9539.reg_len == 1)
data[0] = (u8)(offset & 0xff);
ret = tca9539_raw_rd(info->dev, &info->tca9539, offset, val);
i2cmsg[0].addr = info->tca9539.addr;
i2cmsg[0].len = info->tca9539.reg_len;
i2cmsg[0].buf = (__u8 *)data;
i2cmsg[0].flags = I2C_M_NOSTART;
i2cmsg[1].addr = info->tca9539.addr;
i2cmsg[1].flags = I2C_M_RD;
i2cmsg[1].len = size;
i2cmsg[1].buf = (__u8 *)val;
ret = i2c_transfer(info->tca9539.adap, i2cmsg, 2);
if (ret > 0)
ret = 0;
mutex_unlock(&info->mutex);
return ret;
@@ -901,16 +824,16 @@ static int cdi_mgr_open(struct inode *inode, struct file *file)
"%s: failed to wait for the semaphore\n",
__func__);
if (cdi_mgr->cim_ver == 1U) { /* P3714 A01 */
if (tca9539_raw_rd(cdi_mgr, 0x02, &val) != 0)
if (tca9539_rd(cdi_mgr, 0x02, &val) != 0)
return -EFAULT;
val |= (0x10 << cdi_mgr->tca9539.power_port);
if (tca9539_raw_wr(cdi_mgr, 0x02, val) != 0)
if (tca9539_wr(cdi_mgr, 0x02, val) != 0)
return -EFAULT;
} else if (cdi_mgr->cim_ver == 2U) { /* P3714 A02 */
if (tca9539_raw_rd(cdi_mgr, 0x03, &val) != 0)
if (tca9539_rd(cdi_mgr, 0x03, &val) != 0)
return -EFAULT;
val |= (0x1 << cdi_mgr->tca9539.power_port);
if (tca9539_raw_wr(cdi_mgr, 0x03, val) != 0)
if (tca9539_wr(cdi_mgr, 0x03, val) != 0)
return -EFAULT;
}
up(&tca9539_sem);
@@ -933,16 +856,16 @@ static int cdi_mgr_release(struct inode *inode, struct file *file)
"%s: failed to wait for the semaphore\n",
__func__);
if (cdi_mgr->cim_ver == 1U) { /* P3714 A01 */
if (tca9539_raw_rd(cdi_mgr, 0x02, &val) != 0)
if (tca9539_rd(cdi_mgr, 0x02, &val) != 0)
return -EFAULT;
val &= ~(0x10 << cdi_mgr->tca9539.power_port);
if (tca9539_raw_wr(cdi_mgr, 0x02, val) != 0)
if (tca9539_wr(cdi_mgr, 0x02, val) != 0)
return -EFAULT;
} else if (cdi_mgr->cim_ver == 2U) { /* P3714 A02 */
if (tca9539_raw_rd(cdi_mgr, 0x03, &val) != 0)
if (tca9539_rd(cdi_mgr, 0x03, &val) != 0)
return -EFAULT;
val &= ~(0x1 << cdi_mgr->tca9539.power_port);
if (tca9539_raw_wr(cdi_mgr, 0x03, val) != 0)
if (tca9539_wr(cdi_mgr, 0x03, val) != 0)
return -EFAULT;
}
up(&tca9539_sem);
@@ -1242,7 +1165,7 @@ static int cdi_mgr_suspend(struct device *dev)
if (cdi_mgr->tca9539.enable) {
reg_addr = CDI_MGR_TCA9539_BASE_REG_ADDR;
while (reg_addr < CDI_MGR_TCA9539_REGISTER_COUNT) {
rc = tca9539_raw_rd(cdi_mgr, reg_addr,
rc = tca9539_rd(cdi_mgr, reg_addr,
&cdi_mgr->pre_suspend_tca9539_regvals[reg_addr]);
if (rc != 0) {
dev_err(dev, "%s: tca9539_raw_rd failed reading reg[0x%x]\n",
@@ -1276,7 +1199,7 @@ static int cdi_mgr_resume(struct device *dev)
if (cdi_mgr->tca9539.enable) {
reg_addr = CDI_MGR_TCA9539_BASE_REG_ADDR;
while (reg_addr < CDI_MGR_TCA9539_REGISTER_COUNT) {
rc = tca9539_raw_wr(cdi_mgr, reg_addr,
rc = tca9539_wr(cdi_mgr, reg_addr,
cdi_mgr->pre_suspend_tca9539_regvals[reg_addr]);
if (rc != 0) {
dev_err(dev, "%s: tca9539_raw_wr failed setting reg[0x%x] = 0x%x\n",
@@ -1689,27 +1612,27 @@ static int cdi_mgr_probe(struct platform_device *pdev)
/* the registers in TCA9539 */
/* Use the IO expander to control PWDN signals */
if (cdi_mgr->cim_ver == 1U) { /* P3714 A01 */
if (tca9539_raw_wr(cdi_mgr, 0x6, 0x0E) != 0) {
if (tca9539_wr(cdi_mgr, 0x6, 0x0E) != 0) {
dev_err(&pdev->dev,
"%s: ERR %d: TCA9539: Failed to select PWDN signal source\n",
__func__, err);
goto err_probe;
}
/* Output low for AGGA/B/C/D_PWRDN */
if (tca9539_raw_wr(cdi_mgr, 0x2, 0x0E) != 0) {
if (tca9539_wr(cdi_mgr, 0x2, 0x0E) != 0) {
dev_err(&pdev->dev,
"%s: ERR %d: TCA9539: Failed to set the output level\n",
__func__, err);
goto err_probe;
}
} else if (cdi_mgr->cim_ver == 2U) { /* P3714 A02 */
if (tca9539_raw_wr(cdi_mgr, 0x6, 0xC0) != 0) {
if (tca9539_wr(cdi_mgr, 0x6, 0xC0) != 0) {
dev_err(&pdev->dev,
"%s: ERR %d: TCA9539: Failed to select FS selection signal source\n",
__func__, err);
goto err_probe;
}
if (tca9539_raw_wr(cdi_mgr, 0x7, 0x70) != 0) {
if (tca9539_wr(cdi_mgr, 0x7, 0x70) != 0) {
dev_err(&pdev->dev,
"%s: ERR %d: TCA9539: Failed to select PWDN signal source\n",
__func__, err);
@@ -1722,7 +1645,7 @@ static int cdi_mgr_probe(struct platform_device *pdev)
cdi_mgr->cim_frsync[0],
cdi_mgr->cim_frsync[1],
cdi_mgr->cim_frsync[2]);
if (tca9539_raw_wr(cdi_mgr, 0x2,
if (tca9539_wr(cdi_mgr, 0x2,
(cdi_mgr->cim_frsync[2] << 4) |
(cdi_mgr->cim_frsync[1] << 2) |
(cdi_mgr->cim_frsync[0])) < 0) {
@@ -1732,7 +1655,7 @@ static int cdi_mgr_probe(struct platform_device *pdev)
goto err_probe;
}
/* Output low for AGGA/B/C/D_PWRDN */
if (tca9539_raw_wr(cdi_mgr, 0x3, 0x00) != 0) {
if (tca9539_wr(cdi_mgr, 0x3, 0x00) != 0) {
dev_err(&pdev->dev,
"%s: ERR %d: TCA9539: Failed to set the output level\n",
__func__, err);

View File

@@ -11,6 +11,7 @@
#define CDI_DEV_IOCTL_RW _IOW('o', 1, struct cdi_dev_package)
#define CDI_DEV_IOCTL_GET_PWR_INFO _IOW('o', 2, struct cdi_dev_pwr_ctrl_info)
#define CDI_DEV_IOCTL_FRSYNC_MUX _IOW('o', 3, struct cdi_dev_fsync_mux)
#define DES_PWR_NVCCP 0U
#define DES_PWR_GPIO 1U
@@ -27,6 +28,11 @@ struct __attribute__ ((__packed__)) cdi_dev_pwr_ctrl_info {
__u8 cam_pwr_links[MAX_POWER_LINKS_PER_BLOCK];
};
struct cdi_dev_fsync_mux {
__s8 mux_sel;
__s8 cam_grp;
};
struct __attribute__ ((__packed__)) cdi_dev_package {
__u16 offset;
__u16 offset_len;