diff --git a/drivers/Makefile b/drivers/Makefile index 880248be..099d0f27 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -28,4 +28,5 @@ obj-m += thermal/ obj-m += watchdog/ obj-m += video/tegra/ obj-m += virt/tegra/ +obj-m += media/ diff --git a/drivers/media/Makefile b/drivers/media/Makefile new file mode 100644 index 00000000..1349bbf9 --- /dev/null +++ b/drivers/media/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +obj-m += platform/ diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile new file mode 100644 index 00000000..f1a13000 --- /dev/null +++ b/drivers/media/platform/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +obj-m += tegra/ diff --git a/drivers/media/platform/tegra/Makefile b/drivers/media/platform/tegra/Makefile new file mode 100644 index 00000000..ba9f11cb --- /dev/null +++ b/drivers/media/platform/tegra/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +obj-m += cdi/ diff --git a/drivers/media/platform/tegra/cdi/Makefile b/drivers/media/platform/tegra/cdi/Makefile new file mode 100644 index 00000000..88bbbd0e --- /dev/null +++ b/drivers/media/platform/tegra/cdi/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +LINUXINCLUDE += -I$(srctree.nvidia-oot)/include + +obj-m += cdi_gpio.o +obj-m += cdi_mgr.o +obj-m += cdi_dev.o +obj-m += cdi_pwm.o +obj-m += cdi_tsc.o diff --git a/drivers/media/platform/tegra/cdi/cdi-dev-priv.h b/drivers/media/platform/tegra/cdi/cdi-dev-priv.h new file mode 100644 index 00000000..043ed0be --- /dev/null +++ b/drivers/media/platform/tegra/cdi/cdi-dev-priv.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#ifndef __CDI_DEV_PRIV_H__ +#define __CDI_DEV_PRIV_H__ + +#include + +#define MAX_POWER_LINKS_PER_BLOCK (4U) + +struct max20087_priv { + int bus; + u32 addr; + u32 reg_len; + u32 dat_len; + u8 links[MAX_POWER_LINKS_PER_BLOCK]; +}; + +struct cdi_dev_info { + struct i2c_client *i2c_client; + struct device *dev; + struct cdev cdev; + struct cdi_dev_platform_data *pdata; + atomic_t in_use; + struct mutex mutex; + struct cdi_dev_package rw_pkg; + struct dentry *d_entry; + u32 reg_len; + u32 reg_off; + char devname[32]; + u8 power_is_on; + u8 cam_pwr_method; + u8 cam_pwr_i2c_addr; + struct max20087_priv max20087; +}; + +int cdi_dev_raw_rd(struct cdi_dev_info *info, unsigned int offset, + unsigned int offset_len, u8 *val, size_t size); +int cdi_dev_raw_wr(struct cdi_dev_info *info, unsigned int offset, u8 *val, + size_t size); + +int cdi_dev_debugfs_init(struct cdi_dev_info *cdi_dev); +int cdi_dev_debugfs_remove(struct cdi_dev_info *cdi_dev); + +#endif /* __CDI_DEV_PRIV_H__ */ diff --git a/drivers/media/platform/tegra/cdi/cdi-gpio-priv.h b/drivers/media/platform/tegra/cdi/cdi-gpio-priv.h new file mode 100644 index 00000000..97e7a522 --- /dev/null +++ b/drivers/media/platform/tegra/cdi/cdi-gpio-priv.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#ifndef __CDI_GPIO_PRIV_H__ +#define __CDI_GPIO_PRIV_H__ + +struct cdi_gpio_plat_data { + const char *gpio_prnt_chip; + u32 max_gpio; +}; + +struct cdi_gpio_desc { + u32 gpio; + atomic_t ref_cnt; +}; + +struct cdi_gpio_priv { + struct device *pdev; + struct cdi_gpio_plat_data pdata; + struct mutex mutex; + struct gpio_chip gpio_chip; + struct gpio_chip *tgc; + struct cdi_gpio_desc *gpios; + u32 num_gpio; +}; + +#endif /* __CDI_GPIO_PRIV_H__ */ diff --git a/drivers/media/platform/tegra/cdi/cdi-mgr-priv.h b/drivers/media/platform/tegra/cdi/cdi-mgr-priv.h new file mode 100644 index 00000000..ecc9b183 --- /dev/null +++ b/drivers/media/platform/tegra/cdi/cdi-mgr-priv.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#ifndef __CDI_MGR_PRIV_H__ +#define __CDI_MGR_PRIV_H__ + +#include +#include + +#define CDI_MGR_STOP_GPIO_INTR_EVENT_WAIT (~(0u)) +#define CDI_MGR_TCA9539_REGISTER_COUNT (8) +#define CDI_MGR_TCA9539_BASE_REG_ADDR (0x00) + +enum cam_gpio_direction { + CAM_DEVBLK_GPIO_UNSUSED = 0, + CAM_DEVBLK_GPIO_INPUT_INTERRUPT, + CAM_DEVBLK_GPIO_OUTPUT +}; + +struct cam_gpio_config { + int index; + enum cam_gpio_direction gpio_dir; + struct gpio_desc *desc; + 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 */ + dev_t devt; + struct cdev cdev; + struct class *cdi_class; + struct i2c_adapter *adap; + struct cdi_mgr_platform_data *pdata; + struct list_head dev_list; + struct mutex mutex; + struct dentry *d_entry; + struct work_struct ins_work; + struct task_struct *t; + struct kernel_siginfo sinfo; + int sig_no; /* store signal number from user space */ + spinlock_t spinlock; + atomic_t in_use; + char devname[32]; + u32 pwr_state; + atomic_t irq_in_use; + struct pwm_device *pwm; + wait_queue_head_t err_queue; + bool err_irq_reported; + u8 des_pwr_method; + u8 des_pwr_i2c_addr; + struct tca9539_priv tca9539; + struct cam_gpio_config gpio_arr[MAX_CDI_GPIOS]; + 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 */ + u32 cim_frsync[3]; /* FRSYNC source selection for each muxer */ + u8 pre_suspend_tca9539_regvals[CDI_MGR_TCA9539_REGISTER_COUNT]; +}; + +int cdi_mgr_power_up(struct cdi_mgr_priv *cdi_mgr, unsigned long arg); +int cdi_mgr_power_down(struct cdi_mgr_priv *cdi_mgr, unsigned long arg); + +int cdi_mgr_debugfs_init(struct cdi_mgr_priv *cdi_mgr); +int cdi_mgr_debugfs_remove(struct cdi_mgr_priv *cdi_mgr); + +#endif /* __CDI_MGR_PRIV_H__ */ diff --git a/drivers/media/platform/tegra/cdi/cdi-pwm-priv.h b/drivers/media/platform/tegra/cdi/cdi-pwm-priv.h new file mode 100644 index 00000000..dc2a02ba --- /dev/null +++ b/drivers/media/platform/tegra/cdi/cdi-pwm-priv.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2016-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#ifndef __CDI_PWM_PRIV_H__ +#define __CDI_PWM_PRIV_H__ + +struct cdi_pwm_info { + struct pwm_chip chip; + struct pwm_device *pwm; + atomic_t in_use; + struct mutex mutex; + bool force_on; +}; + +#endif /* __CDI_PWM_PRIV_H__ */ diff --git a/drivers/media/platform/tegra/cdi/cdi_dev.c b/drivers/media/platform/tegra/cdi/cdi_dev.c new file mode 100644 index 00000000..75da67a2 --- /dev/null +++ b/drivers/media/platform/tegra/cdi/cdi_dev.c @@ -0,0 +1,698 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cdi-dev-priv.h" +#include "cdi-mgr-priv.h" + +/* i2c payload size is only 12 bit */ +#define MAX_MSG_SIZE (0xFFF - 1) + +/*#define DEBUG_I2C_TRAFFIC*/ + +/* CDI Dev Debugfs functions + * + * - cdi_dev_debugfs_init + * - cdi_dev_debugfs_remove + * - i2c_oft_get + * - i2c_oft_set + * - i2c_val_get + * - i2c_oft_set + */ +static int i2c_val_get(void *data, u64 *val) +{ + struct cdi_dev_info *cdi_dev = data; + u8 temp = 0; + + if (cdi_dev_raw_rd(cdi_dev, cdi_dev->reg_off, 0, &temp, 1)) { + dev_err(cdi_dev->dev, "ERR:%s failed\n", __func__); + return -EIO; + } + *val = (u64)temp; + return 0; +} + +static int i2c_val_set(void *data, u64 val) +{ + struct cdi_dev_info *cdi_dev = data; + u8 temp[3]; + + temp[2] = val & 0xff; + if (cdi_dev_raw_wr(cdi_dev, cdi_dev->reg_off, temp, 1)) { + dev_err(cdi_dev->dev, "ERR:%s failed\n", __func__); + return -EIO; + } + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(cdi_val_fops, i2c_val_get, i2c_val_set, "0x%02llx\n"); + +static int i2c_oft_get(void *data, u64 *val) +{ + struct cdi_dev_info *cdi_dev = data; + + *val = (u64)cdi_dev->reg_off; + return 0; +} + +static int i2c_oft_set(void *data, u64 val) +{ + struct cdi_dev_info *cdi_dev = data; + + cdi_dev->reg_off = (typeof(cdi_dev->reg_off))val; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(cdi_oft_fops, i2c_oft_get, i2c_oft_set, "0x%02llx\n"); + +int cdi_dev_debugfs_init(struct cdi_dev_info *cdi_dev) +{ + struct cdi_mgr_priv *cdi_mgr = NULL; + struct dentry *d; + + dev_dbg(cdi_dev->dev, "%s %s\n", __func__, cdi_dev->devname); + + if (cdi_dev->pdata) + cdi_mgr = dev_get_drvdata(cdi_dev->pdata->pdev); + + cdi_dev->d_entry = debugfs_create_dir( + cdi_dev->devname, + cdi_mgr ? cdi_mgr->d_entry : NULL); + if (cdi_dev->d_entry == NULL) { + dev_err(cdi_dev->dev, "%s: create dir failed\n", __func__); + return -ENOMEM; + } + + d = debugfs_create_file("val", 0644, cdi_dev->d_entry, + (void *)cdi_dev, &cdi_val_fops); + if (!d) { + dev_err(cdi_dev->dev, "%s: create file failed\n", __func__); + debugfs_remove_recursive(cdi_dev->d_entry); + cdi_dev->d_entry = NULL; + } + + d = debugfs_create_file("offset", 0644, cdi_dev->d_entry, + (void *)cdi_dev, &cdi_oft_fops); + if (!d) { + dev_err(cdi_dev->dev, "%s: create file failed\n", __func__); + debugfs_remove_recursive(cdi_dev->d_entry); + cdi_dev->d_entry = NULL; + } + + return 0; +} + +int cdi_dev_debugfs_remove(struct cdi_dev_info *cdi_dev) +{ + if (cdi_dev->d_entry == NULL) + return 0; + debugfs_remove_recursive(cdi_dev->d_entry); + cdi_dev->d_entry = NULL; + return 0; +} + +static void cdi_dev_dump( + const char *str, + struct cdi_dev_info *info, + unsigned int offset, + u8 *buf, size_t size) +{ +#if (defined(DEBUG) || defined(DEBUG_I2C_TRAFFIC)) + char *dump_buf; + int len, i, off; + + /* alloc enough memory for function name + offset + data */ + len = strlen(str) + size * 3 + 10; + dump_buf = kzalloc(len, GFP_KERNEL); + if (dump_buf == NULL) { + dev_err(info->dev, "%s: Memory alloc ERROR!\n", __func__); + return; + } + + off = sprintf(dump_buf, "%s %04x =", str, offset); + for (i = 0; i < size && off < len - 1; i++) + off += sprintf(dump_buf + off, " %02x", buf[i]); + dump_buf[off] = 0; + dev_notice(info->dev, "%s\n", dump_buf); + kfree(dump_buf); +#endif +} + +/* i2c read from device. + val - buffer contains data to write. + size - number of bytes to be writen to device. + offset - address in the device's register space to start with. +*/ +int cdi_dev_raw_rd( + struct cdi_dev_info *info, unsigned int offset, + unsigned int offset_len, u8 *val, size_t size) +{ + int ret = -ENODEV; + u8 data[2]; + struct i2c_msg i2cmsg[2]; + + dev_dbg(info->dev, "%s\n", __func__); + mutex_lock(&info->mutex); + if (!info->power_is_on) { + dev_err(info->dev, "%s: power is off.\n", __func__); + mutex_unlock(&info->mutex); + return ret; + } + + /* when user read device from debugfs, the offset_len will be 0. + * And the offset_len should come from device info + */ + if (!offset_len) + offset_len = info->reg_len; + + if (offset_len == 2) { + data[0] = (u8)((offset >> 8) & 0xff); + data[1] = (u8)(offset & 0xff); + } else if (offset_len == 1) + data[0] = (u8)(offset & 0xff); + + i2cmsg[0].addr = info->i2c_client->addr; + i2cmsg[0].len = offset_len; + i2cmsg[0].buf = (__u8 *)data; + i2cmsg[0].flags = I2C_M_NOSTART; + + i2cmsg[1].addr = info->i2c_client->addr; + i2cmsg[1].flags = I2C_M_RD; + i2cmsg[1].len = size; + i2cmsg[1].buf = (__u8 *)val; + + ret = i2c_transfer(info->i2c_client->adapter, i2cmsg, 2); + if (ret > 0) + ret = 0; + mutex_unlock(&info->mutex); + + if (!ret) + cdi_dev_dump(__func__, info, offset, val, size); + + return ret; +} + +/* i2c write to device. + val - buffer contains data to write. + size - number of bytes to be writen to device. + offset - address in the device's register space to start with. + if offset == -1, it will be ignored and no offset + value will be integrated into the data buffer. +*/ +int cdi_dev_raw_wr( + struct cdi_dev_info *info, unsigned int offset, u8 *val, size_t size) +{ + int ret = -ENODEV; + u8 *buf_start = NULL; + struct i2c_msg *i2cmsg; + unsigned int num_msgs = 0, total_size, i; + + dev_dbg(info->dev, "%s\n", __func__); + mutex_lock(&info->mutex); + + if (size == 0) { + dev_dbg(info->dev, "%s: size is 0.\n", __func__); + mutex_unlock(&info->mutex); + return 0; + } + + if (!info->power_is_on) { + dev_err(info->dev, "%s: power is off.\n", __func__); + mutex_unlock(&info->mutex); + return ret; + } + + if (offset != (unsigned int)-1) { /* offset is valid */ + if (info->reg_len == 2) { + val[0] = (u8)((offset >> 8) & 0xff); + val[1] = (u8)(offset & 0xff); + size += 2; + } else if (info->reg_len == 1) { + val++; + val[0] = (u8)(offset & 0xff); + size += 1; + } else + val += 2; + } + + cdi_dev_dump(__func__, info, offset, val, size); + + 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) { + dev_err(info->dev, "%s: failed to allocate memory\n", + __func__); + mutex_unlock(&info->mutex); + return -ENOMEM; + } + + buf_start = val; + 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->i2c_client->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->i2c_client->adapter, i2cmsg, num_msgs); + if (ret > 0) + ret = 0; + + kfree(i2cmsg); + mutex_unlock(&info->mutex); + return ret; +} + +static int cdi_dev_raw_rw(struct cdi_dev_info *info) +{ + struct cdi_dev_package *pkg = &info->rw_pkg; + void *u_ptr = (void *)pkg->buffer; + u8 *buf; + int ret = -ENODEV; + + dev_dbg(info->dev, "%s\n", __func__); + + buf = kzalloc(pkg->size, GFP_KERNEL); + if (buf == NULL) { + dev_err(info->dev, "%s: Unable to allocate memory!\n", + __func__); + return -ENOMEM; + } + if (pkg->flags & CDI_DEV_PKG_FLAG_WR) { + /* write to device */ + if (copy_from_user(buf, + (const void __user *)u_ptr, pkg->size)) { + dev_err(info->dev, "%s copy_from_user err line %d\n", + __func__, __LINE__); + kfree(buf); + return -EFAULT; + } + /* in the user access case, the offset is integrated in the + buffer to be transferred, so pass -1 as the offset */ + ret = cdi_dev_raw_wr(info, -1, buf, pkg->size); + } else { + /* read from device */ + ret = cdi_dev_raw_rd(info, pkg->offset, + pkg->offset_len, buf, pkg->size); + if (!ret && copy_to_user( + (void __user *)u_ptr, buf, pkg->size)) { + dev_err(info->dev, "%s copy_to_user err line %d\n", + __func__, __LINE__); + ret = -EINVAL; + } + } + + kfree(buf); + return ret; +} + +static int cdi_dev_get_package( + struct cdi_dev_info *info, unsigned long arg) +{ + struct cdi_dev_package pkg; + + if (copy_from_user(&pkg, + (const void __user *)arg, sizeof(pkg))) { + dev_err(info->dev, "%s copy_from_user err line %d\n", + __func__, __LINE__); + return -EFAULT; + } + info->rw_pkg.offset = pkg.offset; + info->rw_pkg.offset_len = pkg.offset_len; + info->rw_pkg.size = pkg.size; + info->rw_pkg.flags = pkg.flags; + info->rw_pkg.buffer = pkg.buffer; + + if ((void __user *)info->rw_pkg.buffer == NULL) { + dev_err(info->dev, "%s package buffer NULL\n", __func__); + return -EINVAL; + } + + if (!info->rw_pkg.size) { + dev_err(info->dev, "%s invalid package size %d\n", + __func__, info->rw_pkg.size); + return -EINVAL; + } + + return 0; +} + +static int cdi_dev_get_pwr_info( + struct cdi_dev_info *info, + void __user *arg) +{ + struct cdi_dev_pwr_ctrl_info pinfo; + + if (copy_from_user(&pinfo, arg, sizeof(pinfo))) { + dev_err(info->dev, + "%s: failed to copy from user\n", __func__); + return -EFAULT; + } + + pinfo.cam_pwr_method = info->cam_pwr_method; + pinfo.cam_pwr_i2c_addr = info->cam_pwr_i2c_addr; + memcpy(pinfo.cam_pwr_links, info->max20087.links, sizeof(u8) * MAX_POWER_LINKS_PER_BLOCK); + + if (copy_to_user(arg, &pinfo, sizeof(pinfo))) { + dev_err(info->dev, + "%s: failed to copy to user\n", __func__); + return -EFAULT; + } + return 0; +} + +static long cdi_dev_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct cdi_dev_info *info = file->private_data; + int err = 0; + + switch (cmd) { + case CDI_DEV_IOCTL_RW: + err = cdi_dev_get_package(info, arg); + if (err) + break; + + err = cdi_dev_raw_rw(info); + break; + case CDI_DEV_IOCTL_GET_PWR_INFO: + err = cdi_dev_get_pwr_info(info, (void __user *)arg); + break; + default: + dev_dbg(info->dev, "%s: invalid cmd %x\n", __func__, cmd); + return -EINVAL; + } + + return err; +} + +static int cdi_dev_open(struct inode *inode, struct file *file) +{ + struct cdi_dev_info *info; + + if (inode == NULL) + return -ENODEV; + + info = container_of(inode->i_cdev, struct cdi_dev_info, cdev); + + if (atomic_xchg(&info->in_use, 1)) + return -EBUSY; + + file->private_data = info; + dev_dbg(info->dev, "%s\n", __func__); + return 0; +} + +static int cdi_dev_release(struct inode *inode, struct file *file) +{ + struct cdi_dev_info *info = file->private_data; + + dev_dbg(info->dev, "%s\n", __func__); + file->private_data = NULL; + WARN_ON(!atomic_xchg(&info->in_use, 0)); + return 0; +} + +static const struct file_operations cdi_dev_fileops = { + .owner = THIS_MODULE, + .open = cdi_dev_open, + .unlocked_ioctl = cdi_dev_ioctl, + .release = cdi_dev_release, +}; + +static int cdi_dev_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cdi_dev_info *info; + struct cdi_mgr_priv *cdi_mgr = NULL; + struct device *pdev; + struct device_node *child = NULL, *child_max20087 = NULL; + int err; + int numLinks = 0; + int i; + + dev_dbg(&client->dev, "%s: initializing link @%x-%04x\n", + __func__, client->adapter->nr, client->addr); + + info = devm_kzalloc( + &client->dev, sizeof(struct cdi_dev_info), GFP_KERNEL); + if (!info) { + pr_err("%s: Unable to allocate memory!\n", __func__); + return -ENOMEM; + } + + mutex_init(&info->mutex); + + if (client->dev.platform_data) { + info->pdata = client->dev.platform_data; + dev_dbg(&client->dev, "pdata: %p\n", info->pdata); + } else { + dev_notice(&client->dev, "%s NO platform data\n", __func__); + return -ENODEV; + } + if (info->pdata->np != NULL) { + child = of_get_child_by_name(info->pdata->np, + "pwr_ctrl"); + if (child != NULL) { + if (of_property_read_bool(child, + "cam-pwr-max20087")) + info->cam_pwr_method = CAM_PWR_MAX20087; + else if (of_property_read_bool(child, + "cam-pwr-nvccp")) + info->cam_pwr_method = CAM_PWR_NVCCP; + else + info->cam_pwr_method = CAM_PWR_NO_PWR; + + /* get the max20087 information */ + child_max20087 = of_get_child_by_name(child, "max20087"); + if (child_max20087 != NULL) { + err = of_property_read_u32(child_max20087, "i2c-bus", + &info->max20087.bus); + if (err) { + dev_err(&client->dev, "%s: failed to read \"i2c-bus\"\n", + __func__); + return err; + } + err = of_property_read_u32(child_max20087, "addr", + &info->max20087.addr); + if (err || !info->max20087.addr) { + info->cam_pwr_i2c_addr = 0; + dev_err(&client->dev, "%s: failed to read \"addr\"\n", + __func__); + return err; + } + info->cam_pwr_i2c_addr = info->max20087.addr; + err = of_property_read_u32(child_max20087, "reg_len", + &info->max20087.reg_len); + if (err || !info->max20087.reg_len) { + dev_err(&client->dev, "%s: failed to read \"reg_len\"\n", + __func__); + return err; + } + err = of_property_read_u32(child_max20087, "dat_len", + &info->max20087.dat_len); + if (err || !info->max20087.dat_len) { + dev_err(&client->dev, "%s: failed to read \"dat_len\"\n", + __func__); + return err; + } + /* default link mappings */ + for (i = 0; i < MAX_POWER_LINKS_PER_BLOCK; i++) { + info->max20087.links[i] = i; + } + numLinks = of_property_count_elems_of_size(child_max20087, "links", + sizeof(u32)); + /* Only links with MAX_POWER_LINKS_PER_BLOCK elements is valid */ + if (numLinks == MAX_POWER_LINKS_PER_BLOCK) { + u32 link; + for (i = 0; i < numLinks; i++) { + err = of_property_read_u32_index(child_max20087, "links", i, &link); + if (err == 0) { + if ((link >= 0) && (link < MAX_POWER_LINKS_PER_BLOCK)) { + info->max20087.links[i] = (u8) link; + } else { + dev_err(&client->dev, "%s: Incorrect camera module index: %d\n", + __func__, link); + return -EINVAL; + } + } + } + } + } + } + } + + if (info->pdata->reg_bits) + info->reg_len = info->pdata->reg_bits / 8; + else + info->reg_len = 2; + + if (info->reg_len > 2) { + dev_err(&client->dev, + "device offset length invalid: %d\n", info->reg_len); + devm_kfree(&client->dev, info); + return -ENODEV; + } + info->i2c_client = client; + info->dev = &client->dev; + + if (info->pdata) + err = snprintf(info->devname, sizeof(info->devname), + "%s", info->pdata->drv_name); + else + err = snprintf(info->devname, sizeof(info->devname), + "cdi-dev.%u.%02x", client->adapter->nr, + client->addr); + + if (err < 0) { + dev_err(&client->dev, + "encoding error: %d\n", err); + devm_kfree(&client->dev, info); + return err; + } + + if (info->pdata->pdev == NULL) + return -ENODEV; + + cdev_init(&info->cdev, &cdi_dev_fileops); + info->cdev.owner = THIS_MODULE; + pdev = info->pdata->pdev; + + err = cdev_add(&info->cdev, MKDEV(MAJOR(pdev->devt), client->addr), 1); + if (err < 0) { + dev_err(&client->dev, + "%s: Could not add cdev for %d\n", __func__, + MKDEV(MAJOR(pdev->devt), client->addr)); + devm_kfree(&client->dev, info); + return err; + } + + /* send uevents to udev, it will create /dev node for cdi-mgr */ + info->dev = device_create(pdev->class, &client->dev, + info->cdev.dev, + info, info->devname); + if (IS_ERR(info->dev)) { + info->dev = NULL; + cdev_del(&info->cdev); + devm_kfree(&client->dev, info); + return PTR_ERR(info->dev); + } + + /* parse the power control method */ + if (info->pdata) + cdi_mgr = dev_get_drvdata(info->pdata->pdev); + + info->power_is_on = 1; + i2c_set_clientdata(client, info); + cdi_dev_debugfs_init(info); + return 0; +} + +static int cdi_dev_remove(struct i2c_client *client) +{ + struct cdi_dev_info *info = i2c_get_clientdata(client); + struct device *pdev; + + dev_dbg(&client->dev, "%s\n", __func__); + cdi_dev_debugfs_remove(info); + + /* remove only cdi_dev_info not i2c_client itself */ + pdev = info->pdata->pdev; + + if (info->dev) + device_destroy(pdev->class, info->cdev.dev); + + if (info->cdev.dev) + cdev_del(&info->cdev); + + return 0; +} + +#ifdef CONFIG_PM +static int cdi_dev_suspend(struct device *dev) +{ + struct cdi_dev_info *cdi = (struct cdi_dev_info *)dev_get_drvdata(dev); + + dev_info(dev, "Suspending\n"); + mutex_lock(&cdi->mutex); + cdi->power_is_on = 0; + mutex_unlock(&cdi->mutex); + + return 0; +} + +static int cdi_dev_resume(struct device *dev) +{ + struct cdi_dev_info *cdi = (struct cdi_dev_info *)dev_get_drvdata(dev); + + dev_info(dev, "Resuming\n"); + mutex_lock(&cdi->mutex); + cdi->power_is_on = 1; + mutex_unlock(&cdi->mutex); + + return 0; +} +#endif + +static const struct i2c_device_id cdi_dev_id[] = { + { "cdi-dev", 0 }, + { }, +}; + +static const struct dev_pm_ops cdi_dev_pm_ops = { + SET_RUNTIME_PM_OPS(cdi_dev_suspend, + cdi_dev_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(cdi_dev_suspend, + cdi_dev_resume) +}; + +static struct i2c_driver cdi_dev_drv = { + .driver = { + .name = "cdi-dev", + .owner = THIS_MODULE, + .pm = &cdi_dev_pm_ops, + }, + .id_table = cdi_dev_id, + .probe = cdi_dev_probe, + .remove = cdi_dev_remove, +}; + +module_i2c_driver(cdi_dev_drv); + +MODULE_DESCRIPTION("CDI Generic I2C driver"); +MODULE_AUTHOR("Charlie Huang "); +MODULE_LICENSE("GPL v2"); +MODULE_SOFTDEP("pre: cdi_gpio"); diff --git a/drivers/media/platform/tegra/cdi/cdi_gpio.c b/drivers/media/platform/tegra/cdi/cdi_gpio.c new file mode 100644 index 00000000..4b7b9856 --- /dev/null +++ b/drivers/media/platform/tegra/cdi/cdi_gpio.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cdi-gpio-priv.h" + +#define MAX_STR_SIZE 255 + +static int of_cdi_gpio_pdata(struct platform_device *pdev, + struct cdi_gpio_plat_data *pdata) +{ + struct device_node *np = pdev->dev.of_node; + int err; + + err = of_property_read_string(np, "parent-gpio-chip", + &pdata->gpio_prnt_chip); + if (err < 0) + return err; + + err = of_property_read_u32(pdev->dev.of_node, + "max-gpios", &pdata->max_gpio); + + return err; +} + +static int cdi_gpio_chip_match(struct gpio_chip *chip, void *data) +{ + return !strcmp(chip->label, data); +} + +static struct gpio_chip *cdi_gpio_get_chip(struct platform_device *pdev, + struct cdi_gpio_plat_data *pd) +{ + struct gpio_chip *gc = NULL; + char name[MAX_STR_SIZE]; + + if (strlen(pd->gpio_prnt_chip) > MAX_STR_SIZE) { + dev_err(&pdev->dev, "%s: gpio chip name is too long: %s\n", + __func__, pd->gpio_prnt_chip); + return NULL; + } + strcpy(name, pd->gpio_prnt_chip); + + gc = gpiochip_find(name, cdi_gpio_chip_match); + if (!gc) { + dev_err(&pdev->dev, "%s: unable to find gpio parent chip %s\n", + __func__, pd->gpio_prnt_chip); + return NULL; + } + + return gc; +} + +static int cdi_gpio_init_desc(struct platform_device *pdev, + struct cdi_gpio_priv *cdi_gpio) +{ + struct cdi_gpio_desc *desc = NULL; + u32 i; + + desc = devm_kzalloc(&pdev->dev, + (sizeof(struct cdi_gpio_desc) * + cdi_gpio->pdata.max_gpio), + GFP_KERNEL); + if (!desc) { + dev_err(&pdev->dev, "Unable to allocate memory!\n"); + return -ENOMEM; + } + + for (i = 0; i < cdi_gpio->pdata.max_gpio; i++) { + desc[i].gpio = 0; + atomic_set(&desc[i].ref_cnt, 0); + } + + cdi_gpio->gpios = desc; + return 0; +} + +static int cdi_gpio_get_index(struct device *dev, + struct cdi_gpio_priv *cdi_gpio, u32 gpio) +{ + u32 i; + int idx = -1; + + /* find gpio in array */ + for (i = 0; i < cdi_gpio->num_gpio; i++) { + if (cdi_gpio->gpios[i].gpio == gpio) { + idx = i; + break; + } + } + + /* gpio exists, return idx */ + if (idx >= 0) + return idx; + + /* add gpio if it doesn't exist and there is memory available */ + if (cdi_gpio->num_gpio < cdi_gpio->pdata.max_gpio) { + idx = cdi_gpio->num_gpio; + cdi_gpio->gpios[idx].gpio = gpio; + cdi_gpio->num_gpio++; + + return idx; + } + + dev_err(dev, "%s: Unable to add gpio to desc\n", __func__); + return -EFAULT; +} + +static int cdi_gpio_direction_input(struct gpio_chip *gc, unsigned int off) +{ + struct gpio_chip *tgc = NULL; + struct cdi_gpio_priv *cdi_gpio = NULL; + int err; + + cdi_gpio = gpiochip_get_data(gc); + if (!cdi_gpio) + return -EFAULT; + + mutex_lock(&cdi_gpio->mutex); + + tgc = cdi_gpio->tgc; + err = tgc->direction_input(tgc, off); + + mutex_unlock(&cdi_gpio->mutex); + + return err; +} + +static int cdi_gpio_direction_output(struct gpio_chip *gc, unsigned int off, + int val) +{ + struct gpio_chip *tgc = NULL; + struct cdi_gpio_priv *cdi_gpio = NULL; + int err; + + cdi_gpio = gpiochip_get_data(gc); + if (!cdi_gpio) + return -EFAULT; + + mutex_lock(&cdi_gpio->mutex); + + tgc = cdi_gpio->tgc; + err = tgc->direction_output(tgc, off, val); + + mutex_unlock(&cdi_gpio->mutex); + + return err; +} + +static int cdi_gpio_get_value(struct gpio_chip *gc, unsigned int off) +{ + int gpio_val; + struct gpio_chip *tgc = NULL; + struct cdi_gpio_priv *cdi_gpio = NULL; + + cdi_gpio = gpiochip_get_data(gc); + if (!cdi_gpio) + return -EFAULT; + + mutex_lock(&cdi_gpio->mutex); + + tgc = cdi_gpio->tgc; + gpio_val = tgc->get(tgc, off); + + mutex_unlock(&cdi_gpio->mutex); + + return gpio_val; +} + +static void cdi_gpio_set_value(struct gpio_chip *gc, unsigned int off, int val) +{ + int idx; + struct gpio_chip *tgc = NULL; + struct cdi_gpio_priv *cdi_gpio = NULL; + atomic_t *ref_cnt; + struct device *dev = NULL; + + cdi_gpio = gpiochip_get_data(gc); + if (!cdi_gpio) + return; + + mutex_lock(&cdi_gpio->mutex); + + dev = cdi_gpio->pdev; + tgc = cdi_gpio->tgc; + + idx = cdi_gpio_get_index(dev, cdi_gpio, off); + if (idx < 0) { + mutex_unlock(&cdi_gpio->mutex); + return; + } + idx = array_index_nospec(idx, cdi_gpio->pdata.max_gpio); + + /* set gpio value based on refcount */ + ref_cnt = &cdi_gpio->gpios[idx].ref_cnt; + switch (val) { + case 0: + if ((atomic_read(ref_cnt) > 0) && + atomic_dec_and_test(ref_cnt)) { + tgc->set(tgc, off, val); + } + dev_info(dev, "%s: gpio idx: %d, val to set: %d, refcount: %d\n", + __func__, idx, val, atomic_read(ref_cnt)); + break; + case 1: + if (!atomic_inc_and_test(ref_cnt)) + tgc->set(tgc, off, val); + + dev_info(dev, "%s: gpio idx: %d, val to set: %d, refcount: %d\n", + __func__, idx, val, atomic_read(ref_cnt)); + break; + default: + dev_err(dev, "%s: Invalid gpio value provided\n", + __func__); + break; + } + + mutex_unlock(&cdi_gpio->mutex); +} + +static int cdi_gpio_probe(struct platform_device *pdev) +{ + struct cdi_gpio_priv *cdi_gpio; + struct cdi_gpio_plat_data *pd = NULL; + struct gpio_chip *tgc, *gc; + int err; + + dev_info(&pdev->dev, "probing %s...\n", __func__); + + cdi_gpio = devm_kzalloc(&pdev->dev, + sizeof(struct cdi_gpio_priv), + GFP_KERNEL); + if (!cdi_gpio) { + dev_err(&pdev->dev, "Unable to allocate memory!\n"); + return -ENOMEM; + } + + /* get platform data from device tree */ + err = of_cdi_gpio_pdata(pdev, &cdi_gpio->pdata); + if (err < 0) + return err; + + pd = &cdi_gpio->pdata; + + /* get tegra gpio chip */ + tgc = cdi_gpio_get_chip(pdev, pd); + if (!tgc) + return -ENXIO; + + cdi_gpio->tgc = tgc; + + /* initialize gpio desc */ + err = cdi_gpio_init_desc(pdev, cdi_gpio); + if (err < 0) + return err; + + cdi_gpio->num_gpio = 0; + + /* setup gpio chip */ + gc = &cdi_gpio->gpio_chip; + gc->direction_input = cdi_gpio_direction_input; + gc->direction_output = cdi_gpio_direction_output; + gc->get = cdi_gpio_get_value; + gc->set = cdi_gpio_set_value; + + gc->can_sleep = false; + gc->base = -1; + gc->ngpio = pd->max_gpio; + gc->label = "cdi-gpio"; + gc->of_node = pdev->dev.of_node; + gc->owner = THIS_MODULE; + + err = gpiochip_add_data(gc, cdi_gpio); + if (err) { + dev_err(&pdev->dev, "failed to add GPIO controller\n"); + return err; + } + + mutex_init(&cdi_gpio->mutex); + cdi_gpio->pdev = &pdev->dev; + dev_set_drvdata(&pdev->dev, cdi_gpio); + + dev_info(&pdev->dev, "%s: successfully registered gpio device\n", + __func__); + return 0; +} + +static int cdi_gpio_remove(struct platform_device *pdev) +{ + struct cdi_gpio_priv *cdi_gpio = platform_get_drvdata(pdev); + + gpiochip_remove(&cdi_gpio->gpio_chip); + + return 0; +} + +static const struct of_device_id cdi_gpio_dt_ids[] = { + { .compatible = "nvidia,cdi-gpio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cdi_gpio_dt_ids); + +static struct platform_driver cdi_gpio_driver = { + .probe = cdi_gpio_probe, + .remove = cdi_gpio_remove, + .driver = { + .name = "cdi-gpio", + .of_match_table = cdi_gpio_dt_ids, + .owner = THIS_MODULE, + } +}; + +static int __init cdi_gpio_init(void) +{ + return platform_driver_register(&cdi_gpio_driver); +} + +static void __exit cdi_gpio_exit(void) +{ + platform_driver_unregister(&cdi_gpio_driver); +} + +/* call in subsys so that this module loads before cdi-mgr driver */ +subsys_initcall(cdi_gpio_init); +module_exit(cdi_gpio_exit); + +MODULE_DESCRIPTION("Tegra Auto CDI GPIO Driver"); +MODULE_AUTHOR("Anurag Dosapati "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/tegra/cdi/cdi_mgr.c b/drivers/media/platform/tegra/cdi/cdi_mgr.c new file mode 100644 index 00000000..74d67613 --- /dev/null +++ b/drivers/media/platform/tegra/cdi/cdi_mgr.c @@ -0,0 +1,1804 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cdi-mgr-priv.h" + +#define PW_ON(flag) ((flag) ? 0 : 1) +#define PW_OFF(flag) ((flag) ? 1 : 0) + +/* i2c payload size is only 12 bit */ +#define MAX_MSG_SIZE (0xFFF - 1) + +/* minor number range would be 0 to 127 */ +#define CDI_DEV_MAX 128 + +#define TIMEOUT_US 2000000 /* 2 seconds */ + +/* CDI Dev Debugfs functions + * + * - cdi_mgr_debugfs_init + * - cdi_mgr_debugfs_remove + * - cdi_mgr_status_show + * - cdi_mgr_attr_set + * - pwr_on_get + * - pwr_on_set + * - pwr_off_get + * - pwr_off_set + */ +static int cdi_mgr_status_show(struct seq_file *s, void *data) +{ + struct cdi_mgr_priv *cdi_mgr = s->private; + struct cdi_mgr_client *cdi_dev; + + if (cdi_mgr == NULL) + return 0; + pr_info("%s - %s\n", __func__, cdi_mgr->devname); + + if (list_empty(&cdi_mgr->dev_list)) { + seq_printf(s, "%s: No devices supported.\n", cdi_mgr->devname); + return 0; + } + + mutex_lock(&cdi_mgr->mutex); + list_for_each_entry_reverse(cdi_dev, &cdi_mgr->dev_list, list) { + seq_printf(s, " %02d -- @0x%02x, %02d, %d, %s\n", + cdi_dev->id, + cdi_dev->cfg.addr, + cdi_dev->cfg.reg_bits, + cdi_dev->cfg.val_bits, + cdi_dev->cfg.drv_name + ); + } + mutex_unlock(&cdi_mgr->mutex); + + return 0; +} + +static ssize_t cdi_mgr_attr_set(struct file *s, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + return count; +} + +static int cdi_mgr_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, cdi_mgr_status_show, inode->i_private); +} + +static const struct file_operations cdi_mgr_debugfs_fops = { + .open = cdi_mgr_debugfs_open, + .read = seq_read, + .write = cdi_mgr_attr_set, + .llseek = seq_lseek, + .release = single_release, +}; + +static int pwr_on_get(void *data, u64 *val) +{ + struct cdi_mgr_priv *cdi_mgr = data; + + if (cdi_mgr->pdata == NULL || !cdi_mgr->pdata->num_pwr_gpios) { + *val = 0ULL; + return 0; + } + + *val = (cdi_mgr->pwr_state & (BIT(28) - 1)) | + ((cdi_mgr->pdata->num_pwr_gpios & 0x0f) << 28); + return 0; +} + +static int pwr_on_set(void *data, u64 val) +{ + return cdi_mgr_power_up((struct cdi_mgr_priv *)data, val); +} + +DEFINE_SIMPLE_ATTRIBUTE(pwr_on_fops, pwr_on_get, pwr_on_set, "0x%02llx\n"); + +static int pwr_off_get(void *data, u64 *val) +{ + struct cdi_mgr_priv *cdi_mgr = data; + + if (cdi_mgr->pdata == NULL || !cdi_mgr->pdata->num_pwr_gpios) { + *val = 0ULL; + return 0; + } + + *val = (~cdi_mgr->pwr_state) & (BIT(cdi_mgr->pdata->num_pwr_gpios) - 1); + *val = (*val & (BIT(28) - 1)) | + ((cdi_mgr->pdata->num_pwr_gpios & 0x0f) << 28); + return 0; +} + +static int pwr_off_set(void *data, u64 val) +{ + return cdi_mgr_power_down((struct cdi_mgr_priv *)data, val); +} + +DEFINE_SIMPLE_ATTRIBUTE(pwr_off_fops, pwr_off_get, pwr_off_set, "0x%02llx\n"); + +static int tca9539_raw_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; + } + + 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( + 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); + + 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; +} + +int cdi_mgr_debugfs_init(struct cdi_mgr_priv *cdi_mgr) +{ + struct dentry *d; + + dev_dbg(cdi_mgr->dev, "%s %s\n", __func__, cdi_mgr->devname); + cdi_mgr->d_entry = debugfs_create_dir( + cdi_mgr->devname, NULL); + if (cdi_mgr->d_entry == NULL) { + dev_err(cdi_mgr->dev, "%s: create dir failed\n", __func__); + return -ENOMEM; + } + + d = debugfs_create_file("map", 0644, cdi_mgr->d_entry, + (void *)cdi_mgr, &cdi_mgr_debugfs_fops); + if (!d) + goto debugfs_init_err; + + d = debugfs_create_file("pwr-on", 0644, cdi_mgr->d_entry, + (void *)cdi_mgr, &pwr_on_fops); + if (!d) + goto debugfs_init_err; + + d = debugfs_create_file("pwr-off", 0644, cdi_mgr->d_entry, + (void *)cdi_mgr, &pwr_off_fops); + if (!d) + goto debugfs_init_err; + + return 0; + +debugfs_init_err: + dev_err(cdi_mgr->dev, "%s: create file failed\n", __func__); + debugfs_remove_recursive(cdi_mgr->d_entry); + cdi_mgr->d_entry = NULL; + return -ENOMEM; +} + +int cdi_mgr_debugfs_remove(struct cdi_mgr_priv *cdi_mgr) +{ + if (cdi_mgr->d_entry == NULL) + return 0; + debugfs_remove_recursive(cdi_mgr->d_entry); + cdi_mgr->d_entry = NULL; + return 0; +} + +static irqreturn_t cdi_mgr_isr(int irq, void *data) +{ + struct cdi_mgr_priv *cdi_mgr; + int ret; + unsigned long flags; + int i = 0, gpio_mask = 0; + + if (data) { + cdi_mgr = (struct cdi_mgr_priv *)data; + + spin_lock_irqsave(&cdi_mgr->spinlock, flags); + for (i = 0; i < cdi_mgr->gpio_count; i++) { + if (irq == cdi_mgr->gpio_arr[i].gpio_intr_irq) + gpio_mask |= (1 << cdi_mgr->gpio_arr[i].index); + } + spin_unlock_irqrestore(&cdi_mgr->spinlock, flags); + cdi_mgr->err_irq_recvd_status_mask = gpio_mask; + wake_up_interruptible(&cdi_mgr->err_queue); + + spin_lock_irqsave(&cdi_mgr->spinlock, flags); + if (cdi_mgr->sinfo.si_signo && cdi_mgr->t) { + /* send the signal to user space */ + ret = send_sig_info(cdi_mgr->sinfo.si_signo, + &cdi_mgr->sinfo, + cdi_mgr->t); + if (ret < 0) { + pr_err("error sending signal\n"); + spin_unlock_irqrestore(&cdi_mgr->spinlock, + flags); + return IRQ_HANDLED; + } + } + spin_unlock_irqrestore(&cdi_mgr->spinlock, flags); + } + + return IRQ_HANDLED; +} + +int cdi_delete_lst(struct device *dev, struct i2c_client *client) +{ + struct cdi_mgr_priv *cdi_mgr; + struct cdi_mgr_client *cdi_dev; + + if (dev == NULL) + return -EFAULT; + + cdi_mgr = (struct cdi_mgr_priv *)dev_get_drvdata(dev); + + mutex_lock(&cdi_mgr->mutex); + list_for_each_entry(cdi_dev, &cdi_mgr->dev_list, list) { + if (cdi_dev->client == client) { + list_del(&cdi_dev->list); + break; + } + } + mutex_unlock(&cdi_mgr->mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(cdi_delete_lst); + +static int cdi_remove_dev(struct cdi_mgr_priv *cdi_mgr, unsigned long arg) +{ + struct cdi_mgr_client *cdi_dev; + + dev_dbg(cdi_mgr->dev, "%s %ld\n", __func__, arg); + mutex_lock(&cdi_mgr->mutex); + list_for_each_entry(cdi_dev, &cdi_mgr->dev_list, list) { + if (cdi_dev->id == arg) { + list_del(&cdi_dev->list); + break; + } + } + mutex_unlock(&cdi_mgr->mutex); + + if (&cdi_dev->list != &cdi_mgr->dev_list) + i2c_unregister_device(cdi_dev->client); + else + dev_err(cdi_mgr->dev, "%s: list %lx un-exist\n", __func__, arg); + + return 0; +} + +static int __cdi_create_dev( + struct cdi_mgr_priv *cdi_mgr, struct cdi_mgr_new_dev *new_dev) +{ + struct cdi_mgr_client *cdi_dev; + struct i2c_board_info brd; + int err = 0; + + if (new_dev->addr >= 0x80 || new_dev->drv_name[0] == '\0' || + (new_dev->val_bits != 8 && new_dev->val_bits != 16) || + (new_dev->reg_bits != 0 && new_dev->reg_bits != 8 && + new_dev->reg_bits != 16)) { + dev_err(cdi_mgr->dev, + "%s: invalid cdi dev params: %s %x %d %d\n", + __func__, new_dev->drv_name, new_dev->addr, + new_dev->reg_bits, new_dev->val_bits); + return -EINVAL; + } + + cdi_dev = devm_kzalloc(cdi_mgr->dev, sizeof(*cdi_dev), GFP_KERNEL); + if (!cdi_dev) { + dev_err(cdi_mgr->dev, "Unable to allocate memory!\n"); + return -ENOMEM; + } + + memcpy(&cdi_dev->cfg, new_dev, sizeof(cdi_dev->cfg)); + dev_dbg(cdi_mgr->pdev, "%s - %s @ %x, %d %d\n", __func__, + cdi_dev->cfg.drv_name, cdi_dev->cfg.addr, + cdi_dev->cfg.reg_bits, cdi_dev->cfg.val_bits); + + cdi_dev->pdata.drv_name[sizeof(cdi_dev->pdata.drv_name) - 1] = '\0'; + cdi_dev->cfg.drv_name[sizeof(cdi_dev->cfg.drv_name) - 1] = '\0'; + err = snprintf(cdi_dev->pdata.drv_name, sizeof(cdi_dev->pdata.drv_name) - 1, + "%s.%u.%02x", cdi_dev->cfg.drv_name, + cdi_mgr->adap->nr, cdi_dev->cfg.addr); + + if (err < 0) { + dev_err(cdi_mgr->dev, "output error: %d", err); + goto dev_create_err; + } + + cdi_dev->pdata.reg_bits = cdi_dev->cfg.reg_bits; + cdi_dev->pdata.val_bits = cdi_dev->cfg.val_bits; + cdi_dev->pdata.pdev = cdi_mgr->dev; + cdi_dev->pdata.np = cdi_mgr->pdev->of_node; + + mutex_init(&cdi_dev->mutex); + INIT_LIST_HEAD(&cdi_dev->list); + + memset(&brd, 0, sizeof(brd)); + strncpy(brd.type, "cdi-dev", sizeof(brd.type)); + brd.addr = cdi_dev->cfg.addr; + brd.platform_data = &cdi_dev->pdata; + cdi_dev->client = i2c_new_client_device(cdi_mgr->adap, &brd); + if (!cdi_dev->client) { + dev_err(cdi_mgr->dev, + "%s cannot allocate client: %s bus %d, %x\n", __func__, + cdi_dev->pdata.drv_name, cdi_mgr->adap->nr, brd.addr); + err = -EINVAL; + goto dev_create_err; + } + + mutex_lock(&cdi_mgr->mutex); + if (!list_empty(&cdi_mgr->dev_list)) + cdi_dev->id = list_entry(cdi_mgr->dev_list.next, + struct cdi_mgr_client, list)->id + 1; + list_add(&cdi_dev->list, &cdi_mgr->dev_list); + mutex_unlock(&cdi_mgr->mutex); + + return cdi_dev->id; + +dev_create_err: + devm_kfree(cdi_mgr->dev, cdi_dev); + return err; +} + +static int cdi_create_dev(struct cdi_mgr_priv *cdi_mgr, const void __user *arg) +{ + struct cdi_mgr_new_dev d_cfg; + + if (copy_from_user(&d_cfg, arg, sizeof(d_cfg))) { + dev_err(cdi_mgr->pdev, + "%s: failed to copy from user\n", __func__); + return -EFAULT; + } + + return __cdi_create_dev(cdi_mgr, &d_cfg); +} + +static int cdi_mgr_write_pid(struct file *file, const void __user *arg) +{ + struct cdi_mgr_priv *cdi_mgr = file->private_data; + struct cdi_mgr_sinfo sinfo; + unsigned long flags; + + if (copy_from_user(&sinfo, arg, sizeof(sinfo))) { + dev_err(cdi_mgr->pdev, + "%s: failed to copy from user\n", __func__); + return -EFAULT; + } + + if (cdi_mgr->sinfo.si_int) { + dev_err(cdi_mgr->pdev, "exist signal info\n"); + return -EINVAL; + } + + if ((sinfo.sig_no < SIGRTMIN) || (sinfo.sig_no > SIGRTMAX)) { + dev_err(cdi_mgr->pdev, "Invalid signal number\n"); + return -EINVAL; + } + + if (!sinfo.pid) { + dev_err(cdi_mgr->pdev, "Invalid PID\n"); + return -EINVAL; + } + + spin_lock_irqsave(&cdi_mgr->spinlock, flags); + cdi_mgr->sinfo.si_signo = cdi_mgr->sig_no = sinfo.sig_no; + cdi_mgr->sinfo.si_code = SI_QUEUE; + cdi_mgr->sinfo.si_ptr = (void __user *)((unsigned long)sinfo.context); + spin_unlock_irqrestore(&cdi_mgr->spinlock, flags); + + rcu_read_lock(); + cdi_mgr->t = pid_task(find_pid_ns(sinfo.pid, &init_pid_ns), + PIDTYPE_PID); + if (cdi_mgr->t == NULL) { + dev_err(cdi_mgr->pdev, "no such pid\n"); + rcu_read_unlock(); + return -ENODEV; + } + rcu_read_unlock(); + + return 0; +} + +static int cdi_mgr_get_pwr_info(struct cdi_mgr_priv *cdi_mgr, + void __user *arg) +{ + struct cdi_mgr_platform_data *pd = cdi_mgr->pdata; + struct cdi_mgr_pwr_info pinfo; + int err; + + if (copy_from_user(&pinfo, arg, sizeof(pinfo))) { + dev_err(cdi_mgr->pdev, + "%s: failed to copy from user\n", __func__); + return -EFAULT; + } + + if (!pd->num_pwr_gpios) { + dev_err(cdi_mgr->pdev, + "%s: no power gpios\n", __func__); + pinfo.pwr_status = -1; + err = -ENODEV; + goto pwr_info_end; + } + + if (pinfo.pwr_gpio >= pd->num_pwr_gpios || pinfo.pwr_gpio < 0) { + dev_err(cdi_mgr->pdev, + "%s: invalid power gpio provided\n", __func__); + pinfo.pwr_status = -1; + err = -EINVAL; + goto pwr_info_end; + } + + pinfo.pwr_gpio = array_index_nospec(pinfo.pwr_gpio, pd->num_pwr_gpios); + + pinfo.pwr_status = gpio_get_value(pd->pwr_gpios[pinfo.pwr_gpio]); + err = 0; + +pwr_info_end: + if (copy_to_user(arg, &pinfo, sizeof(pinfo))) { + dev_err(cdi_mgr->pdev, + "%s: failed to copy to user\n", __func__); + return -EFAULT; + } + return err; +} + +static int cdi_mgr_get_pwr_ctrl_info(struct cdi_mgr_priv *cdi_mgr, + void __user *arg) +{ + struct cdi_mgr_pwr_ctrl_info pinfo; + int err = 0; + + if (copy_from_user(&pinfo, arg, sizeof(pinfo))) { + dev_err(cdi_mgr->pdev, + "%s: failed to copy from user\n", __func__); + return -EFAULT; + } + + pinfo.des_pwr_method = cdi_mgr->des_pwr_method; + pinfo.des_pwr_i2c_addr = cdi_mgr->des_pwr_i2c_addr; + + if (copy_to_user(arg, &pinfo, sizeof(pinfo))) { + dev_err(cdi_mgr->pdev, + "%s: failed to copy to user\n", __func__); + return -EFAULT; + } + return err; +} + +int cdi_mgr_power_up(struct cdi_mgr_priv *cdi_mgr, unsigned long arg) +{ + struct cdi_mgr_platform_data *pd = cdi_mgr->pdata; + int i; + u32 pwr_gpio; + + dev_dbg(cdi_mgr->pdev, "%s - %lu\n", __func__, arg); + + if (!pd->num_pwr_gpios) + goto pwr_up_end; + + if (arg >= MAX_CDI_GPIOS) + arg = MAX_CDI_GPIOS - 1; + + arg = array_index_nospec(arg, MAX_CDI_GPIOS); + pwr_gpio = pd->pwr_mapping[arg]; + + if (pwr_gpio < pd->num_pwr_gpios) { + pwr_gpio = array_index_nospec(pwr_gpio, pd->num_pwr_gpios); + gpio_set_value(pd->pwr_gpios[pwr_gpio], + PW_ON(pd->pwr_flags[pwr_gpio])); + cdi_mgr->pwr_state |= BIT(pwr_gpio); + return 0; + } + + for (i = 0; i < pd->num_pwr_gpios; i++) { + dev_dbg(cdi_mgr->pdev, " - %d, %d\n", + pd->pwr_gpios[i], PW_ON(pd->pwr_flags[i])); + gpio_set_value(pd->pwr_gpios[i], PW_ON(pd->pwr_flags[i])); + cdi_mgr->pwr_state |= BIT(i); + } + +pwr_up_end: + return 0; +} + +int cdi_mgr_power_down(struct cdi_mgr_priv *cdi_mgr, unsigned long arg) +{ + struct cdi_mgr_platform_data *pd = cdi_mgr->pdata; + int i; + u32 pwr_gpio; + + dev_dbg(cdi_mgr->pdev, "%s - %lx\n", __func__, arg); + + if (!pd->num_pwr_gpios) + goto pwr_dn_end; + + if (arg >= MAX_CDI_GPIOS) + arg = MAX_CDI_GPIOS - 1; + + arg = array_index_nospec(arg, MAX_CDI_GPIOS); + + pwr_gpio = pd->pwr_mapping[arg]; + + if (pwr_gpio < pd->num_pwr_gpios) { + pwr_gpio = array_index_nospec(pwr_gpio, pd->num_pwr_gpios); + gpio_set_value(pd->pwr_gpios[pwr_gpio], + PW_OFF(pd->pwr_flags[pwr_gpio])); + cdi_mgr->pwr_state &= ~BIT(pwr_gpio); + return 0; + } + + for (i = 0; i < pd->num_pwr_gpios; i++) { + dev_dbg(cdi_mgr->pdev, " - %d, %d\n", + pd->pwr_gpios[i], PW_OFF(pd->pwr_flags[i])); + gpio_set_value(pd->pwr_gpios[i], PW_OFF(pd->pwr_flags[i])); + cdi_mgr->pwr_state &= ~BIT(i); + } + mdelay(7); + +pwr_dn_end: + return 0; +} + +static int cdi_mgr_mcdi_ctrl(struct cdi_mgr_priv *cdi_mgr, bool mcdi_on) +{ + struct cdi_mgr_platform_data *pd = cdi_mgr->pdata; + int err, i; + + dev_dbg(cdi_mgr->pdev, "%s - %s\n", __func__, mcdi_on ? "ON" : "OFF"); + + if (!pd->num_mcdi_gpios) + return 0; + + for (i = 0; i < pd->num_mcdi_gpios; i++) { + if (mcdi_on) { + if (devm_gpio_request(cdi_mgr->pdev, + pd->mcdi_gpios[i], + "mcdi-gpio")) { + dev_err(cdi_mgr->pdev, "failed req GPIO: %d\n", + pd->mcdi_gpios[i]); + goto mcdi_ctrl_err; + } + + err = gpio_direction_output( + pd->mcdi_gpios[i], PW_ON(pd->mcdi_flags[i])); + } else { + err = gpio_direction_output( + pd->mcdi_gpios[i], PW_OFF(pd->mcdi_flags[i])); + devm_gpio_free(cdi_mgr->pdev, pd->mcdi_gpios[i]); + } + } + return 0; + +mcdi_ctrl_err: + for (; i >= 0; i--) + devm_gpio_free(cdi_mgr->pdev, pd->mcdi_gpios[i]); + return -EBUSY; +} + +static int cdi_mgr_pwm_enable( + struct cdi_mgr_priv *cdi_mgr, unsigned long arg) +{ + int err = 0; + + if (!cdi_mgr || !cdi_mgr->pwm) + return -EINVAL; + + switch (arg) { + case CDI_MGR_PWM_ENABLE: + err = pwm_enable(cdi_mgr->pwm); + break; + case CDI_MGR_PWM_DISABLE: + pwm_disable(cdi_mgr->pwm); + break; + default: + dev_err(cdi_mgr->pdev, "%s unrecognized command: %lx\n", + __func__, arg); + } + + return err; +} + +static int cdi_mgr_pwm_config( + struct cdi_mgr_priv *cdi_mgr, const void __user *arg) +{ + struct cdi_mgr_pwm_info pwm_cfg; + int err = 0; + + if (!cdi_mgr || !cdi_mgr->pwm) + return -EINVAL; + + if (copy_from_user(&pwm_cfg, arg, sizeof(pwm_cfg))) { + dev_err(cdi_mgr->pdev, + "%s: failed to copy from user\n", __func__); + return -EFAULT; + } + + err = pwm_config(cdi_mgr->pwm, pwm_cfg.duty_ns, pwm_cfg.period_ns); + + return err; +} + +static int cdi_mgr_wait_err( + struct cdi_mgr_priv *cdi_mgr, void __user *arg) +{ + int err = 0, i = 0; + uint32_t gpio_irq_monitor_mask = 0; + uint32_t gpio_irq_status_mask = 0; + + if (!atomic_xchg(&cdi_mgr->irq_in_use, 1)) { + for (i = 0; i < cdi_mgr->gpio_count; i++) { + if ((cdi_mgr->gpio_arr[i].gpio_dir == CAM_DEVBLK_GPIO_INPUT_INTERRUPT) && + cdi_mgr->gpio_arr[i].gpio_intr_irq >= 0) + enable_irq(cdi_mgr->gpio_arr[i].gpio_intr_irq); + } + cdi_mgr->err_irq_recvd_status_mask = 0; + cdi_mgr->stop_err_irq_wait = false; + } + + if (get_user(gpio_irq_monitor_mask, (uint32_t __user *)arg)) { + dev_err(cdi_mgr->pdev, + "%s: failed to get_user\n", __func__); + return -EFAULT; + } + + do { + err = wait_event_interruptible(cdi_mgr->err_queue, + (cdi_mgr->err_irq_recvd_status_mask & gpio_irq_monitor_mask) != 0); + if (err < 0) { + dev_err(cdi_mgr->pdev, "%s: wait_event_interruptible failed\n", __func__); + break; + } + + gpio_irq_status_mask = cdi_mgr->err_irq_recvd_status_mask & gpio_irq_monitor_mask; + + if (!cdi_mgr->stop_err_irq_wait && + put_user(gpio_irq_status_mask, (u32 __user *) arg)) { + dev_err(cdi_mgr->pdev, + "%s: failed to put_user\n", __func__); + return -EFAULT; + } + cdi_mgr->err_irq_recvd_status_mask = 0; + } while (cdi_mgr->err_irq_reported == false); + + return err; +} + +static long cdi_mgr_ioctl( + struct file *file, unsigned int cmd, unsigned long arg) +{ + struct cdi_mgr_priv *cdi_mgr = file->private_data; + int err = 0, i = 0; + unsigned long flags; + + /* command distributor */ + switch (cmd) { + case CDI_MGR_IOCTL_DEV_ADD: + err = cdi_create_dev(cdi_mgr, (const void __user *)arg); + break; + case CDI_MGR_IOCTL_DEV_DEL: + cdi_remove_dev(cdi_mgr, arg); + break; + case CDI_MGR_IOCTL_PWR_DN: + err = cdi_mgr_power_down(cdi_mgr, arg); + break; + case CDI_MGR_IOCTL_PWR_UP: + err = cdi_mgr_power_up(cdi_mgr, arg); + break; + case CDI_MGR_IOCTL_SET_PID: + /* first enable irq to clear pending interrupt + * and then register PID + */ + if (!atomic_xchg(&cdi_mgr->irq_in_use, 1)) { + for (i = 0; i < cdi_mgr->gpio_count; i++) { + if ((cdi_mgr->gpio_arr[i].gpio_dir == + CAM_DEVBLK_GPIO_INPUT_INTERRUPT) && + cdi_mgr->gpio_arr[i].gpio_intr_irq >= 0) + enable_irq(cdi_mgr->gpio_arr[i].gpio_intr_irq); + } + } + + err = cdi_mgr_write_pid(file, (const void __user *)arg); + break; + case CDI_MGR_IOCTL_SIGNAL: + switch (arg) { + case CDI_MGR_SIGNAL_RESUME: + if (!cdi_mgr->sig_no) { + dev_err(cdi_mgr->pdev, + "invalid sig_no, setup pid first\n"); + return -EINVAL; + } + spin_lock_irqsave(&cdi_mgr->spinlock, flags); + cdi_mgr->sinfo.si_signo = cdi_mgr->sig_no; + spin_unlock_irqrestore(&cdi_mgr->spinlock, flags); + break; + case CDI_MGR_SIGNAL_SUSPEND: + spin_lock_irqsave(&cdi_mgr->spinlock, flags); + cdi_mgr->sinfo.si_signo = 0; + spin_unlock_irqrestore(&cdi_mgr->spinlock, flags); + break; + default: + dev_err(cdi_mgr->pdev, "%s unrecognized signal: %lx\n", + __func__, arg); + } + break; + case CDI_MGR_IOCTL_PWR_INFO: + err = cdi_mgr_get_pwr_info(cdi_mgr, (void __user *)arg); + break; + case CDI_MGR_IOCTL_PWM_ENABLE: + err = cdi_mgr_pwm_enable(cdi_mgr, arg); + break; + case CDI_MGR_IOCTL_PWM_CONFIG: + err = cdi_mgr_pwm_config(cdi_mgr, (const void __user *)arg); + break; + case CDI_MGR_IOCTL_WAIT_ERR: + err = cdi_mgr_wait_err(cdi_mgr, (void __user *)arg); + break; + case CDI_MGR_IOCTL_ABORT_WAIT_ERR: + cdi_mgr->err_irq_recvd_status_mask = CDI_MGR_STOP_GPIO_INTR_EVENT_WAIT; + cdi_mgr->err_irq_reported = true; + cdi_mgr->stop_err_irq_wait = true; + wake_up_interruptible(&cdi_mgr->err_queue); + break; + case CDI_MGR_IOCTL_ENABLE_ERROR_REPORT: + cdi_mgr->err_irq_reported = true; + break; + case CDI_MGR_IOCTL_GET_PWR_INFO: + err = cdi_mgr_get_pwr_ctrl_info(cdi_mgr, (void __user *)arg); + break; + default: + dev_err(cdi_mgr->pdev, "%s unsupported ioctl: %x\n", + __func__, cmd); + err = -EINVAL; + } + + if (err) + dev_dbg(cdi_mgr->pdev, "err = %d\n", err); + + return err; +} + +static struct semaphore tca9539_sem; + +static int cdi_mgr_open(struct inode *inode, struct file *file) +{ + u8 val; + struct cdi_mgr_priv *cdi_mgr = container_of(inode->i_cdev, + struct cdi_mgr_priv, cdev); + + /* only one application can open one cdi_mgr device */ + if (atomic_xchg(&cdi_mgr->in_use, 1)) + return -EBUSY; + + cdi_mgr->err_irq_reported = false; + + dev_dbg(cdi_mgr->pdev, "%s\n", __func__); + file->private_data = cdi_mgr; + + /* if runtime_pwrctrl_off is not true, power on all here */ + if (!cdi_mgr->pdata->runtime_pwrctrl_off) + cdi_mgr_power_up(cdi_mgr, 0xffffffff); + + cdi_mgr_mcdi_ctrl(cdi_mgr, true); + + if (cdi_mgr->tca9539.enable) { + if (down_timeout(&tca9539_sem, + usecs_to_jiffies(TIMEOUT_US)) != 0) + dev_err(cdi_mgr->dev, + "%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) + return -EFAULT; + val |= (0x10 << cdi_mgr->tca9539.power_port); + if (tca9539_raw_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) + return -EFAULT; + val |= (0x1 << cdi_mgr->tca9539.power_port); + if (tca9539_raw_wr(cdi_mgr, 0x03, val) != 0) + return -EFAULT; + } + up(&tca9539_sem); + } + + return 0; +} + +static int cdi_mgr_release(struct inode *inode, struct file *file) +{ + u8 val; + int i = 0; + struct cdi_mgr_priv *cdi_mgr = file->private_data; + + cdi_mgr->err_irq_reported = true; + if (cdi_mgr->tca9539.enable) { + if (down_timeout(&tca9539_sem, + usecs_to_jiffies(TIMEOUT_US)) != 0) + dev_err(cdi_mgr->dev, + "%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) + return -EFAULT; + val &= ~(0x10 << cdi_mgr->tca9539.power_port); + if (tca9539_raw_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) + return -EFAULT; + val &= ~(0x1 << cdi_mgr->tca9539.power_port); + if (tca9539_raw_wr(cdi_mgr, 0x03, val) != 0) + return -EFAULT; + } + up(&tca9539_sem); + } + + if (cdi_mgr->pwm) + if (pwm_is_enabled(cdi_mgr->pwm)) + pwm_disable(cdi_mgr->pwm); + + cdi_mgr_mcdi_ctrl(cdi_mgr, false); + + /* disable irq if irq is in use, when device is closed */ + if (atomic_xchg(&cdi_mgr->irq_in_use, 0)) { + for (i = 0; i < cdi_mgr->gpio_count; i++) { + if ((cdi_mgr->gpio_arr[i].gpio_dir == CAM_DEVBLK_GPIO_INPUT_INTERRUPT) && + cdi_mgr->gpio_arr[i].gpio_intr_irq >= 0) + disable_irq(cdi_mgr->gpio_arr[i].gpio_intr_irq); + } + cdi_mgr->err_irq_recvd_status_mask = CDI_MGR_STOP_GPIO_INTR_EVENT_WAIT; + cdi_mgr->stop_err_irq_wait = true; + wake_up_interruptible(&cdi_mgr->err_queue); + } + + /* if runtime_pwrctrl_off is not true, power off all here */ + if (!cdi_mgr->pdata->runtime_pwrctrl_off) + cdi_mgr_power_down(cdi_mgr, 0xffffffff); + + /* clear sinfo to prevent report error after handler is closed */ + memset(&cdi_mgr->sinfo, 0, sizeof(struct kernel_siginfo)); + cdi_mgr->t = NULL; + WARN_ON(!atomic_xchg(&cdi_mgr->in_use, 0)); + + return 0; +} + +static const struct file_operations cdi_mgr_fileops = { + .owner = THIS_MODULE, + .open = cdi_mgr_open, + .unlocked_ioctl = cdi_mgr_ioctl, + .release = cdi_mgr_release, +}; + +static void cdi_mgr_del(struct cdi_mgr_priv *cdi_mgr) +{ + struct cdi_mgr_platform_data *pd = cdi_mgr->pdata; + struct cdi_mgr_client *cdi_dev = NULL; + int i; + + mutex_lock(&cdi_mgr->mutex); + list_for_each_entry(cdi_dev, &cdi_mgr->dev_list, list) { + /* remove i2c_clients that cdi-mgr created */ + if (cdi_dev->client != NULL) { + i2c_unregister_device(cdi_dev->client); + cdi_dev->client = NULL; + } + } + mutex_unlock(&cdi_mgr->mutex); + + for (i = 0; i < pd->num_pwr_gpios; i++) + if (pd->pwr_gpios[i]) + gpio_direction_output( + pd->pwr_gpios[i], PW_OFF(pd->pwr_flags[i])); + + if (cdi_mgr->tca9539.enable) + i2c_put_adapter(cdi_mgr->tca9539.adap); + i2c_put_adapter(cdi_mgr->adap); + + for (i = 0; i < MAX_CDI_GPIOS; i++) { + if (cdi_mgr->gpio_arr[i].desc) + devm_gpiod_put(cdi_mgr->dev, cdi_mgr->gpio_arr[i].desc); + } +} + +static void cdi_mgr_dev_ins(struct work_struct *work) +{ + struct cdi_mgr_priv *cdi_mgr = + container_of(work, struct cdi_mgr_priv, ins_work); + struct device_node *np = cdi_mgr->pdev->of_node; + struct device_node *subdev; + struct cdi_mgr_new_dev d_cfg = {.drv_name = "cdi-dev"}; + const char *sname; + u32 val; + int err = 0; + + if (np == NULL) + return; + + dev_dbg(cdi_mgr->dev, "%s - %s\n", __func__, np->full_name); + sname = of_get_property(np, "cdi-dev", NULL); + if (sname) + strncpy(d_cfg.drv_name, sname, sizeof(d_cfg.drv_name) - 8); + + for_each_child_of_node(np, subdev) { + err = of_property_read_u32(subdev, "addr", &val); + if (err || !val) + continue; + + d_cfg.addr = val; + err = of_property_read_u32(subdev, "reg_len", &val); + if (err || !val) + continue; + + d_cfg.reg_bits = val; + err = of_property_read_u32(subdev, "dat_len", &val); + if (err || !val) + continue; + + d_cfg.val_bits = val; + + __cdi_create_dev(cdi_mgr, &d_cfg); + } +} + +static int cdi_mgr_of_get_grp_gpio( + struct device *dev, struct device_node *np, + const char *name, int size, u32 *grp, u32 *flags) +{ + int num, i; + + num = of_gpio_named_count(np, name); + dev_dbg(dev, " num gpios of %s: %d\n", name, num); + if (num < 0) + return 0; + + for (i = 0; (i < num) && (i < size); i++) { + grp[i] = of_get_named_gpio_flags(np, name, i, &flags[i]); + if ((int)grp[i] < 0) { + dev_err(dev, "%s: gpio[%d] invalid\n", __func__, i); + return -EINVAL; + } + dev_dbg(dev, " [%d] - %d %x\n", i, grp[i], flags[i]); + } + + return num; +} + +static int cdi_mgr_get_pwr_map( + struct device *dev, struct device_node *np, + struct cdi_mgr_platform_data *pd) +{ + int num_map_items = 0; + u32 pwr_map_val; + unsigned int i; + + for (i = 0; i < MAX_CDI_GPIOS; i++) + pd->pwr_mapping[i] = i; + + if (!of_get_property(np, "pwr-items", NULL)) + return 0; + + num_map_items = of_property_count_elems_of_size(np, + "pwr-items", sizeof(u32)); + if (num_map_items < 0) { + dev_err(dev, "%s: error processing pwr items\n", + __func__); + return -1; + } + + if (num_map_items < pd->num_pwr_gpios) { + dev_err(dev, "%s: invalid number of pwr items\n", + __func__); + return -1; + } + + for (i = 0; i < num_map_items; i++) { + if (of_property_read_u32_index(np, "pwr-items", + i, &pwr_map_val)) { + dev_err(dev, "%s: failed to get pwr item\n", + __func__); + goto pwr_map_err; + } + + if (pwr_map_val >= pd->num_pwr_gpios) { + dev_err(dev, "%s: invalid power item index provided\n", + __func__); + goto pwr_map_err; + } + pd->pwr_mapping[i] = pwr_map_val; + } + + pd->num_pwr_map = num_map_items; + return 0; + +pwr_map_err: + for (i = 0; i < MAX_CDI_GPIOS; i++) + pd->pwr_mapping[i] = i; + + pd->num_pwr_map = pd->num_pwr_gpios; + + return -1; +} + +static struct cdi_mgr_platform_data *of_cdi_mgr_pdata(struct platform_device + *pdev) +{ + struct device_node *np = pdev->dev.of_node, *child_np = NULL; + struct cdi_mgr_platform_data *pd = NULL; + int err; + bool ext_pwr_ctrl_des = false, ext_pwr_ctrl_sensor = false; + + dev_dbg(&pdev->dev, "%s\n", __func__); + pd = devm_kzalloc(&pdev->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) { + dev_err(&pdev->dev, "%s: allocate memory error\n", __func__); + return ERR_PTR(-ENOMEM); + } + + child_np = of_get_child_by_name(np, "tegra"); + if (child_np == NULL) { + dev_err(&pdev->dev, "%s: missing tegra node # DT %s\n", + __func__, np->full_name); + return ERR_PTR(-EEXIST); + } + + pd->drv_name = (void *)of_get_property(np, "drv_name", NULL); + if (pd->drv_name) + dev_dbg(&pdev->dev, " drvname: %s\n", pd->drv_name); + + err = of_property_read_u32(child_np, "i2c-bus", &pd->bus); + if (err) { + dev_err(&pdev->dev, "%s: missing i2c bus # DT %s\n", + __func__, child_np->full_name); + return ERR_PTR(-EEXIST); + } + dev_dbg(&pdev->dev, " i2c-bus: %d\n", pd->bus); + + err = of_property_read_u32(child_np, "csi-port", &pd->csi_port); + if (err) { + dev_err(&pdev->dev, "%s: missing csi port # DT %s\n", + __func__, child_np->full_name); + return ERR_PTR(-EEXIST); + } + dev_dbg(&pdev->dev, " csiport: %d\n", pd->csi_port); + + pd->num_pwr_gpios = cdi_mgr_of_get_grp_gpio( + &pdev->dev, child_np, "pwdn-gpios", + ARRAY_SIZE(pd->pwr_gpios), pd->pwr_gpios, pd->pwr_flags); + if (pd->num_pwr_gpios < 0) + return ERR_PTR(pd->num_pwr_gpios); + + pd->num_mcdi_gpios = cdi_mgr_of_get_grp_gpio( + &pdev->dev, child_np, "mcdi-gpios", + ARRAY_SIZE(pd->mcdi_gpios), pd->mcdi_gpios, pd->mcdi_flags); + if (pd->num_mcdi_gpios < 0) + return ERR_PTR(pd->num_mcdi_gpios); + + child_np = of_get_child_by_name(np, "pwr_ctrl"); + if (child_np == NULL) { + dev_err(&pdev->dev, "%s: missing pwr_ctrl node # DT %s\n", + __func__, np->full_name); + return ERR_PTR(-EEXIST); + } + + pd->default_pwr_on = of_property_read_bool(child_np, + "default-power-on"); + pd->runtime_pwrctrl_off = + of_property_read_bool(child_np, "runtime-pwrctrl-off"); + + pd->ext_pwr_ctrl = 0; + ext_pwr_ctrl_des = + of_property_read_bool(child_np, "ext-pwr-ctrl-deserializer"); + if (ext_pwr_ctrl_des == true) + pd->ext_pwr_ctrl |= 1 << 0; + ext_pwr_ctrl_sensor = of_property_read_bool(child_np, + "ext-pwr-ctrl-sensor"); + if (ext_pwr_ctrl_sensor == true) + pd->ext_pwr_ctrl |= 1 << 1; + + err = cdi_mgr_get_pwr_map(&pdev->dev, child_np, pd); + if (err) + dev_err(&pdev->dev, + "%s: failed to map pwr items. Using default values\n", + __func__); + + return pd; +} + +static char *cdi_mgr_devnode(struct device *dev, umode_t *mode) +{ + if (!mode) + return NULL; + + /* set alway user to access this device */ + *mode = 0666; + + return NULL; +} + +static int cdi_mgr_suspend(struct device *dev) +{ + unsigned int reg_addr; + int rc; + struct cdi_mgr_priv *cdi_mgr; + + cdi_mgr = (struct cdi_mgr_priv *)dev_get_drvdata(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, + &cdi_mgr->pre_suspend_tca9539_regvals[reg_addr]); + if (rc != 0) { + dev_err(dev, "%s: tca9539_raw_rd failed reading reg[0x%x]\n", + __func__, reg_addr); + return -EIO; + } + + ++reg_addr; + } + } + + return 0; +} + +static int cdi_mgr_resume(struct device *dev) +{ + struct pwm_device *pwm; + struct cdi_mgr_priv *cdi_mgr; + unsigned int reg_addr; + int rc; + + /* Reconfigure PWM as done during boot time */ + if (of_property_read_bool(dev->of_node, "pwms")) { + pwm = devm_pwm_get(dev, NULL); + if (!IS_ERR(pwm)) + dev_info(dev, "%s Resume successful\n", __func__); + } + + cdi_mgr = (struct cdi_mgr_priv *)dev_get_drvdata(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, + 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", + __func__, reg_addr, + cdi_mgr->pre_suspend_tca9539_regvals[reg_addr]); + return -EIO; + } + ++reg_addr; + } + } + + return 0; +} + +static const struct dev_pm_ops cdi_mgr_pm_ops = { + .suspend = cdi_mgr_suspend, + .resume = cdi_mgr_resume, + .runtime_suspend = cdi_mgr_suspend, + .runtime_resume = cdi_mgr_resume, +}; + +static int cdi_mgr_setup_gpio_interrupt(struct device *dev, struct cdi_mgr_priv *cdi_mgr, + uint32_t idx, uint32_t gpio_idx, uint32_t intr_edge) +{ + int ret = 0; + int gpio_irq = 0; + + ret = gpiod_direction_input(cdi_mgr->gpio_arr[idx].desc); + if (ret) { + dev_err(dev, "%s Failed to gpio direction : input 0\n", + __func__); + return ret; + } + + gpio_irq = gpiod_to_irq(cdi_mgr->gpio_arr[idx].desc); + if (gpio_irq < 0) { + dev_err(dev, "gpiod_to_irq() failed: %d\n", gpio_irq); + return gpio_irq; + } + + cdi_mgr->gpio_arr[idx].gpio_intr_irq = gpio_irq; + ret = devm_request_irq(dev, + cdi_mgr->gpio_arr[idx].gpio_intr_irq, + cdi_mgr_isr, intr_edge, dev_name(dev), cdi_mgr); + if (ret) { + dev_err(dev, "devm_request_irq failed with err %d\n", ret); + return ret; + } + disable_irq(cdi_mgr->gpio_arr[idx].gpio_intr_irq); + atomic_set(&cdi_mgr->irq_in_use, 0); + + cdi_mgr->gpio_arr[idx].gpio_dir = CAM_DEVBLK_GPIO_INPUT_INTERRUPT; + cdi_mgr->gpio_arr[idx].index = gpio_idx; + + return 0; +} + +static int cdi_mgr_configure_gpios(struct device *dev, struct cdi_mgr_priv *cdi_mgr) +{ + struct device_node *node = NULL; + int gpio_count = 0; + uint32_t i = 0, j = 0; + int ret = 0; + + node = of_get_child_by_name(dev->of_node, "tegra"); + + if (node != NULL) { + node = of_get_child_by_name(node, "gpios"); + + if (node != NULL) { + struct device_node *child = NULL; + + gpio_count = of_get_child_count(node); + if (!gpio_count || gpio_count > MAX_CDI_GPIOS) { + dev_err(dev, "%s Invalid number of gpios : %d\n", + __func__, gpio_count); + return -EINVAL; + } + dev_dbg(dev, "%s gpio node count : %d\n", __func__, gpio_count); + + for_each_child_of_node(node, child) { + uint32_t gpio_index = 0; + + if (of_property_read_u32(child, "index", &gpio_index)) { + dev_err(dev, "%s \"index\" dt property not found\n", + __func__); + return -ENOENT; + } + + if (gpio_index >= MAX_CDI_GPIOS) { + dev_err(dev, "%s Invalid gpios index: %d," + " valid value is below %d\n", + __func__, gpio_index, MAX_CDI_GPIOS); + return -EINVAL; + } + + for (j = 0; j < cdi_mgr->gpio_count; j++) { + if (cdi_mgr->gpio_arr[j].index == gpio_index) { + dev_err(dev, "%s GPIO already in use\n", __func__); + return -EPERM; + } + } + + cdi_mgr->gpio_arr[i].desc = devm_fwnode_get_gpiod_from_child(dev, + "devblk", &child->fwnode, GPIOD_ASIS, NULL); + if (IS_ERR(cdi_mgr->gpio_arr[i].desc)) { + ret = PTR_ERR(cdi_mgr->gpio_arr[i].desc); + if (ret < 0) + dev_err(dev, "%s Failed to allocate gpio desc\n", + __func__); + return ret; + } + + if (of_property_read_bool(child, "intr-edge-falling")) { + ret = cdi_mgr_setup_gpio_interrupt(dev, cdi_mgr, i, + gpio_index, IRQF_TRIGGER_FALLING); + if (ret < 0) { + dev_err(dev, "%s():%d Failed to setup input" + "interrupt gpio\n", + __func__, __LINE__); + return ret; + } + } else if (of_property_read_bool(child, "intr-edge-rising")) { + ret = cdi_mgr_setup_gpio_interrupt(dev, cdi_mgr, i, + gpio_index, IRQF_TRIGGER_RISING); + if (ret < 0) { + dev_err(dev, "%s():%d Failed to setup input" + " interrupt gpio\n", + __func__, __LINE__); + return ret; + } + } else { + dev_err(dev, "%s(): Invalid DT property\n", __func__); + return -EINVAL; + } + i++; + cdi_mgr->gpio_count++; + } + } + } else { + dev_err(dev, "%s \"tegra\" dt node not found\n", __func__); + return -EINVAL; + } + + return 0; +} + +static void cdi_mgr_get_cim_ver(struct device *dev, struct cdi_mgr_priv *cdi_mgr) +{ + int err = 0; + struct device_node *child = NULL; + struct device_node *cim = NULL; + const char *cim_ver; + + child = of_get_parent(dev->of_node); + 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(dev, + "CIM A01\n"); + cdi_mgr->cim_ver = 1U; + } else { + dev_info(dev, + "CIM A02\n"); + cdi_mgr->cim_ver = 2U; + if (of_property_read_u32_array(cim, + "cim_frsync_src", + cdi_mgr->cim_frsync, + sizeof(cdi_mgr->cim_frsync)/sizeof(u32))) { + memset((void *)cdi_mgr->cim_frsync, + 0U, + sizeof(cdi_mgr->cim_frsync)); + } + } + } + } + } +} + +static int cdi_mgr_probe(struct platform_device *pdev) +{ + int err = 0; + struct cdi_mgr_priv *cdi_mgr; + struct cdi_mgr_platform_data *pd; + unsigned int i; + struct device_node *child = NULL; + struct device_node *child_tca9539 = NULL; + + dev_info(&pdev->dev, "%sing...\n", __func__); + + cdi_mgr = devm_kzalloc(&pdev->dev, + sizeof(struct cdi_mgr_priv), + GFP_KERNEL); + if (!cdi_mgr) { + dev_err(&pdev->dev, "Unable to allocate memory!\n"); + return -ENOMEM; + } + + spin_lock_init(&cdi_mgr->spinlock); + atomic_set(&cdi_mgr->in_use, 0); + INIT_LIST_HEAD(&cdi_mgr->dev_list); + mutex_init(&cdi_mgr->mutex); + init_waitqueue_head(&cdi_mgr->err_queue); + cdi_mgr->err_irq_recvd_status_mask = 0; + cdi_mgr->err_irq_reported = false; + cdi_mgr->pwm = NULL; + + if (pdev->dev.of_node) { + pd = of_cdi_mgr_pdata(pdev); + if (IS_ERR(pd)) + return PTR_ERR(pd); + cdi_mgr->pdata = pd; + } else if (pdev->dev.platform_data) { + cdi_mgr->pdata = pdev->dev.platform_data; + pd = cdi_mgr->pdata; + } else { + dev_err(&pdev->dev, "%s No platform data.\n", __func__); + return -EFAULT; + } + + if (of_property_read_bool(pdev->dev.of_node, "pwms")) { + cdi_mgr->pwm = devm_pwm_get(&pdev->dev, NULL); + if (!IS_ERR(cdi_mgr->pwm)) { + dev_info(&pdev->dev, + "%s: success to get PWM\n", __func__); + pwm_disable(cdi_mgr->pwm); + } else { + err = PTR_ERR(cdi_mgr->pwm); + if (err != -EPROBE_DEFER) + dev_err(&pdev->dev, + "%s: fail to get PWM\n", __func__); + return err; + } + } + + cdi_mgr->adap = i2c_get_adapter(pd->bus); + if (!cdi_mgr->adap) { + dev_err(&pdev->dev, "%s no such i2c bus %d\n", + __func__, pd->bus); + return -ENODEV; + } + + if (pd->num_pwr_gpios > 0) { + for (i = 0; i < pd->num_pwr_gpios; i++) { + if (!gpio_is_valid(pd->pwr_gpios[i])) + goto err_probe; + + if (devm_gpio_request( + &pdev->dev, pd->pwr_gpios[i], "pwdn-gpios")) { + dev_err(&pdev->dev, "failed to req GPIO: %d\n", + pd->pwr_gpios[i]); + goto err_probe; + } + + err = gpio_direction_output(pd->pwr_gpios[i], + pd->default_pwr_on ? + PW_ON(pd->pwr_flags[i]) : + PW_OFF(pd->pwr_flags[i])); + if (err < 0) { + dev_err(&pdev->dev, "failed to setup GPIO: %d\n", + pd->pwr_gpios[i]); + i++; + goto err_probe; + } + if (pd->default_pwr_on) + cdi_mgr->pwr_state |= BIT(i); + } + } + + if (cdi_mgr_configure_gpios(&pdev->dev, cdi_mgr) < 0) { + dev_err(&pdev->dev, "%s(): GPIO setup failed\n", __func__); + goto err_probe; + } + cdi_mgr->pdev = &pdev->dev; + dev_set_drvdata(&pdev->dev, cdi_mgr); + + if (pd->drv_name) + err = snprintf(cdi_mgr->devname, sizeof(cdi_mgr->devname), + "%s.%x.%c", pd->drv_name, pd->bus, + 'a' + pd->csi_port); + else + err = snprintf(cdi_mgr->devname, sizeof(cdi_mgr->devname), + "cdi-mgr.%x.%c", pd->bus, 'a' + pd->csi_port); + + if (err < 0) { + dev_err(&pdev->dev, "encoding error: %d\n", err); + goto err_probe; + } + + /* Request dynamic allocation of a device major number */ + err = alloc_chrdev_region(&cdi_mgr->devt, + 0, CDI_DEV_MAX, cdi_mgr->devname); + if (err < 0) { + dev_err(&pdev->dev, "failed to allocate char dev region %d\n", + err); + goto err_probe; + } + + /* poluate sysfs entries */ + cdi_mgr->cdi_class = class_create(THIS_MODULE, cdi_mgr->devname); + if (IS_ERR(cdi_mgr->cdi_class)) { + err = PTR_ERR(cdi_mgr->cdi_class); + cdi_mgr->cdi_class = NULL; + dev_err(&pdev->dev, "failed to create class %d\n", + err); + goto err_probe; + } + + cdi_mgr->cdi_class->devnode = cdi_mgr_devnode; + + /* connect the file operations with the cdev */ + cdev_init(&cdi_mgr->cdev, &cdi_mgr_fileops); + cdi_mgr->cdev.owner = THIS_MODULE; + + /* connect the major/minor number to this dev */ + err = cdev_add(&cdi_mgr->cdev, MKDEV(MAJOR(cdi_mgr->devt), 0), 1); + if (err) { + dev_err(&pdev->dev, "Unable to add cdev %d\n", err); + goto err_probe; + } + /* send uevents to udev, it will create /dev node for cdi-mgr */ + cdi_mgr->dev = device_create(cdi_mgr->cdi_class, &pdev->dev, + cdi_mgr->cdev.dev, + cdi_mgr, + cdi_mgr->devname); + if (IS_ERR(cdi_mgr->dev)) { + err = PTR_ERR(cdi_mgr->dev); + cdi_mgr->dev = NULL; + dev_err(&pdev->dev, "failed to create device %d\n", err); + goto err_probe; + } + + /* Find CIM version */ + cdi_mgr_get_cim_ver(&pdev->dev, cdi_mgr); + + child = of_get_child_by_name(pdev->dev.of_node, "pwr_ctrl"); + if (child != NULL) { + if (of_property_read_bool(child, + "deserializer-pwr-gpio")) + cdi_mgr->des_pwr_method = DES_PWR_GPIO; + else if (of_property_read_bool(child, + "deserializer-pwr-nvccp")) + cdi_mgr->des_pwr_method = DES_PWR_NVCCP; + else + cdi_mgr->des_pwr_method = DES_PWR_NO_PWR; + cdi_mgr->des_pwr_i2c_addr = 0; + + /* 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", + &cdi_mgr->tca9539.bus); + if (err) + cdi_mgr->tca9539.bus = pd->bus; + err = of_property_read_u32(child_tca9539, "addr", + &cdi_mgr->tca9539.addr); + if (err || !cdi_mgr->tca9539.addr) { + dev_err(&pdev->dev, "%s: ERROR %d addr = %d\n", + __func__, err, + cdi_mgr->tca9539.addr); + goto err_probe; + } + err = of_property_read_u32(child_tca9539, "reg_len", + &cdi_mgr->tca9539.reg_len); + if (err || !cdi_mgr->tca9539.reg_len) { + dev_err(&pdev->dev, "%s: ERROR %d reg_len = %d\n", + __func__, err, + cdi_mgr->tca9539.reg_len); + goto err_probe; + } + err = of_property_read_u32(child_tca9539, "dat_len", + &cdi_mgr->tca9539.dat_len); + if (err || !cdi_mgr->tca9539.dat_len) { + dev_err(&pdev->dev, "%s: ERROR %d dat_len = %d\n", + __func__, err, + cdi_mgr->tca9539.dat_len); + goto err_probe; + } + err = of_property_read_u32(child_tca9539->parent, + "power_port", + &cdi_mgr->tca9539.power_port); + if (err) { + dev_err(&pdev->dev, "%s: ERROR %d power_port = %d\n", + __func__, err, + cdi_mgr->tca9539.power_port); + goto err_probe; + } + + cdi_mgr->tca9539.reg_len /= 8; + cdi_mgr->tca9539.dat_len /= 8; + cdi_mgr->tca9539.enable = 1; + cdi_mgr->tca9539.adap = i2c_get_adapter(cdi_mgr->tca9539.bus); + if (!cdi_mgr->tca9539.adap) { + dev_err(&pdev->dev, "%s no such i2c bus %d\n", + __func__, cdi_mgr->tca9539.bus); + goto err_probe; + } + + sema_init(&tca9539_sem, 1); + + /* Set the init values */ + /* TODO : read the array to initialize */ + /* 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) { + 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) { + 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) { + 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) { + dev_err(&pdev->dev, + "%s: ERR %d: TCA9539: Failed to select PWDN signal source\n", + __func__, err); + goto err_probe; + } + + /* Configure FRSYNC logic */ + dev_info(&pdev->dev, + "FRSYNC source: %d %d %d\n", + cdi_mgr->cim_frsync[0], + cdi_mgr->cim_frsync[1], + cdi_mgr->cim_frsync[2]); + if (tca9539_raw_wr(cdi_mgr, 0x2, + (cdi_mgr->cim_frsync[2] << 4) | + (cdi_mgr->cim_frsync[1] << 2) | + (cdi_mgr->cim_frsync[0])) < 0) { + dev_err(&pdev->dev, + "%s: ERR %d: TCA9539: Failed to set FRSYNC control logic\n", + __func__, err); + goto err_probe; + } + /* Output low for AGGA/B/C/D_PWRDN */ + if (tca9539_raw_wr(cdi_mgr, 0x3, 0x00) != 0) { + dev_err(&pdev->dev, + "%s: ERR %d: TCA9539: Failed to set the output level\n", + __func__, err); + goto err_probe; + } + } + } + } + + cdi_mgr_debugfs_init(cdi_mgr); + INIT_WORK(&cdi_mgr->ins_work, cdi_mgr_dev_ins); + schedule_work(&cdi_mgr->ins_work); + return 0; + +err_probe: + cdi_mgr_del(cdi_mgr); + return err; +} + +static int cdi_mgr_remove(struct platform_device *pdev) +{ + struct cdi_mgr_priv *cdi_mgr = dev_get_drvdata(&pdev->dev); + + if (cdi_mgr) { + cdi_mgr_debugfs_remove(cdi_mgr); + cdi_mgr_del(cdi_mgr); + + if (cdi_mgr->dev) + device_destroy(cdi_mgr->cdi_class, + cdi_mgr->cdev.dev); + if (cdi_mgr->cdev.dev) + cdev_del(&cdi_mgr->cdev); + + if (cdi_mgr->cdi_class) + class_destroy(cdi_mgr->cdi_class); + + if (cdi_mgr->devt) + unregister_chrdev_region(cdi_mgr->devt, CDI_DEV_MAX); + } + + return 0; +} + +static const struct of_device_id cdi_mgr_of_match[] = { + { .compatible = "nvidia,cdi-mgr", }, + { } +}; +MODULE_DEVICE_TABLE(of, cdi_mgr_of_match); + +static struct platform_driver cdi_mgr_driver = { + .driver = { + .name = "cdi-mgr", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(cdi_mgr_of_match), + .pm = &cdi_mgr_pm_ops, + }, + .probe = cdi_mgr_probe, + .remove = cdi_mgr_remove, +}; + +module_platform_driver(cdi_mgr_driver); + +MODULE_DESCRIPTION("tegra auto cdi manager driver"); +MODULE_AUTHOR("Songhee Baek "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cdi_mgr"); +MODULE_SOFTDEP("pre: cdi_pwm"); diff --git a/drivers/media/platform/tegra/cdi/cdi_pwm.c b/drivers/media/platform/tegra/cdi/cdi_pwm.c new file mode 100644 index 00000000..18cc28c7 --- /dev/null +++ b/drivers/media/platform/tegra/cdi/cdi_pwm.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2016-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include +#include + +#include "cdi-pwm-priv.h" + +/* + * Below values are configured during suspend. + * Invalid values are chosen so that PWM + * configuration in resume works fine. + * Period is chosen as least non-zero value + * and duty-ratio zero. + */ +#define PWM_SUSPEND_PERIOD 1 +#define PWM_SUSPEND_DUTY_RATIO 0 + +static const struct of_device_id cdi_pwm_of_match[] = { + { .compatible = "nvidia,cdi-pwm", .data = NULL }, + {}, +}; + +static inline struct cdi_pwm_info *to_cdi_pwm_info(struct pwm_chip *chip) +{ + return container_of(chip, struct cdi_pwm_info, chip); +} + +static int cdi_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct cdi_pwm_info *info = to_cdi_pwm_info(chip); + int err = 0; + + if (!chip || !pwm) + return -EINVAL; + + if (info->force_on) + return err; + + mutex_lock(&info->mutex); + + if (atomic_inc_return(&info->in_use) == 1) + err = pwm_enable(info->pwm); + + mutex_unlock(&info->mutex); + + return err; +} + +static void cdi_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct cdi_pwm_info *info = to_cdi_pwm_info(chip); + int atomic_val; + + mutex_lock(&info->mutex); + + atomic_val = atomic_read(&info->in_use); + if (atomic_val > 0) { + if (atomic_dec_and_test(&info->in_use)) + pwm_disable(info->pwm); + } + + mutex_unlock(&info->mutex); +} + +static int cdi_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct cdi_pwm_info *info = to_cdi_pwm_info(chip); + int err = 0; + + if (info->force_on) + return err; + + mutex_lock(&info->mutex); + + err = pwm_config(info->pwm, duty_ns, period_ns); + + mutex_unlock(&info->mutex); + + return err; +} + +static struct pwm_device *of_cdi_pwm_xlate(struct pwm_chip *pc, + const struct of_phandle_args *args) +{ + struct pwm_device *pwm; + struct cdi_pwm_info *info = to_cdi_pwm_info(pc); + int err = 0; + + pwm = pwm_request_from_chip(pc, args->args[0], NULL); + if (!args->args[1]) { + dev_err(pc->dev, "Period should be larger than 0\n"); + return NULL; + } + + if (info->force_on) { + err = pwm_config(info->pwm, args->args[1]/4, args->args[1]); + if (err) { + dev_err(pc->dev, "can't config PWM: %d\n", err); + return NULL; + } + + err = pwm_enable(info->pwm); + if (err) { + dev_err(pc->dev, "can't enable PWM: %d\n", err); + return NULL; + } + } else { + err = pwm_config(pwm, args->args[1]/4, args->args[1]); + if (err) { + dev_err(pc->dev, "can't config PWM: %d\n", err); + return NULL; + } + } + + return pwm; +} + +static const struct pwm_ops cdi_pwm_ops = { + .config = cdi_pwm_config, + .enable = cdi_pwm_enable, + .disable = cdi_pwm_disable, + .owner = THIS_MODULE, +}; + +static int cdi_pwm_probe(struct platform_device *pdev) +{ + struct cdi_pwm_info *info = NULL; + int err = 0, npwm; + bool force_on = false; + + dev_info(&pdev->dev, "%sing...\n", __func__); + + info = devm_kzalloc( + &pdev->dev, sizeof(struct cdi_pwm_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + atomic_set(&info->in_use, 0); + mutex_init(&info->mutex); + + err = of_property_read_u32(pdev->dev.of_node, "npwm", &npwm); + if (err < 0) { + dev_err(&pdev->dev, "npwm is not defined: %d\n", err); + return err; + } + + force_on = of_property_read_bool(pdev->dev.of_node, "force_on"); + + info->chip.dev = &pdev->dev; + info->chip.ops = &cdi_pwm_ops; + info->chip.base = -1; + info->chip.npwm = npwm; + info->chip.of_xlate = of_cdi_pwm_xlate; + info->chip.of_pwm_n_cells = 2; + info->force_on = force_on; + + err = pwmchip_add(&info->chip); + if (err < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", err); + return err; + } + + platform_set_drvdata(pdev, info); + + info->pwm = devm_pwm_get(&pdev->dev, NULL); + if (!IS_ERR(info->pwm)) { + pwm_disable(info->pwm); + dev_info(&pdev->dev, "%s success to get PWM\n", __func__); + } else { + pwmchip_remove(&info->chip); + err = PTR_ERR(info->pwm); + if (err != -EPROBE_DEFER) + dev_err(&pdev->dev, + "%s: fail to get PWM\n", __func__); + } + + return err; +} + +static int cdi_pwm_remove(struct platform_device *pdev) +{ + struct cdi_pwm_info *info = platform_get_drvdata(pdev); + + pwmchip_remove(&info->chip); + + return 0; +} + +static int cdi_pwm_suspend(struct device *dev) +{ + int err = 0; + struct cdi_pwm_info *info = dev_get_drvdata(dev); + + if (info == NULL) { + dev_err(dev, "%s: fail to get info\n", __func__); + } else { + if (!IS_ERR(info->pwm)) { + pwm_disable(info->pwm); + err = pwm_config(info->pwm, PWM_SUSPEND_DUTY_RATIO, + PWM_SUSPEND_PERIOD); + } + } + return 0; +} + +static int cdi_pwm_resume(struct device *dev) +{ + /* Do nothing */ + return 0; +} + +static const struct dev_pm_ops cdi_pwm_pm_ops = { + .suspend = cdi_pwm_suspend, + .resume = cdi_pwm_resume, + .runtime_suspend = cdi_pwm_suspend, + .runtime_resume = cdi_pwm_resume, +}; + +static struct platform_driver cdi_pwm_driver = { + .driver = { + .name = "cdi-pwm", + .owner = THIS_MODULE, + .of_match_table = cdi_pwm_of_match, + .pm = &cdi_pwm_pm_ops, + }, + .probe = cdi_pwm_probe, + .remove = cdi_pwm_remove, +}; + +module_platform_driver(cdi_pwm_driver); + +MODULE_AUTHOR("Junghyun Kim "); +MODULE_DESCRIPTION("CDI PWM driver"); +MODULE_LICENSE("GPL v2"); +MODULE_DEVICE_TABLE(of, cdi_pwm_of_match); +MODULE_SOFTDEP("pre: cdi_dev"); diff --git a/drivers/media/platform/tegra/cdi/cdi_tsc.c b/drivers/media/platform/tegra/cdi/cdi_tsc.c new file mode 100644 index 00000000..f783d841 --- /dev/null +++ b/drivers/media/platform/tegra/cdi/cdi_tsc.c @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TSC_TICKS_PER_HZ (31250000ULL) +#define TSC_NS_PER_TICK (32) +#define NS_PER_MS (1000000U) + +#define TSC_MTSCCNTCV0 (0x10) +#define TSC_MTSCCNTCV0_CV GENMASK(31, 0) + +#define TSC_MTSCCNTCV1 (0x14) +#define TSC_MTSCCNTCV1_CV GENMASK(31, 0) + +#define TSC_GENX_CTRL (0x00) +#define TSC_GENX_CTRL_RST (0x00) +#define TSC_GENX_CTRL_INITIAL_VAL BIT(1) +#define TSC_GENX_CTRL_ENABLE BIT(0) + +#define TSC_GENX_START0 (0x04) +#define TSC_GENX_START0_LSB_VAL GENMASK(31, 0) + +#define TSC_GENX_START1 (0x08) +#define TSC_GENX_START1_MSB_VAL GENMASK(23, 0) + +#define TSC_GENX_STATUS (0x0C) +#define TSC_GENX_STATUS_INTERRUPT_STATUS BIT(6) +#define TSC_GENX_STATUS_VALUE BIT(5) +#define TSC_GENX_STATUS_EDGE_ID GENMASK(4, 2) +#define TSC_GENX_STATUS_RUNNING BIT(1) +#define TSC_GENX_STATUS_WAITING BIT(0) + +#define TSC_GENX_EDGE0 (0x18) +#define TSC_GENX_EDGE1 (0x1C) +#define TSC_GENX_EDGE2 (0x20) +#define TSC_GENX_EDGE3 (0x24) +#define TSC_GENX_EDGE4 (0x28) +#define TSC_GENX_EDGE5 (0x2C) +#define TSC_GENX_EDGE6 (0x30) +#define TSC_GENX_EDGE7 (0x34) + +#define TSC_GENX_EDGEX_INTERRUPT_EN BIT(31) +#define TSC_GENX_EDGEX_STOP BIT(30) +#define TSC_GENX_EDGEX_TOGGLE BIT(29) +#define TSC_GENX_EDGEX_LOOP BIT(28) +#define TSC_GENX_EDGEX_OFFSET GENMASK(27, 0) + +/* Time (ms) offset for the TSC signal generators */ +#define TSC_GENX_START_OFFSET_MS (100) + +/** + * struct tsc_signal_controller_features: TSC signal controller SW feature support. + * @rational_locking: + * @enforced: Generator periods must be derived from a common base. + * @max_freq_hz_lcm: Maximum frequency (hz) of the common base generator period. + * @offset: + * @enabled: Allow generators to offset their signal from the start of their period. + */ +struct tsc_signal_controller_features { + struct { + bool enforced; + u32 max_freq_hz_lcm; + } rational_locking; + struct { + bool enabled; + } offset; +}; + +/** + * struct tsc_signal_generator : Generator context. + * @base: ioremapped register base. + * @of: Generator device node. + * @config: + * @freq_hz: Frequency (hz) of the generator. + * @duty_cycle: Duty cycle (%) of the generator. + * @offset_ms: Offset (ms) to shift the signal by. + * @debugfs: + * @regset_ro: Debug FS read-only register set. + * @list: List node + */ +struct tsc_signal_generator { + void __iomem *base; + struct device_node *of; + struct { + u32 freq_hz; + u32 duty_cycle; + u32 offset_ms; + } config; + struct { + struct debugfs_regset32 regset_ro; + } debugfs; + struct list_head list; +}; + +/** + * struct tsc_signal_controller : Controller context + * @dev: device. + * @base: ioremapped register base. + * @debugfs: + * @d: dentry to debugfs directory. + * @features: Feature support for the controller. + * @generators: Linked list of child generators. + */ +struct tsc_signal_controller { + struct device *dev; + void __iomem *base; + struct { + struct dentry *d; + } debugfs; + const struct tsc_signal_controller_features *features; + struct list_head generators; +}; + +static const struct tsc_signal_controller_features tegra234_tsc_features = { + .rational_locking = { + .enforced = true, + .max_freq_hz_lcm = 120, + }, + .offset = { + .enabled = true, + }, +}; + +static const struct debugfs_reg32 tsc_signal_generator_debugfs_regset[] = { + { + .name = "status", + .offset = TSC_GENX_STATUS, + }, +}; +#define TSC_SIG_GEN_DEBUGFS_REGSET_SIZE ARRAY_SIZE(tsc_signal_generator_debugfs_regset) + +static inline void cdi_tsc_generator_writel(struct tsc_signal_generator *generator, u32 reg, u32 val) +{ + writel(val, generator->base + reg); +} + +static inline u32 cdi_tsc_generator_readl(struct tsc_signal_generator *generator, u32 reg) +{ + return readl(generator->base + reg); +} + +static inline u32 cdi_tsc_controller_readl(struct tsc_signal_controller *controller, u32 reg) +{ + return readl(controller->base + reg); +} + +static u32 cdi_tsc_find_max_freq_hz_lcm(const struct tsc_signal_controller *controller) +{ + struct tsc_signal_generator *generator; + u32 running_lcm = 0; + + list_for_each_entry(generator, &controller->generators, list) { + running_lcm = lcm_not_zero(generator->config.freq_hz, running_lcm); + } + + return running_lcm; +} + +static int cdi_tsc_find_and_add_generators(struct tsc_signal_controller *controller) +{ + struct tsc_signal_generator *generator; + struct device_node *np; + struct resource res; + const char *node_status; + int err; + + for_each_child_of_node(controller->dev->of_node, np) { + err = of_property_read_string(np, "status", &node_status); + if (err != 0) { + dev_err(controller->dev, "Failed to read generator status: %d\n", err); + return err; + } + if (strcmp("okay", node_status) != 0) { + dev_dbg(controller->dev, "Generator %s disabled - skipping\n", np->full_name); + continue; + } + + dev_dbg(controller->dev, "Generator found: %s\n", np->full_name); + + generator = devm_kzalloc(controller->dev, sizeof(*generator), GFP_KERNEL); + if (!generator) + return -ENOMEM; + + generator->of = np; + INIT_LIST_HEAD(&generator->list); + + if (of_address_to_resource(np, 0, &res)) + return -EINVAL; + + generator->base = devm_ioremap_resource(controller->dev, &res); + if (IS_ERR(generator->base)) + return PTR_ERR(generator->base); + + err = of_property_read_u32(np, "freq_hz", &generator->config.freq_hz); + if (err != 0) { + dev_err(controller->dev, "Failed to read generator frequency: %d\n", err); + return err; + } + + if (generator->config.freq_hz == 0) { + dev_err(controller->dev, "Frequency must be non-zero\n"); + return -EINVAL; + } + + err = of_property_read_u32(np, "duty_cycle", &generator->config.duty_cycle); + if (err != 0) { + dev_err(controller->dev, "Failed to read generator duty cycle: %d\n", err); + return err; + } + if (generator->config.duty_cycle >= 100) { + dev_err(controller->dev, "Duty cycle must be < 100%%\n"); + return -EINVAL; + } + + if (controller->features->offset.enabled) { + err = of_property_read_u32(np, "offset_ms", &generator->config.offset_ms); + if (err != 0) { + dev_err(controller->dev, "Failed to read generator offset: %d\n", err); + return err; + } + } + + list_add_tail(&generator->list, &controller->generators); + dev_dbg(controller->dev, "Generator %s added to controller\n", np->full_name); + } + + return 0; +} + +static int cdi_tsc_program_generator_edges(struct tsc_signal_controller *controller) +{ + struct tsc_signal_generator *generator; + u32 max_freq_hz_lcm = 0; + + /* + * If rational locking is enforced (e.g. a 30Hz & 60Hz signal must align every two periods + * w.r.t. the 60Hz signal) edges will be derived from whole-number multiples of the LCM of + * all generator frequencies belonging to this controller. + * + * If rational locking is _not_ enforced then generator edges will be independently + * derived based on their configured frequency. + */ + if (controller->features->rational_locking.enforced) { + max_freq_hz_lcm = cdi_tsc_find_max_freq_hz_lcm(controller); + if (max_freq_hz_lcm > controller->features->rational_locking.max_freq_hz_lcm) { + dev_err(controller->dev, + "Highest common frequency of %u hz exceeds maximum allowed (%u hz)\n", + max_freq_hz_lcm, + controller->features->rational_locking.max_freq_hz_lcm); + return -EINVAL; + } + } + + list_for_each_entry(generator, &controller->generators, list) { + u32 ticks_in_period = 0; + u32 ticks_active = 0; + u32 ticks_inactive = 0; + + if (controller->features->rational_locking.enforced) { + ticks_in_period = DIV_ROUND_CLOSEST(TSC_TICKS_PER_HZ, max_freq_hz_lcm); + ticks_in_period *= max_freq_hz_lcm / generator->config.freq_hz; + } else { + ticks_in_period = DIV_ROUND_CLOSEST(TSC_TICKS_PER_HZ, generator->config.freq_hz); + } + + ticks_active = mult_frac(ticks_in_period, generator->config.duty_cycle, 100); + ticks_inactive = ticks_in_period - ticks_active; + + cdi_tsc_generator_writel(generator, TSC_GENX_EDGE0, + TSC_GENX_EDGEX_TOGGLE | + FIELD_PREP(TSC_GENX_EDGEX_OFFSET, ticks_active)); + + cdi_tsc_generator_writel(generator, TSC_GENX_EDGE1, + TSC_GENX_EDGEX_TOGGLE | + TSC_GENX_EDGEX_LOOP | + FIELD_PREP(TSC_GENX_EDGEX_OFFSET, ticks_inactive)); + } + + return 0; +} + +static void cdi_tsc_program_generator_start_values(struct tsc_signal_controller *controller) +{ + const u32 relative_ticks_to_start = mult_frac( + TSC_GENX_START_OFFSET_MS, NS_PER_MS, TSC_NS_PER_TICK); + + const u32 current_ticks_lo = FIELD_GET(TSC_MTSCCNTCV0_CV, + cdi_tsc_controller_readl(controller, TSC_MTSCCNTCV0)); + const u32 current_ticks_hi = FIELD_GET(TSC_MTSCCNTCV1_CV, + cdi_tsc_controller_readl(controller, TSC_MTSCCNTCV1)); + + const u64 current_ticks = ((u64)current_ticks_hi << 32) | current_ticks_lo; + + struct tsc_signal_generator *generator; + + list_for_each_entry(generator, &controller->generators, list) { + u64 absolute_ticks_to_start = current_ticks + relative_ticks_to_start; + + if (controller->features->offset.enabled && (generator->config.offset_ms != 0)) { + absolute_ticks_to_start += mult_frac(generator->config.offset_ms, NS_PER_MS, TSC_NS_PER_TICK); + } + + cdi_tsc_generator_writel(generator, TSC_GENX_START0, + FIELD_PREP(TSC_GENX_START0_LSB_VAL, lower_32_bits(absolute_ticks_to_start))); + + cdi_tsc_generator_writel(generator, TSC_GENX_START1, + FIELD_PREP(TSC_GENX_START1_MSB_VAL, upper_32_bits(absolute_ticks_to_start))); + } +} + +static bool cdi_tsc_generator_is_running(struct tsc_signal_generator *generator) +{ + const u32 status = cdi_tsc_generator_readl(generator, TSC_GENX_STATUS); + + return FIELD_GET(TSC_GENX_STATUS_RUNNING, status) == 1; +} + +static bool cdi_tsc_generator_is_waiting(struct tsc_signal_generator *generator) +{ + const u32 status = cdi_tsc_generator_readl(generator, TSC_GENX_STATUS); + + return FIELD_GET(TSC_GENX_STATUS_WAITING, status) == 1; +} + +static inline bool cdi_tsc_generator_is_idle(struct tsc_signal_generator *generator) +{ + return !cdi_tsc_generator_is_running(generator) && + !cdi_tsc_generator_is_waiting(generator); +} + +static int cdi_tsc_start_generators(struct tsc_signal_controller *controller) +{ + struct tsc_signal_generator *generator; + int err; + + /* A generator must be idle (e.g. neither running nor waiting) before starting */ + list_for_each_entry(generator, &controller->generators, list) { + if (!cdi_tsc_generator_is_idle(generator)) { + dev_err(controller->dev, "Generator %s is not idle\n", generator->of->full_name); + return -EBUSY; + } + } + + err = cdi_tsc_program_generator_edges(controller); + if (err != 0) + return err; + + cdi_tsc_program_generator_start_values(controller); + + /* Start the generators */ + list_for_each_entry(generator, &controller->generators, list) { + cdi_tsc_generator_writel(generator, TSC_GENX_CTRL, + TSC_GENX_CTRL_INITIAL_VAL | TSC_GENX_CTRL_ENABLE); + } + + return 0; +} + +static int cdi_tsc_stop_generators(struct tsc_signal_controller *controller) +{ + struct tsc_signal_generator *generator; + + list_for_each_entry(generator, &controller->generators, list) { + cdi_tsc_generator_writel(generator, TSC_GENX_CTRL, TSC_GENX_CTRL_RST); + + /* Ensure the generator has stopped */ + if (!cdi_tsc_generator_is_idle(generator)) { + dev_err(controller->dev, "Generator %s failed to stop\n", + generator->of->full_name); + return -EIO; + } + } + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static int cdi_tsc_debugfs_init(struct tsc_signal_controller *controller) +{ + struct tsc_signal_generator *generator; + + controller->debugfs.d = + debugfs_create_dir(controller->dev->of_node->full_name, NULL); + if (IS_ERR(controller->debugfs.d)) + return PTR_ERR(controller->debugfs.d); + + list_for_each_entry(generator, &controller->generators, list) { + generator->debugfs.regset_ro.regs = tsc_signal_generator_debugfs_regset; + generator->debugfs.regset_ro.nregs = TSC_SIG_GEN_DEBUGFS_REGSET_SIZE; + generator->debugfs.regset_ro.base = generator->base; + + debugfs_create_regset32( + generator->of->full_name, + 0400, + controller->debugfs.d, + &generator->debugfs.regset_ro); + } + + return 0; +} + +static void cdi_tsc_debugfs_remove(struct tsc_signal_controller *controller) +{ + debugfs_remove_recursive(controller->debugfs.d); + controller->debugfs.d = NULL; +} +#endif + +static int cdi_tsc_probe(struct platform_device *pdev) +{ + struct tsc_signal_controller *controller; + struct resource *res; + int err; + + dev_info(&pdev->dev, "CDI TSC probing...\n"); + + controller = devm_kzalloc(&pdev->dev, sizeof(*controller), GFP_KERNEL); + if (!controller) + return -ENOMEM; + + controller->dev = &pdev->dev; + controller->features = of_device_get_match_data(&pdev->dev); + if (controller->features == NULL) { + dev_err(controller->dev, "No controller feature table found\n"); + return -ENODEV; + } + + INIT_LIST_HEAD(&controller->generators); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + controller->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(controller->base)) + return PTR_ERR(controller->base); + + platform_set_drvdata(pdev, controller); + + err = cdi_tsc_find_and_add_generators(controller); + if (err != 0) + return err; + +#ifdef CONFIG_DEBUG_FS + err = cdi_tsc_debugfs_init(controller); + if (err != 0) + return err; +#endif + + return cdi_tsc_start_generators(controller); +} + +static int cdi_tsc_remove(struct platform_device *pdev) +{ + struct tsc_signal_controller *controller = platform_get_drvdata(pdev); + +#ifdef CONFIG_DEBUG_FS + cdi_tsc_debugfs_remove(controller); +#endif + return cdi_tsc_stop_generators(controller); +} + +static int __maybe_unused cdi_tsc_suspend(struct device *dev) +{ + struct tsc_signal_controller *controller = dev_get_drvdata(dev); + + return cdi_tsc_stop_generators(controller); +} + +static int __maybe_unused cdi_tsc_resume(struct device *dev) +{ + struct tsc_signal_controller *controller = dev_get_drvdata(dev); + + return cdi_tsc_start_generators(controller); +} + +static const struct of_device_id cdi_tsc_of_match[] = { + { .compatible = "nvidia,tegra234-cdi-tsc", .data = &tegra234_tsc_features }, + { }, +}; +MODULE_DEVICE_TABLE(of, cdi_tsc_of_match); + +static SIMPLE_DEV_PM_OPS(cdi_tsc_pm, cdi_tsc_suspend, cdi_tsc_resume); + +static struct platform_driver cdi_tsc_driver = { + .driver = { + .name = "cdi_tsc", + .owner = THIS_MODULE, + .of_match_table = cdi_tsc_of_match, + .pm = &cdi_tsc_pm, + }, + .probe = cdi_tsc_probe, + .remove = cdi_tsc_remove, +}; +module_platform_driver(cdi_tsc_driver); + +MODULE_AUTHOR("Ian Kaszubski "); +MODULE_DESCRIPTION("CDI TSC Signal Generation Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cdi_tsc"); + diff --git a/drivers/media/platform/tegra/cdi/debugfs.c b/drivers/media/platform/tegra/cdi/debugfs.c new file mode 100644 index 00000000..f507bcde --- /dev/null +++ b/drivers/media/platform/tegra/cdi/debugfs.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isc-mgr-priv.h" +#include "isc-dev-priv.h" + +static int isc_mgr_status_show(struct seq_file *s, void *data) +{ + struct isc_mgr_priv *isc_mgr = s->private; + struct isc_mgr_client *isc_dev; + + if (isc_mgr == NULL) + return 0; + pr_info("%s - %s\n", __func__, isc_mgr->devname); + + if (list_empty(&isc_mgr->dev_list)) { + seq_printf(s, "%s: No devices supported.\n", isc_mgr->devname); + return 0; + } + + mutex_lock(&isc_mgr->mutex); + list_for_each_entry_reverse(isc_dev, &isc_mgr->dev_list, list) { + seq_printf(s, " %02d -- @0x%02x, %02d, %d, %s\n", + isc_dev->id, + isc_dev->cfg.addr, + isc_dev->cfg.reg_bits, + isc_dev->cfg.val_bits, + isc_dev->cfg.drv_name + ); + } + mutex_unlock(&isc_mgr->mutex); + + return 0; +} + +static ssize_t isc_mgr_attr_set(struct file *s, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + return count; +} + +static int isc_mgr_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, isc_mgr_status_show, inode->i_private); +} + +static const struct file_operations isc_mgr_debugfs_fops = { + .open = isc_mgr_debugfs_open, + .read = seq_read, + .write = isc_mgr_attr_set, + .llseek = seq_lseek, + .release = single_release, +}; + +static int pwr_on_get(void *data, u64 *val) +{ + struct isc_mgr_priv *isc_mgr = data; + + if (isc_mgr->pdata == NULL || !isc_mgr->pdata->num_pwr_gpios) { + *val = 0ULL; + return 0; + } + + *val = (isc_mgr->pwr_state & (BIT(28) - 1)) | + ((isc_mgr->pdata->num_pwr_gpios & 0x0f) << 28); + return 0; +} + +static int pwr_on_set(void *data, u64 val) +{ + return isc_mgr_power_up((struct isc_mgr_priv *)data, val); +} + +DEFINE_SIMPLE_ATTRIBUTE(pwr_on_fops, pwr_on_get, pwr_on_set, "0x%02llx\n"); + +static int pwr_off_get(void *data, u64 *val) +{ + struct isc_mgr_priv *isc_mgr = data; + + if (isc_mgr->pdata == NULL || !isc_mgr->pdata->num_pwr_gpios) { + *val = 0ULL; + return 0; + } + + *val = (~isc_mgr->pwr_state) & (BIT(isc_mgr->pdata->num_pwr_gpios) - 1); + *val = (*val & (BIT(28) - 1)) | + ((isc_mgr->pdata->num_pwr_gpios & 0x0f) << 28); + return 0; +} + +static int pwr_off_set(void *data, u64 val) +{ + return isc_mgr_power_down((struct isc_mgr_priv *)data, val); +} + +DEFINE_SIMPLE_ATTRIBUTE(pwr_off_fops, pwr_off_get, pwr_off_set, "0x%02llx\n"); + +int isc_mgr_debugfs_init(struct isc_mgr_priv *isc_mgr) +{ + struct dentry *d; + + dev_dbg(isc_mgr->dev, "%s %s\n", __func__, isc_mgr->devname); + isc_mgr->d_entry = debugfs_create_dir( + isc_mgr->devname, NULL); + if (isc_mgr->d_entry == NULL) { + dev_err(isc_mgr->dev, "%s: create dir failed\n", __func__); + return -ENOMEM; + } + + d = debugfs_create_file("map", S_IRUGO|S_IWUSR, isc_mgr->d_entry, + (void *)isc_mgr, &isc_mgr_debugfs_fops); + if (!d) + goto debugfs_init_err; + + d = debugfs_create_file("pwr-on", S_IRUGO|S_IWUSR, isc_mgr->d_entry, + (void *)isc_mgr, &pwr_on_fops); + if (!d) + goto debugfs_init_err; + + d = debugfs_create_file("pwr-off", S_IRUGO|S_IWUSR, isc_mgr->d_entry, + (void *)isc_mgr, &pwr_off_fops); + if (!d) + goto debugfs_init_err; + + return 0; + +debugfs_init_err: + dev_err(isc_mgr->dev, "%s: create file failed\n", __func__); + debugfs_remove_recursive(isc_mgr->d_entry); + isc_mgr->d_entry = NULL; + return -ENOMEM; +} + +int isc_mgr_debugfs_remove(struct isc_mgr_priv *isc_mgr) +{ + if (isc_mgr->d_entry == NULL) + return 0; + debugfs_remove_recursive(isc_mgr->d_entry); + isc_mgr->d_entry = NULL; + return 0; +} + +static int i2c_val_get(void *data, u64 *val) +{ + struct isc_dev_info *isc_dev = data; + u8 temp = 0; + + if (isc_dev_raw_rd(isc_dev, isc_dev->reg_off, 0, &temp, 1)) { + dev_err(isc_dev->dev, "ERR:%s failed\n", __func__); + return -EIO; + } + *val = (u64)temp; + return 0; +} + +static int i2c_val_set(void *data, u64 val) +{ + struct isc_dev_info *isc_dev = data; + u8 temp[3]; + + temp[2] = val & 0xff; + if (isc_dev_raw_wr(isc_dev, isc_dev->reg_off, temp, 1)) { + dev_err(isc_dev->dev, "ERR:%s failed\n", __func__); + return -EIO; + } + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(isc_val_fops, i2c_val_get, i2c_val_set, "0x%02llx\n"); + +static int i2c_oft_get(void *data, u64 *val) +{ + struct isc_dev_info *isc_dev = data; + + *val = (u64)isc_dev->reg_off; + return 0; +} + +static int i2c_oft_set(void *data, u64 val) +{ + struct isc_dev_info *isc_dev = data; + + isc_dev->reg_off = (typeof (isc_dev->reg_off))val; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(isc_oft_fops, i2c_oft_get, i2c_oft_set, "0x%02llx\n"); + +int isc_dev_debugfs_init(struct isc_dev_info *isc_dev) +{ + struct isc_mgr_priv *isc_mgr = NULL; + struct dentry *d; + + dev_dbg(isc_dev->dev, "%s %s\n", __func__, isc_dev->devname); + + if (isc_dev->pdata) + isc_mgr = dev_get_drvdata(isc_dev->pdata->pdev); + + isc_dev->d_entry = debugfs_create_dir( + isc_dev->devname, + isc_mgr ? isc_mgr->d_entry : NULL); + if (isc_dev->d_entry == NULL) { + dev_err(isc_dev->dev, "%s: create dir failed\n", __func__); + return -ENOMEM; + } + + d = debugfs_create_file("val", S_IRUGO|S_IWUSR, isc_dev->d_entry, + (void *)isc_dev, &isc_val_fops); + if (!d) { + dev_err(isc_dev->dev, "%s: create file failed\n", __func__); + debugfs_remove_recursive(isc_dev->d_entry); + isc_dev->d_entry = NULL; + } + + d = debugfs_create_file("offset", S_IRUGO|S_IWUSR, isc_dev->d_entry, + (void *)isc_dev, &isc_oft_fops); + if (!d) { + dev_err(isc_dev->dev, "%s: create file failed\n", __func__); + debugfs_remove_recursive(isc_dev->d_entry); + isc_dev->d_entry = NULL; + } + + return 0; +} + +int isc_dev_debugfs_remove(struct isc_dev_info *isc_dev) +{ + if (isc_dev->d_entry == NULL) + return 0; + debugfs_remove_recursive(isc_dev->d_entry); + isc_dev->d_entry = NULL; + return 0; +} diff --git a/include/media/cdi-dev.h b/include/media/cdi-dev.h new file mode 100644 index 00000000..41f06523 --- /dev/null +++ b/include/media/cdi-dev.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#ifndef __CDI_DEV_H__ +#define __CDI_DEV_H__ + +#include +#include + +#define MAX_CDI_NAME_LENGTH 32 + +struct cdi_dev_platform_data { + struct device *pdev; /* parent device of cdi_dev */ + struct device_node *np; + int reg_bits; + int val_bits; + char drv_name[MAX_CDI_NAME_LENGTH]; +}; + +#endif /* __CDI_DEV_H__ */ diff --git a/include/media/cdi-mgr.h b/include/media/cdi-mgr.h new file mode 100644 index 00000000..15ba3634 --- /dev/null +++ b/include/media/cdi-mgr.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#ifndef __TEGRA_CDI_MGR_H__ +#define __TEGRA_CDI_MGR_H__ + +#include + +#define MAX_CDI_GPIOS 8 + +struct cdi_mgr_client { + struct mutex mutex; + struct list_head list; + struct i2c_client *client; + struct cdi_mgr_new_dev cfg; + struct cdi_dev_platform_data pdata; + int id; +}; + +struct cdi_mgr_platform_data { + int bus; + int num_pwr_gpios; + u32 pwr_gpios[MAX_CDI_GPIOS]; + u32 pwr_flags[MAX_CDI_GPIOS]; + int num_pwr_map; + u32 pwr_mapping[MAX_CDI_GPIOS]; + int num_mcdi_gpios; + u32 mcdi_gpios[MAX_CDI_GPIOS]; + u32 mcdi_flags[MAX_CDI_GPIOS]; + int csi_port; + bool default_pwr_on; + bool runtime_pwrctrl_off; + char *drv_name; + u8 ext_pwr_ctrl; /* bit 0 - des, bit 1 - sensor */ + bool max20087_pwrctl; +}; + +int cdi_delete_lst(struct device *dev, struct i2c_client *client); + +#endif /* __TEGRA_CDI_MGR_H__ */ diff --git a/include/uapi/media/cdi-dev.h b/include/uapi/media/cdi-dev.h new file mode 100644 index 00000000..c5edfaa5 --- /dev/null +++ b/include/uapi/media/cdi-dev.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#ifndef __UAPI_CDI_DEV_H__ +#define __UAPI_CDI_DEV_H__ + +#include +#include + +#define CDI_DEV_PKG_FLAG_WR 1 + +#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 DES_PWR_NVCCP 0U +#define DES_PWR_GPIO 1U +#define DES_PWR_NO_PWR 0xFFU +#define CAM_PWR_NVCCP 0U +#define CAM_PWR_MAX20087 1U +#define CAM_PWR_NO_PWR 0xFFU + +#define MAX_POWER_LINKS_PER_BLOCK (4U) + +struct __attribute__ ((__packed__)) cdi_dev_pwr_ctrl_info { + __s8 cam_pwr_method; + __s8 cam_pwr_i2c_addr; + __u8 cam_pwr_links[MAX_POWER_LINKS_PER_BLOCK]; +}; + +struct __attribute__ ((__packed__)) cdi_dev_package { + __u16 offset; + __u16 offset_len; + __u32 size; + __u32 flags; + unsigned long buffer; +}; + +#endif /* __UAPI_CDI_DEV_H__ */ diff --git a/include/uapi/media/cdi-mgr.h b/include/uapi/media/cdi-mgr.h new file mode 100644 index 00000000..3085c766 --- /dev/null +++ b/include/uapi/media/cdi-mgr.h @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#ifndef __UAPI_TEGRA_CDI_MGR_H__ +#define __UAPI_TEGRA_CDI_MGR_H__ + +#include +#include + +#define CDI_MGR_IOCTL_PWR_DN _IOW('o', 1, __s16) +#define CDI_MGR_IOCTL_PWR_UP _IOR('o', 2, __s16) +#define CDI_MGR_IOCTL_SET_PID _IOW('o', 3, struct cdi_mgr_sinfo) +#define CDI_MGR_IOCTL_SIGNAL _IOW('o', 4, int) +#define CDI_MGR_IOCTL_DEV_ADD _IOW('o', 5, struct cdi_mgr_new_dev) +#define CDI_MGR_IOCTL_DEV_DEL _IOW('o', 6, int) +#define CDI_MGR_IOCTL_PWR_INFO _IOW('o', 7, struct cdi_mgr_pwr_info) +#define CDI_MGR_IOCTL_PWM_ENABLE _IOW('o', 8, int) +#define CDI_MGR_IOCTL_PWM_CONFIG _IOW('o', 9, struct cdi_mgr_pwm_info) +#define CDI_MGR_IOCTL_WAIT_ERR _IOWR('o', 10, __u32) +#define CDI_MGR_IOCTL_ABORT_WAIT_ERR _IO('o', 11) +#define CDI_MGR_IOCTL_GET_EXT_PWR_CTRL _IOR('o', 12, __u8) +#define CDI_MGR_IOCTL_ENABLE_ERROR_REPORT _IO('o', 13) +#define CDI_MGR_IOCTL_GET_PWR_INFO _IOW('o', 14, struct cdi_mgr_pwr_ctrl_info) + +#define CDI_MGR_POWER_ALL 5 +#define MAX_CDI_NAME_LENGTH 32 + +#define DES_PWR_NVCCP 0U +#define DES_PWR_GPIO 1U +#define DES_PWR_NO_PWR 0xFFU +#define CAM_PWR_NVCCP 0U +#define CAM_PWR_MAX20087 1U +#define CAM_PWR_NO_PWR 0xFFU + +struct cdi_mgr_new_dev { + __u16 addr; + __u8 reg_bits; + __u8 val_bits; + __u8 drv_name[MAX_CDI_NAME_LENGTH]; +}; + +struct cdi_mgr_sinfo { + __s32 pid; + __s32 sig_no; + __u64 context; +}; + +struct cdi_mgr_pwr_info { + __s32 pwr_gpio; + __s32 pwr_status; +}; + +struct cdi_mgr_pwr_ctrl_info { + __s8 des_pwr_method; + __s8 des_pwr_i2c_addr; +}; + +struct cdi_mgr_pwm_info { + __u64 duty_ns; + __u64 period_ns; +}; + +enum { + CDI_MGR_PWM_DISABLE = 0, + CDI_MGR_PWM_ENABLE, +}; + +enum { + CDI_MGR_SIGNAL_RESUME = 0, + CDI_MGR_SIGNAL_SUSPEND, +}; + +#endif /* __UAPI_TEGRA_CDI_MGR_H__ */