diff --git a/drivers/media/platform/tegra/Makefile b/drivers/media/platform/tegra/Makefile index ba9f11cb..63a246c9 100644 --- a/drivers/media/platform/tegra/Makefile +++ b/drivers/media/platform/tegra/Makefile @@ -2,3 +2,4 @@ # Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. obj-m += cdi/ +obj-m += isc/ diff --git a/drivers/media/platform/tegra/isc/Makefile b/drivers/media/platform/tegra/isc/Makefile new file mode 100644 index 00000000..cb516e16 --- /dev/null +++ b/drivers/media/platform/tegra/isc/Makefile @@ -0,0 +1,6 @@ +GCOV_PROFILE := y + +obj-m += isc_gpio.o +obj-m += isc_mgr.o +obj-m += isc_dev.o +obj-m += isc_pwm.o diff --git a/drivers/media/platform/tegra/isc/debugfs.c b/drivers/media/platform/tegra/isc/debugfs.c new file mode 100644 index 00000000..f9eacbf1 --- /dev/null +++ b/drivers/media/platform/tegra/isc/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/drivers/media/platform/tegra/isc/isc-dev-priv.h b/drivers/media/platform/tegra/isc/isc-dev-priv.h new file mode 100644 index 00000000..5944159a --- /dev/null +++ b/drivers/media/platform/tegra/isc/isc-dev-priv.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __ISC_DEV_PRIV_H__ +#define __ISC_DEV_PRIV_H__ + +#include + +struct isc_dev_info { + struct i2c_client *i2c_client; + struct device *dev; + struct cdev cdev; + struct isc_dev_platform_data *pdata; + atomic_t in_use; + struct mutex mutex; + struct isc_dev_package rw_pkg; + struct dentry *d_entry; + u32 reg_len; + u32 reg_off; + char devname[32]; + u8 power_is_on; +}; + +int isc_dev_raw_rd(struct isc_dev_info *, unsigned int, + unsigned int, u8 *, size_t); +int isc_dev_raw_wr(struct isc_dev_info *, unsigned int, u8 *, size_t); + +int isc_dev_debugfs_init(struct isc_dev_info *isc_dev); +int isc_dev_debugfs_remove(struct isc_dev_info *isc_dev); + +#endif /* __ISC_DEV_PRIV_H__ */ diff --git a/drivers/media/platform/tegra/isc/isc-gpio-priv.h b/drivers/media/platform/tegra/isc/isc-gpio-priv.h new file mode 100644 index 00000000..68aa3f2b --- /dev/null +++ b/drivers/media/platform/tegra/isc/isc-gpio-priv.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __ISC_GPIO_PRIV_H__ +#define __ISC_GPIO_PRIV_H__ + +struct isc_gpio_plat_data { + const char *gpio_prnt_chip; + u32 max_gpio; +}; + +struct isc_gpio_desc { + u32 gpio; + atomic_t ref_cnt; +}; + +struct isc_gpio_priv { + struct device *pdev; + struct isc_gpio_plat_data pdata; + struct mutex mutex; + struct gpio_chip gpio_chip; + struct gpio_chip *tgc; + struct isc_gpio_desc *gpios; + u32 num_gpio; +}; + +#endif /* __ISC_GPIO_PRIV_H__ */ diff --git a/drivers/media/platform/tegra/isc/isc-mgr-priv.h b/drivers/media/platform/tegra/isc/isc-mgr-priv.h new file mode 100644 index 00000000..691af8dd --- /dev/null +++ b/drivers/media/platform/tegra/isc/isc-mgr-priv.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __ISC_MGR_PRIV_H__ +#define __ISC_MGR_PRIV_H__ + +#include +#include + +struct isc_mgr_priv { + struct device *pdev; /* parent device */ + struct device *dev; /* this device */ + dev_t devt; + struct cdev cdev; + struct class *isc_class; + struct i2c_adapter *adap; + struct isc_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; + int err_irq; + char devname[32]; + u32 pwr_state; + atomic_t irq_in_use; + struct pwm_device *pwm; + wait_queue_head_t err_queue; + bool err_irq_recvd; +}; + +int isc_mgr_power_up(struct isc_mgr_priv *isc_mgr, unsigned long arg); +int isc_mgr_power_down(struct isc_mgr_priv *isc_mgr, unsigned long arg); + +int isc_mgr_debugfs_init(struct isc_mgr_priv *isc_mgr); +int isc_mgr_debugfs_remove(struct isc_mgr_priv *isc_mgr); + +#endif /* __ISC_MGR_PRIV_H__ */ diff --git a/drivers/media/platform/tegra/isc/isc-pwm-priv.h b/drivers/media/platform/tegra/isc/isc-pwm-priv.h new file mode 100644 index 00000000..3fb087ac --- /dev/null +++ b/drivers/media/platform/tegra/isc/isc-pwm-priv.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __ISC_PWM_PRIV_H__ +#define __ISC_PWM_PRIV_H__ + +struct isc_pwm_info { + struct pwm_chip chip; + struct pwm_device *pwm; + atomic_t in_use; + struct mutex mutex; + bool force_on; +}; + +#endif /* __ISC_PWM_PRIV_H__ */ diff --git a/drivers/media/platform/tegra/isc/isc_dev.c b/drivers/media/platform/tegra/isc/isc_dev.c new file mode 100644 index 00000000..475f1ade --- /dev/null +++ b/drivers/media/platform/tegra/isc/isc_dev.c @@ -0,0 +1,626 @@ +// 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 "isc-dev-priv.h" +#include "isc-mgr-priv.h" + +/* i2c payload size is only 12 bit */ +#define MAX_MSG_SIZE (0xFFF - 1) + +/*#define DEBUG_I2C_TRAFFIC*/ + +/* ISC Dev Debugfs functions + * + * - isc_dev_debugfs_init + * - isc_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 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; +} + +static void isc_dev_dump( + const char *str, + struct isc_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 written to device. + * offset - address in the device's register space to start with. + */ +int isc_dev_raw_rd( + struct isc_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) + isc_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 written 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 isc_dev_raw_wr( + struct isc_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; + } + + isc_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 isc_dev_raw_rw(struct isc_dev_info *info) +{ + struct isc_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 & ISC_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 = isc_dev_raw_wr(info, -1, buf, pkg->size); + } else { + /* read from device */ + ret = isc_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 isc_dev_get_package( + struct isc_dev_info *info, unsigned long arg, bool is_compat) +{ + if (is_compat) { + struct isc_dev_package32 pkg32; + + if (copy_from_user(&pkg32, + (const void __user *)arg, sizeof(pkg32))) { + dev_err(info->dev, "%s copy_from_user err line %d\n", + __func__, __LINE__); + return -EFAULT; + } + info->rw_pkg.offset = pkg32.offset; + info->rw_pkg.offset_len = pkg32.offset_len; + info->rw_pkg.size = pkg32.size; + info->rw_pkg.flags = pkg32.flags; + info->rw_pkg.buffer = (unsigned long)pkg32.buffer; + } else { + struct isc_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 long isc_dev_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct isc_dev_info *info = file->private_data; + int err = 0; + + switch (cmd) { + case ISC_DEV_IOCTL_RW: + err = isc_dev_get_package(info, arg, false); + if (err) + break; + + err = isc_dev_raw_rw(info); + break; + default: + dev_dbg(info->dev, "%s: invalid cmd %x\n", __func__, cmd); + return -EINVAL; + } + + return err; +} + +#ifdef CONFIG_COMPAT +static long isc_dev_ioctl32(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct isc_dev_info *info = file->private_data; + int err = 0; + + switch (cmd) { + case ISC_DEV_IOCTL_RW32: + err = isc_dev_get_package(info, arg, true); + if (err) + break; + + err = isc_dev_raw_rw(info); + break; + default: + return isc_dev_ioctl(file, cmd, arg); + } + + return err; +} +#endif + +static int isc_dev_open(struct inode *inode, struct file *file) +{ + struct isc_dev_info *info; + + if (inode == NULL) + return -ENODEV; + + info = container_of(inode->i_cdev, struct isc_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 isc_dev_release(struct inode *inode, struct file *file) +{ + struct isc_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 isc_dev_fileops = { + .owner = THIS_MODULE, + .open = isc_dev_open, + .unlocked_ioctl = isc_dev_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = isc_dev_ioctl32, +#endif + .release = isc_dev_release, +}; + +static int isc_dev_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isc_dev_info *info; + struct device *pdev; + int err; + + dev_dbg(&client->dev, "%s: initializing link @%x-%04x\n", + __func__, client->adapter->nr, client->addr); + + info = devm_kzalloc( + &client->dev, sizeof(struct isc_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->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) + snprintf(info->devname, sizeof(info->devname), + "%s", info->pdata->drv_name); + else + snprintf(info->devname, sizeof(info->devname), + "isc-dev.%u.%02x", client->adapter->nr, client->addr); + + if (info->pdata->pdev == NULL) + return -ENODEV; + + cdev_init(&info->cdev, &isc_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 isc-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); + } + + info->power_is_on = 1; + i2c_set_clientdata(client, info); + isc_dev_debugfs_init(info); + return 0; +} + +static int isc_dev_remove(struct i2c_client *client) +{ + struct isc_dev_info *info = i2c_get_clientdata(client); + struct device *pdev; + + dev_dbg(&client->dev, "%s\n", __func__); + isc_dev_debugfs_remove(info); + + /* remove only isc_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 isc_dev_suspend(struct device *dev) +{ + struct isc_dev_info *isc = (struct isc_dev_info *)dev_get_drvdata(dev); + + dev_info(dev, "Suspending\n"); + mutex_lock(&isc->mutex); + isc->power_is_on = 0; + mutex_unlock(&isc->mutex); + + return 0; +} + +static int isc_dev_resume(struct device *dev) +{ + struct isc_dev_info *isc = (struct isc_dev_info *)dev_get_drvdata(dev); + + dev_info(dev, "Resuming\n"); + mutex_lock(&isc->mutex); + isc->power_is_on = 1; + mutex_unlock(&isc->mutex); + + return 0; +} +#endif + +static const struct i2c_device_id isc_dev_id[] = { + { "isc-dev", 0 }, + { }, +}; + +static const struct dev_pm_ops isc_dev_pm_ops = { + SET_RUNTIME_PM_OPS(isc_dev_suspend, + isc_dev_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(isc_dev_suspend, + isc_dev_resume) +}; + +static struct i2c_driver isc_dev_drv = { + .driver = { + .name = "isc-dev", + .owner = THIS_MODULE, + .pm = &isc_dev_pm_ops, + }, + .id_table = isc_dev_id, + .probe = isc_dev_probe, + .remove = isc_dev_remove, +}; + +module_i2c_driver(isc_dev_drv); + +MODULE_DESCRIPTION("ISC Generic I2C driver"); +MODULE_AUTHOR("Charlie Huang "); +MODULE_LICENSE("GPL v2"); +MODULE_SOFTDEP("pre: isc_gpio"); diff --git a/drivers/media/platform/tegra/isc/isc_gpio.c b/drivers/media/platform/tegra/isc/isc_gpio.c new file mode 100644 index 00000000..255273c0 --- /dev/null +++ b/drivers/media/platform/tegra/isc/isc_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 "isc-gpio-priv.h" + +#define MAX_STR_SIZE 255 + +static int of_isc_gpio_pdata(struct platform_device *pdev, + struct isc_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 isc_gpio_chip_match(struct gpio_chip *chip, void *data) +{ + return !strcmp(chip->label, data); +} + +static struct gpio_chip *isc_gpio_get_chip(struct platform_device *pdev, + struct isc_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, isc_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 isc_gpio_init_desc(struct platform_device *pdev, + struct isc_gpio_priv *isc_gpio) +{ + struct isc_gpio_desc *desc = NULL; + u32 i; + + desc = devm_kzalloc(&pdev->dev, + (sizeof(struct isc_gpio_desc) * + isc_gpio->pdata.max_gpio), + GFP_KERNEL); + if (!desc) { + dev_err(&pdev->dev, "Unable to allocate memory!\n"); + return -ENOMEM; + } + + for (i = 0; i < isc_gpio->pdata.max_gpio; i++) { + desc[i].gpio = 0; + atomic_set(&desc[i].ref_cnt, 0); + } + + isc_gpio->gpios = desc; + return 0; +} + +static int isc_gpio_get_index(struct device *dev, + struct isc_gpio_priv *isc_gpio, u32 gpio) +{ + u32 i; + int idx = -1; + + /* find gpio in array */ + for (i = 0; i < isc_gpio->num_gpio; i++) { + if (isc_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 (isc_gpio->num_gpio < isc_gpio->pdata.max_gpio) { + idx = isc_gpio->num_gpio; + isc_gpio->gpios[idx].gpio = gpio; + isc_gpio->num_gpio++; + + return idx; + } + + dev_err(dev, "%s: Unable to add gpio to desc\n", __func__); + return -EFAULT; +} + +static int isc_gpio_direction_input(struct gpio_chip *gc, unsigned int off) +{ + struct gpio_chip *tgc = NULL; + struct isc_gpio_priv *isc_gpio = NULL; + int err; + + isc_gpio = gpiochip_get_data(gc); + if (!isc_gpio) + return -EFAULT; + + mutex_lock(&isc_gpio->mutex); + + tgc = isc_gpio->tgc; + err = tgc->direction_input(tgc, off); + + mutex_unlock(&isc_gpio->mutex); + + return err; +} + +static int isc_gpio_direction_output(struct gpio_chip *gc, unsigned int off, + int val) +{ + struct gpio_chip *tgc = NULL; + struct isc_gpio_priv *isc_gpio = NULL; + int err; + + isc_gpio = gpiochip_get_data(gc); + if (!isc_gpio) + return -EFAULT; + + mutex_lock(&isc_gpio->mutex); + + tgc = isc_gpio->tgc; + err = tgc->direction_output(tgc, off, val); + + mutex_unlock(&isc_gpio->mutex); + + return err; +} + +static int isc_gpio_get_value(struct gpio_chip *gc, unsigned int off) +{ + int gpio_val; + struct gpio_chip *tgc = NULL; + struct isc_gpio_priv *isc_gpio = NULL; + + isc_gpio = gpiochip_get_data(gc); + if (!isc_gpio) + return -EFAULT; + + mutex_lock(&isc_gpio->mutex); + + tgc = isc_gpio->tgc; + gpio_val = tgc->get(tgc, off); + + mutex_unlock(&isc_gpio->mutex); + + return gpio_val; +} + +static void isc_gpio_set_value(struct gpio_chip *gc, unsigned int off, int val) +{ + int idx; + struct gpio_chip *tgc = NULL; + struct isc_gpio_priv *isc_gpio = NULL; + atomic_t *ref_cnt; + struct device *dev = NULL; + + isc_gpio = gpiochip_get_data(gc); + if (!isc_gpio) + return; + + mutex_lock(&isc_gpio->mutex); + + dev = isc_gpio->pdev; + tgc = isc_gpio->tgc; + + idx = isc_gpio_get_index(dev, isc_gpio, off); + if (idx < 0) { + mutex_unlock(&isc_gpio->mutex); + return; + } + idx = array_index_nospec(idx, isc_gpio->pdata.max_gpio); + + /* set gpio value based on refcount */ + ref_cnt = &isc_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(&isc_gpio->mutex); +} + +static int isc_gpio_probe(struct platform_device *pdev) +{ + struct isc_gpio_priv *isc_gpio; + struct isc_gpio_plat_data *pd = NULL; + struct gpio_chip *tgc, *gc; + int err; + + dev_info(&pdev->dev, "probing %s...\n", __func__); + + isc_gpio = devm_kzalloc(&pdev->dev, + sizeof(struct isc_gpio_priv), + GFP_KERNEL); + if (!isc_gpio) { + dev_err(&pdev->dev, "Unable to allocate memory!\n"); + return -ENOMEM; + } + + /* get platform data from device tree */ + err = of_isc_gpio_pdata(pdev, &isc_gpio->pdata); + if (err < 0) + return err; + + pd = &isc_gpio->pdata; + + /* get tegra gpio chip */ + tgc = isc_gpio_get_chip(pdev, pd); + if (!tgc) + return -ENXIO; + + isc_gpio->tgc = tgc; + + /* initialize gpio desc */ + err = isc_gpio_init_desc(pdev, isc_gpio); + if (err < 0) + return err; + + isc_gpio->num_gpio = 0; + + /* setup gpio chip */ + gc = &isc_gpio->gpio_chip; + gc->direction_input = isc_gpio_direction_input; + gc->direction_output = isc_gpio_direction_output; + gc->get = isc_gpio_get_value; + gc->set = isc_gpio_set_value; + + gc->can_sleep = false; + gc->base = -1; + gc->ngpio = pd->max_gpio; + gc->label = "isc-gpio"; + gc->of_node = pdev->dev.of_node; + gc->owner = THIS_MODULE; + + err = gpiochip_add_data(gc, isc_gpio); + if (err) { + dev_err(&pdev->dev, "failed to add GPIO controller\n"); + return err; + } + + mutex_init(&isc_gpio->mutex); + isc_gpio->pdev = &pdev->dev; + dev_set_drvdata(&pdev->dev, isc_gpio); + + dev_info(&pdev->dev, "%s: successfully registered gpio device\n", + __func__); + return 0; +} + +static int isc_gpio_remove(struct platform_device *pdev) +{ + struct isc_gpio_priv *isc_gpio = platform_get_drvdata(pdev); + + gpiochip_remove(&isc_gpio->gpio_chip); + + return 0; +} + +static const struct of_device_id isc_gpio_dt_ids[] = { + { .compatible = "nvidia,isc-gpio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, isc_gpio_dt_ids); + +static struct platform_driver isc_gpio_driver = { + .probe = isc_gpio_probe, + .remove = isc_gpio_remove, + .driver = { + .name = "isc-gpio", + .of_match_table = isc_gpio_dt_ids, + .owner = THIS_MODULE, + } +}; + +static int __init isc_gpio_init(void) +{ + return platform_driver_register(&isc_gpio_driver); +} + +static void __exit isc_gpio_exit(void) +{ + platform_driver_unregister(&isc_gpio_driver); +} + +/* call in subsys so that this module loads before isc-mgr driver */ +subsys_initcall(isc_gpio_init); +module_exit(isc_gpio_exit); + +MODULE_DESCRIPTION("Tegra Auto ISC GPIO Driver"); +MODULE_AUTHOR("Anurag Dosapati "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/tegra/isc/isc_mgr.c b/drivers/media/platform/tegra/isc/isc_mgr.c new file mode 100644 index 00000000..ba2f4e00 --- /dev/null +++ b/drivers/media/platform/tegra/isc/isc_mgr.c @@ -0,0 +1,1208 @@ +// 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 "isc-mgr-priv.h" + +#define PW_ON(flag) ((flag) ? 0 : 1) +#define PW_OFF(flag) ((flag) ? 1 : 0) + +/* minor number range would be 0 to 127 */ +#define ISC_DEV_MAX 128 + +/* ISC Dev Debugfs functions + * + * - isc_mgr_debugfs_init + * - isc_mgr_debugfs_remove + * - isc_mgr_status_show + * - isc_mgr_attr_set + * - pwr_on_get + * - pwr_on_set + * - pwr_off_get + * - pwr_off_set + */ +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 irqreturn_t isc_mgr_isr(int irq, void *data) +{ + struct isc_mgr_priv *isc_mgr; + int ret; + unsigned long flags; + + if (data) { + isc_mgr = (struct isc_mgr_priv *)data; + isc_mgr->err_irq_recvd = true; + wake_up_interruptible(&isc_mgr->err_queue); + spin_lock_irqsave(&isc_mgr->spinlock, flags); + if (isc_mgr->sinfo.si_signo && isc_mgr->t) { + /* send the signal to user space */ + ret = send_sig_info(isc_mgr->sinfo.si_signo, + &isc_mgr->sinfo, + isc_mgr->t); + if (ret < 0) { + pr_err("error sending signal\n"); + spin_unlock_irqrestore(&isc_mgr->spinlock, + flags); + return IRQ_HANDLED; + } + } + spin_unlock_irqrestore(&isc_mgr->spinlock, flags); + } + + return IRQ_HANDLED; +} + +int isc_delete_lst(struct device *dev, struct i2c_client *client) +{ + struct isc_mgr_priv *isc_mgr; + struct isc_mgr_client *isc_dev; + + if (dev == NULL) + return -EFAULT; + + isc_mgr = (struct isc_mgr_priv *)dev_get_drvdata(dev); + + mutex_lock(&isc_mgr->mutex); + list_for_each_entry(isc_dev, &isc_mgr->dev_list, list) { + if (isc_dev->client == client) { + list_del(&isc_dev->list); + break; + } + } + mutex_unlock(&isc_mgr->mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(isc_delete_lst); + +static int isc_remove_dev(struct isc_mgr_priv *isc_mgr, unsigned long arg) +{ + struct isc_mgr_client *isc_dev; + + dev_dbg(isc_mgr->dev, "%s %ld\n", __func__, arg); + mutex_lock(&isc_mgr->mutex); + list_for_each_entry(isc_dev, &isc_mgr->dev_list, list) { + if (isc_dev->id == arg) { + list_del(&isc_dev->list); + break; + } + } + mutex_unlock(&isc_mgr->mutex); + + if (&isc_dev->list != &isc_mgr->dev_list) + i2c_unregister_device(isc_dev->client); + else + dev_err(isc_mgr->dev, "%s: list %lx un-exist\n", __func__, arg); + + return 0; +} + +static int __isc_create_dev( + struct isc_mgr_priv *isc_mgr, struct isc_mgr_new_dev *new_dev) +{ + struct isc_mgr_client *isc_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(isc_mgr->dev, + "%s: invalid isc 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; + } + + isc_dev = devm_kzalloc(isc_mgr->dev, sizeof(*isc_dev), GFP_KERNEL); + if (!isc_dev) { + dev_err(isc_mgr->dev, "Unable to allocate memory!\n"); + return -ENOMEM; + } + + memcpy(&isc_dev->cfg, new_dev, sizeof(isc_dev->cfg)); + dev_dbg(isc_mgr->pdev, "%s - %s @ %x, %d %d\n", __func__, + isc_dev->cfg.drv_name, isc_dev->cfg.addr, + isc_dev->cfg.reg_bits, isc_dev->cfg.val_bits); + + snprintf(isc_dev->pdata.drv_name, sizeof(isc_dev->pdata.drv_name), + "%s.%u.%02x", isc_dev->cfg.drv_name, + isc_mgr->adap->nr, isc_dev->cfg.addr); + isc_dev->pdata.reg_bits = isc_dev->cfg.reg_bits; + isc_dev->pdata.val_bits = isc_dev->cfg.val_bits; + isc_dev->pdata.pdev = isc_mgr->dev; + + mutex_init(&isc_dev->mutex); + INIT_LIST_HEAD(&isc_dev->list); + + memset(&brd, 0, sizeof(brd)); + strncpy(brd.type, "isc-dev", sizeof(brd.type)); + brd.addr = isc_dev->cfg.addr; + brd.platform_data = &isc_dev->pdata; + isc_dev->client = i2c_new_client_device(isc_mgr->adap, &brd); + if (!isc_dev->client) { + dev_err(isc_mgr->dev, + "%s cannot allocate client: %s bus %d, %x\n", __func__, + isc_dev->pdata.drv_name, isc_mgr->adap->nr, brd.addr); + err = -EINVAL; + goto dev_create_err; + } + + mutex_lock(&isc_mgr->mutex); + if (!list_empty(&isc_mgr->dev_list)) + isc_dev->id = list_entry(isc_mgr->dev_list.next, + struct isc_mgr_client, list)->id + 1; + list_add(&isc_dev->list, &isc_mgr->dev_list); + mutex_unlock(&isc_mgr->mutex); + +dev_create_err: + if (err) { + devm_kfree(isc_mgr->dev, isc_dev); + return err; + } else + return isc_dev->id; +} + +static int isc_create_dev(struct isc_mgr_priv *isc_mgr, const void __user *arg) +{ + struct isc_mgr_new_dev d_cfg; + + if (copy_from_user(&d_cfg, arg, sizeof(d_cfg))) { + dev_err(isc_mgr->pdev, + "%s: failed to copy from user\n", __func__); + return -EFAULT; + } + + return __isc_create_dev(isc_mgr, &d_cfg); +} + +static int isc_mgr_write_pid(struct file *file, const void __user *arg) +{ + struct isc_mgr_priv *isc_mgr = file->private_data; + struct isc_mgr_sinfo sinfo; + unsigned long flags; + + if (copy_from_user(&sinfo, arg, sizeof(sinfo))) { + dev_err(isc_mgr->pdev, + "%s: failed to copy from user\n", __func__); + return -EFAULT; + } + + if (isc_mgr->sinfo.si_int) { + dev_err(isc_mgr->pdev, "exist signal info\n"); + return -EINVAL; + } + + if ((sinfo.sig_no < SIGRTMIN) || (sinfo.sig_no > SIGRTMAX)) { + dev_err(isc_mgr->pdev, "Invalid signal number\n"); + return -EINVAL; + } + + if (!sinfo.pid) { + dev_err(isc_mgr->pdev, "Invalid PID\n"); + return -EINVAL; + } + + spin_lock_irqsave(&isc_mgr->spinlock, flags); + isc_mgr->sinfo.si_signo = isc_mgr->sig_no = sinfo.sig_no; + isc_mgr->sinfo.si_code = SI_QUEUE; + isc_mgr->sinfo.si_ptr = (void __user *)((unsigned long)sinfo.context); + spin_unlock_irqrestore(&isc_mgr->spinlock, flags); + + rcu_read_lock(); + isc_mgr->t = pid_task(find_pid_ns(sinfo.pid, &init_pid_ns), + PIDTYPE_PID); + if (isc_mgr->t == NULL) { + dev_err(isc_mgr->pdev, "no such pid\n"); + rcu_read_unlock(); + return -ENODEV; + } + rcu_read_unlock(); + + return 0; +} + +static int isc_mgr_get_pwr_info(struct isc_mgr_priv *isc_mgr, + void __user *arg) +{ + struct isc_mgr_platform_data *pd = isc_mgr->pdata; + struct isc_mgr_pwr_info pinfo; + int err; + + if (copy_from_user(&pinfo, arg, sizeof(pinfo))) { + dev_err(isc_mgr->pdev, + "%s: failed to copy from user\n", __func__); + return -EFAULT; + } + + if (!pd->num_pwr_gpios) { + dev_err(isc_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(isc_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(isc_mgr->pdev, + "%s: failed to copy to user\n", __func__); + return -EFAULT; + } + return err; +} + +int isc_mgr_power_up(struct isc_mgr_priv *isc_mgr, unsigned long arg) +{ + struct isc_mgr_platform_data *pd = isc_mgr->pdata; + int i; + u32 pwr_gpio; + + dev_dbg(isc_mgr->pdev, "%s - %lu\n", __func__, arg); + + if (!pd->num_pwr_gpios) + goto pwr_up_end; + + if (arg >= MAX_ISC_GPIOS) + arg = MAX_ISC_GPIOS - 1; + + arg = array_index_nospec(arg, MAX_ISC_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])); + isc_mgr->pwr_state |= BIT(pwr_gpio); + return 0; + } + + for (i = 0; i < pd->num_pwr_gpios; i++) { + dev_dbg(isc_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])); + isc_mgr->pwr_state |= BIT(i); + } + +pwr_up_end: + return 0; +} + +int isc_mgr_power_down(struct isc_mgr_priv *isc_mgr, unsigned long arg) +{ + struct isc_mgr_platform_data *pd = isc_mgr->pdata; + int i; + u32 pwr_gpio; + + dev_dbg(isc_mgr->pdev, "%s - %lx\n", __func__, arg); + + if (!pd->num_pwr_gpios) + goto pwr_dn_end; + + if (arg >= MAX_ISC_GPIOS) + arg = MAX_ISC_GPIOS - 1; + + arg = array_index_nospec(arg, MAX_ISC_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])); + isc_mgr->pwr_state &= ~BIT(pwr_gpio); + return 0; + } + + for (i = 0; i < pd->num_pwr_gpios; i++) { + dev_dbg(isc_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])); + isc_mgr->pwr_state &= ~BIT(i); + } + mdelay(7); + +pwr_dn_end: + return 0; +} + +static int isc_mgr_misc_ctrl(struct isc_mgr_priv *isc_mgr, bool misc_on) +{ + struct isc_mgr_platform_data *pd = isc_mgr->pdata; + int err, i; + + dev_dbg(isc_mgr->pdev, "%s - %s\n", __func__, misc_on ? "ON" : "OFF"); + + if (!pd->num_misc_gpios) + return 0; + + for (i = 0; i < pd->num_misc_gpios; i++) { + if (misc_on) { + if (devm_gpio_request(isc_mgr->pdev, + pd->misc_gpios[i], + "misc-gpio")) { + dev_err(isc_mgr->pdev, "failed req GPIO: %d\n", + pd->misc_gpios[i]); + goto misc_ctrl_err; + } + + err = gpio_direction_output( + pd->misc_gpios[i], PW_ON(pd->misc_flags[i])); + } else { + err = gpio_direction_output( + pd->misc_gpios[i], PW_OFF(pd->misc_flags[i])); + devm_gpio_free(isc_mgr->pdev, pd->misc_gpios[i]); + } + } + return 0; + +misc_ctrl_err: + for (; i >= 0; i--) + devm_gpio_free(isc_mgr->pdev, pd->misc_gpios[i]); + return -EBUSY; +} + +static int isc_mgr_pwm_enable( + struct isc_mgr_priv *isc_mgr, unsigned long arg) +{ + int err = 0; + + if (!isc_mgr || !isc_mgr->pwm) + return -EINVAL; + + switch (arg) { + case ISC_MGR_PWM_ENABLE: + err = pwm_enable(isc_mgr->pwm); + break; + case ISC_MGR_PWM_DISABLE: + pwm_disable(isc_mgr->pwm); + break; + default: + dev_err(isc_mgr->pdev, "%s unrecognized command: %lx\n", + __func__, arg); + } + + return err; +} + +static int isc_mgr_pwm_config( + struct isc_mgr_priv *isc_mgr, const void __user *arg) +{ + struct isc_mgr_pwm_info pwm_cfg; + int err = 0; + + if (!isc_mgr || !isc_mgr->pwm) + return -EINVAL; + + if (copy_from_user(&pwm_cfg, arg, sizeof(pwm_cfg))) { + dev_err(isc_mgr->pdev, + "%s: failed to copy from user\n", __func__); + return -EFAULT; + } + + err = pwm_config(isc_mgr->pwm, pwm_cfg.duty_ns, pwm_cfg.period_ns); + + return err; +} + +static long isc_mgr_ioctl( + struct file *file, unsigned int cmd, unsigned long arg) +{ + struct isc_mgr_priv *isc_mgr = file->private_data; + struct isc_mgr_platform_data *pd = isc_mgr->pdata; + int err = 0; + unsigned long flags; + + /* command distributor */ + switch (cmd) { + case ISC_MGR_IOCTL_DEV_ADD: + err = isc_create_dev(isc_mgr, (const void __user *)arg); + break; + case ISC_MGR_IOCTL_DEV_DEL: + isc_remove_dev(isc_mgr, arg); + break; + case ISC_MGR_IOCTL_PWR_DN: + err = isc_mgr_power_down(isc_mgr, arg); + break; + case ISC_MGR_IOCTL_PWR_UP: + err = isc_mgr_power_up(isc_mgr, arg); + break; + case ISC_MGR_IOCTL_SET_PID: + /* first enable irq to clear pending interrupt + * and then register PID + */ + if (isc_mgr->err_irq && !atomic_xchg(&isc_mgr->irq_in_use, 1)) + enable_irq(isc_mgr->err_irq); + + err = isc_mgr_write_pid(file, (const void __user *)arg); + break; + case ISC_MGR_IOCTL_SIGNAL: + switch (arg) { + case ISC_MGR_SIGNAL_RESUME: + if (!isc_mgr->sig_no) { + dev_err(isc_mgr->pdev, + "invalid sig_no, setup pid first\n"); + return -EINVAL; + } + spin_lock_irqsave(&isc_mgr->spinlock, flags); + isc_mgr->sinfo.si_signo = isc_mgr->sig_no; + spin_unlock_irqrestore(&isc_mgr->spinlock, flags); + break; + case ISC_MGR_SIGNAL_SUSPEND: + spin_lock_irqsave(&isc_mgr->spinlock, flags); + isc_mgr->sinfo.si_signo = 0; + spin_unlock_irqrestore(&isc_mgr->spinlock, flags); + break; + default: + dev_err(isc_mgr->pdev, "%s unrecognized signal: %lx\n", + __func__, arg); + } + break; + case ISC_MGR_IOCTL_PWR_INFO: + err = isc_mgr_get_pwr_info(isc_mgr, (void __user *)arg); + break; + case ISC_MGR_IOCTL_PWM_ENABLE: + err = isc_mgr_pwm_enable(isc_mgr, arg); + break; + case ISC_MGR_IOCTL_PWM_CONFIG: + err = isc_mgr_pwm_config(isc_mgr, (const void __user *)arg); + break; + case ISC_MGR_IOCTL_WAIT_ERR: + if (isc_mgr->err_irq && !atomic_xchg(&isc_mgr->irq_in_use, 1)) { + enable_irq(isc_mgr->err_irq); + isc_mgr->err_irq_recvd = false; + } + + err = wait_event_interruptible(isc_mgr->err_queue, + isc_mgr->err_irq_recvd); + isc_mgr->err_irq_recvd = false; + break; + case ISC_MGR_IOCTL_ABORT_WAIT_ERR: + isc_mgr->err_irq_recvd = true; + wake_up_interruptible(&isc_mgr->err_queue); + break; + case ISC_MGR_IOCTL_GET_EXT_PWR_CTRL: + if (copy_to_user((void __user *)arg, + &pd->ext_pwr_ctrl, + sizeof(u8))) { + dev_err(isc_mgr->pdev, "%s: failed to copy to user\n", + __func__); + return -EFAULT; + } + break; + default: + dev_err(isc_mgr->pdev, "%s unsupported ioctl: %x\n", + __func__, cmd); + err = -EINVAL; + } + + if (err) + dev_dbg(isc_mgr->pdev, "err = %d\n", err); + + return err; +} + +static int isc_mgr_open(struct inode *inode, struct file *file) +{ + struct isc_mgr_priv *isc_mgr = container_of(inode->i_cdev, + struct isc_mgr_priv, cdev); + + /* only one application can open one isc_mgr device */ + if (atomic_xchg(&isc_mgr->in_use, 1)) + return -EBUSY; + + dev_dbg(isc_mgr->pdev, "%s\n", __func__); + file->private_data = isc_mgr; + + /* if runtime_pwrctrl_off is not true, power on all here */ + if (!isc_mgr->pdata->runtime_pwrctrl_off) + isc_mgr_power_up(isc_mgr, 0xffffffff); + + isc_mgr_misc_ctrl(isc_mgr, true); + return 0; +} + +static int isc_mgr_release(struct inode *inode, struct file *file) +{ + struct isc_mgr_priv *isc_mgr = file->private_data; + + if (isc_mgr->pwm) + if (pwm_is_enabled(isc_mgr->pwm)) + pwm_disable(isc_mgr->pwm); + + isc_mgr_misc_ctrl(isc_mgr, false); + + /* disable irq if irq is in use, when device is closed */ + if (atomic_xchg(&isc_mgr->irq_in_use, 0)) { + disable_irq(isc_mgr->err_irq); + isc_mgr->err_irq_recvd = true; + wake_up_interruptible(&isc_mgr->err_queue); + } + + /* if runtime_pwrctrl_off is not true, power off all here */ + if (!isc_mgr->pdata->runtime_pwrctrl_off) + isc_mgr_power_down(isc_mgr, 0xffffffff); + + /* clear sinfo to prevent report error after handler is closed */ + memset(&isc_mgr->sinfo, 0, sizeof(struct kernel_siginfo)); + + isc_mgr->t = NULL; + WARN_ON(!atomic_xchg(&isc_mgr->in_use, 0)); + + return 0; +} + +static const struct file_operations isc_mgr_fileops = { + .owner = THIS_MODULE, + .open = isc_mgr_open, + .unlocked_ioctl = isc_mgr_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = isc_mgr_ioctl, +#endif + .release = isc_mgr_release, +}; + +static void isc_mgr_del(struct isc_mgr_priv *isc_mgr) +{ + struct isc_mgr_platform_data *pd = isc_mgr->pdata; + struct isc_mgr_client *isc_dev = NULL; + int i; + + mutex_lock(&isc_mgr->mutex); + list_for_each_entry(isc_dev, &isc_mgr->dev_list, list) { + /* remove i2c_clients that isc-mgr created */ + if (isc_dev->client != NULL) { + i2c_unregister_device(isc_dev->client); + isc_dev->client = NULL; + } + } + mutex_unlock(&isc_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])); + + i2c_put_adapter(isc_mgr->adap); +} + +static void isc_mgr_dev_ins(struct work_struct *work) +{ + struct isc_mgr_priv *isc_mgr = + container_of(work, struct isc_mgr_priv, ins_work); + struct device_node *np = isc_mgr->pdev->of_node; + struct device_node *subdev; + struct isc_mgr_new_dev d_cfg = {.drv_name = "isc-dev"}; + const char *sname; + u32 val; + int err = 0; + + if (np == NULL) + return; + + dev_dbg(isc_mgr->dev, "%s - %s\n", __func__, np->full_name); + sname = of_get_property(np, "isc-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) { + dev_err(isc_mgr->dev, "%s: ERROR %d addr = %d\n", + __func__, err, val); + continue; + } + d_cfg.addr = val; + err = of_property_read_u32(subdev, "reg_len", &val); + if (err || !val) { + dev_err(isc_mgr->dev, "%s: ERROR %d reg_len = %d\n", + __func__, err, val); + continue; + } + d_cfg.reg_bits = val; + err = of_property_read_u32(subdev, "dat_len", &val); + if (err || !val) { + dev_err(isc_mgr->dev, "%s: ERROR %d dat_len = %d\n", + __func__, err, val); + continue; + } + d_cfg.val_bits = val; + + __isc_create_dev(isc_mgr, &d_cfg); + } +} + +static int isc_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 isc_mgr_get_pwr_map( + struct device *dev, struct device_node *np, + struct isc_mgr_platform_data *pd) +{ + int num_map_items = 0; + u32 pwr_map_val; + unsigned int i; + + for (i = 0; i < MAX_ISC_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_ISC_GPIOS; i++) + pd->pwr_mapping[i] = i; + + pd->num_pwr_map = pd->num_pwr_gpios; + + return -1; +} + +static struct isc_mgr_platform_data *of_isc_mgr_pdata(struct platform_device + *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct isc_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); + } + + 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(np, "i2c-bus", &pd->bus); + if (err) { + dev_err(&pdev->dev, "%s: missing i2c bus # DT %s\n", + __func__, np->full_name); + return ERR_PTR(-EEXIST); + } + dev_dbg(&pdev->dev, " i2c-bus: %d\n", pd->bus); + + err = of_property_read_u32(np, "csi-port", &pd->csi_port); + if (err) { + dev_err(&pdev->dev, "%s: missing csi port # DT %s\n", + __func__, np->full_name); + return ERR_PTR(-EEXIST); + } + dev_dbg(&pdev->dev, " csiport: %d\n", pd->csi_port); + + pd->num_pwr_gpios = isc_mgr_of_get_grp_gpio( + &pdev->dev, 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_misc_gpios = isc_mgr_of_get_grp_gpio( + &pdev->dev, np, "misc-gpios", + ARRAY_SIZE(pd->misc_gpios), pd->misc_gpios, pd->misc_flags); + if (pd->num_misc_gpios < 0) + return ERR_PTR(pd->num_misc_gpios); + + pd->default_pwr_on = of_property_read_bool(np, "default-power-on"); + pd->runtime_pwrctrl_off = + of_property_read_bool(np, "runtime-pwrctrl-off"); + + pd->ext_pwr_ctrl = 0; + ext_pwr_ctrl_des = + of_property_read_bool(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(np, "ext-pwr-ctrl-sensor"); + if (ext_pwr_ctrl_sensor == true) + pd->ext_pwr_ctrl |= 1 << 1; + + err = isc_mgr_get_pwr_map(&pdev->dev, np, pd); + if (err) + dev_err(&pdev->dev, + "%s: failed to map pwr items. Using default values\n", + __func__); + + return pd; +} + +static char *isc_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 isc_mgr_suspend(struct device *dev) +{ + /* Nothing required for isc-mgr suspend*/ + return 0; +} + +static int isc_mgr_resume(struct device *dev) +{ + struct pwm_device *pwm; + /* 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__); + } + return 0; +} + +static const struct dev_pm_ops isc_mgr_pm_ops = { + .suspend = isc_mgr_suspend, + .resume = isc_mgr_resume, + .runtime_suspend = isc_mgr_suspend, + .runtime_resume = isc_mgr_resume, +}; + +static int isc_mgr_probe(struct platform_device *pdev) +{ + int err = 0; + struct isc_mgr_priv *isc_mgr; + struct isc_mgr_platform_data *pd; + unsigned int i; + + dev_info(&pdev->dev, "%sing...\n", __func__); + + isc_mgr = devm_kzalloc(&pdev->dev, + sizeof(struct isc_mgr_priv), + GFP_KERNEL); + if (!isc_mgr) { + dev_err(&pdev->dev, "Unable to allocate memory!\n"); + return -ENOMEM; + } + + spin_lock_init(&isc_mgr->spinlock); + atomic_set(&isc_mgr->in_use, 0); + INIT_LIST_HEAD(&isc_mgr->dev_list); + mutex_init(&isc_mgr->mutex); + init_waitqueue_head(&isc_mgr->err_queue); + isc_mgr->err_irq_recvd = false; + isc_mgr->pwm = NULL; + + if (pdev->dev.of_node) { + pd = of_isc_mgr_pdata(pdev); + if (IS_ERR(pd)) + return PTR_ERR(pd); + isc_mgr->pdata = pd; + } else if (pdev->dev.platform_data) { + isc_mgr->pdata = pdev->dev.platform_data; + pd = isc_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")) { + isc_mgr->pwm = devm_pwm_get(&pdev->dev, NULL); + if (!IS_ERR(isc_mgr->pwm)) { + dev_info(&pdev->dev, + "%s: success to get PWM\n", __func__); + pwm_disable(isc_mgr->pwm); + } else { + err = PTR_ERR(isc_mgr->pwm); + if (err != -EPROBE_DEFER) + dev_err(&pdev->dev, + "%s: fail to get PWM\n", __func__); + return err; + } + } + + isc_mgr->adap = i2c_get_adapter(pd->bus); + if (!isc_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) + isc_mgr->pwr_state |= BIT(i); + } + } + + isc_mgr->err_irq = platform_get_irq_optional(pdev, 0); + if (isc_mgr->err_irq > 0) { + err = devm_request_irq(&pdev->dev, + isc_mgr->err_irq, + isc_mgr_isr, 0, pdev->name, isc_mgr); + if (err) { + dev_err(&pdev->dev, + "request_irq failed with err %d\n", err); + isc_mgr->err_irq = 0; + goto err_probe; + } + disable_irq(isc_mgr->err_irq); + atomic_set(&isc_mgr->irq_in_use, 0); + } + + isc_mgr->pdev = &pdev->dev; + dev_set_drvdata(&pdev->dev, isc_mgr); + + if (pd->drv_name) + snprintf(isc_mgr->devname, sizeof(isc_mgr->devname), + "%s.%x.%c", pd->drv_name, pd->bus, 'a' + pd->csi_port); + else + snprintf(isc_mgr->devname, sizeof(isc_mgr->devname), + "isc-mgr.%x.%c", pd->bus, 'a' + pd->csi_port); + + /* Request dynamic allocation of a device major number */ + err = alloc_chrdev_region(&isc_mgr->devt, + 0, ISC_DEV_MAX, isc_mgr->devname); + if (err < 0) { + dev_err(&pdev->dev, "failed to allocate char dev region %d\n", + err); + goto err_probe; + } + + /* poluate sysfs entries */ + isc_mgr->isc_class = class_create(THIS_MODULE, isc_mgr->devname); + if (IS_ERR(isc_mgr->isc_class)) { + err = PTR_ERR(isc_mgr->isc_class); + isc_mgr->isc_class = NULL; + dev_err(&pdev->dev, "failed to create class %d\n", + err); + goto err_probe; + } + + isc_mgr->isc_class->devnode = isc_mgr_devnode; + + /* connect the file operations with the cdev */ + cdev_init(&isc_mgr->cdev, &isc_mgr_fileops); + isc_mgr->cdev.owner = THIS_MODULE; + + /* connect the major/minor number to this dev */ + err = cdev_add(&isc_mgr->cdev, MKDEV(MAJOR(isc_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 isc-mgr */ + isc_mgr->dev = device_create(isc_mgr->isc_class, &pdev->dev, + isc_mgr->cdev.dev, + isc_mgr, + isc_mgr->devname); + if (IS_ERR(isc_mgr->dev)) { + err = PTR_ERR(isc_mgr->dev); + isc_mgr->dev = NULL; + dev_err(&pdev->dev, "failed to create device %d\n", err); + goto err_probe; + } + + isc_mgr_debugfs_init(isc_mgr); + INIT_WORK(&isc_mgr->ins_work, isc_mgr_dev_ins); + schedule_work(&isc_mgr->ins_work); + return 0; + +err_probe: + isc_mgr_del(isc_mgr); + return err; +} + +static int isc_mgr_remove(struct platform_device *pdev) +{ + struct isc_mgr_priv *isc_mgr = dev_get_drvdata(&pdev->dev); + + if (isc_mgr) { + isc_mgr_debugfs_remove(isc_mgr); + isc_mgr_del(isc_mgr); + + if (isc_mgr->dev) + device_destroy(isc_mgr->isc_class, + isc_mgr->cdev.dev); + if (isc_mgr->cdev.dev) + cdev_del(&isc_mgr->cdev); + + if (isc_mgr->isc_class) + class_destroy(isc_mgr->isc_class); + + if (isc_mgr->devt) + unregister_chrdev_region(isc_mgr->devt, ISC_DEV_MAX); + } + + return 0; +} + +static const struct of_device_id isc_mgr_of_match[] = { + { .compatible = "nvidia,isc-mgr", }, + { } +}; +MODULE_DEVICE_TABLE(of, isc_mgr_of_match); + +static struct platform_driver isc_mgr_driver = { + .driver = { + .name = "isc-mgr", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(isc_mgr_of_match), + .pm = &isc_mgr_pm_ops, + }, + .probe = isc_mgr_probe, + .remove = isc_mgr_remove, +}; + +module_platform_driver(isc_mgr_driver); + +MODULE_DESCRIPTION("tegra auto isc manager driver"); +MODULE_AUTHOR("Songhee Baek "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:isc_mgr"); +MODULE_SOFTDEP("pre: isc_pwm"); diff --git a/drivers/media/platform/tegra/isc/isc_pwm.c b/drivers/media/platform/tegra/isc/isc_pwm.c new file mode 100644 index 00000000..792f4126 --- /dev/null +++ b/drivers/media/platform/tegra/isc/isc_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 "isc-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 isc_pwm_of_match[] = { + { .compatible = "nvidia, isc-pwm", .data = NULL }, + {}, +}; + +static inline struct isc_pwm_info *to_isc_pwm_info(struct pwm_chip *chip) +{ + return container_of(chip, struct isc_pwm_info, chip); +} + +static int isc_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct isc_pwm_info *info = to_isc_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 isc_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct isc_pwm_info *info = to_isc_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 isc_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct isc_pwm_info *info = to_isc_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_isc_pwm_xlate(struct pwm_chip *pc, + const struct of_phandle_args *args) +{ + struct pwm_device *pwm; + struct isc_pwm_info *info = to_isc_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 isc_pwm_ops = { + .config = isc_pwm_config, + .enable = isc_pwm_enable, + .disable = isc_pwm_disable, + .owner = THIS_MODULE, +}; + +static int isc_pwm_probe(struct platform_device *pdev) +{ + struct isc_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 isc_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 = &isc_pwm_ops; + info->chip.base = -1; + info->chip.npwm = npwm; + info->chip.of_xlate = of_isc_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 isc_pwm_remove(struct platform_device *pdev) +{ + struct isc_pwm_info *info = platform_get_drvdata(pdev); + + pwmchip_remove(&info->chip); + + return 0; +} + +static int isc_pwm_suspend(struct device *dev) +{ + int err = 0; + struct isc_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 isc_pwm_resume(struct device *dev) +{ + /* Do nothing */ + return 0; +} + +static const struct dev_pm_ops isc_pwm_pm_ops = { + .suspend = isc_pwm_suspend, + .resume = isc_pwm_resume, + .runtime_suspend = isc_pwm_suspend, + .runtime_resume = isc_pwm_resume, +}; + +static struct platform_driver isc_pwm_driver = { + .driver = { + .name = "isc-pwm", + .owner = THIS_MODULE, + .of_match_table = isc_pwm_of_match, + .pm = &isc_pwm_pm_ops, + }, + .probe = isc_pwm_probe, + .remove = isc_pwm_remove, +}; + +module_platform_driver(isc_pwm_driver); + +MODULE_AUTHOR("Junghyun Kim "); +MODULE_DESCRIPTION("ISC PWM driver"); +MODULE_LICENSE("GPL v2"); +MODULE_DEVICE_TABLE(of, isc_pwm_of_match); +MODULE_SOFTDEP("pre: isc_dev"); diff --git a/include/media/isc-dev.h b/include/media/isc-dev.h new file mode 100644 index 00000000..9eeebd64 --- /dev/null +++ b/include/media/isc-dev.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __ISC_DEV_H__ +#define __ISC_DEV_H__ + +#include +#include + +#define MAX_ISC_NAME_LENGTH 32 + +struct isc_dev_platform_data { + struct device *pdev; /* parent device of isc_dev */ + int reg_bits; + int val_bits; + char drv_name[MAX_ISC_NAME_LENGTH]; +}; + +#endif /* __ISC_DEV_H__ */ diff --git a/include/media/isc-mgr.h b/include/media/isc-mgr.h new file mode 100644 index 00000000..b195b876 --- /dev/null +++ b/include/media/isc-mgr.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __TEGRA_ISC_MGR_H__ +#define __TEGRA_ISC_MGR_H__ + +#include + +#define MAX_ISC_GPIOS 8 + +struct isc_mgr_client { + struct mutex mutex; + struct list_head list; + struct i2c_client *client; + struct isc_mgr_new_dev cfg; + struct isc_dev_platform_data pdata; + int id; +}; + +struct isc_mgr_platform_data { + int bus; + int num_pwr_gpios; + u32 pwr_gpios[MAX_ISC_GPIOS]; + u32 pwr_flags[MAX_ISC_GPIOS]; + int num_pwr_map; + u32 pwr_mapping[MAX_ISC_GPIOS]; + int num_misc_gpios; + u32 misc_gpios[MAX_ISC_GPIOS]; + u32 misc_flags[MAX_ISC_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 */ +}; + +int isc_delete_lst(struct device *dev, struct i2c_client *client); + +#endif /* __TEGRA_ISC_MGR_H__ */ diff --git a/include/uapi/media/isc-dev.h b/include/uapi/media/isc-dev.h new file mode 100644 index 00000000..efa9077b --- /dev/null +++ b/include/uapi/media/isc-dev.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __UAPI_ISC_DEV_H__ +#define __UAPI_ISC_DEV_H__ + +#include +#include + +#define ISC_DEV_PKG_FLAG_WR 1 + +#define ISC_DEV_IOCTL_RW _IOW('o', 1, struct isc_dev_package) + +struct __attribute__ ((__packed__)) isc_dev_package { + __u16 offset; + __u16 offset_len; + __u32 size; + __u32 flags; + unsigned long buffer; +}; + +#ifdef __KERNEL__ +#ifdef CONFIG_COMPAT +#define ISC_DEV_IOCTL_RW32 _IOW('o', 1, struct isc_dev_package32) + +struct __attribute__ ((__packed__)) isc_dev_package32 { + __u16 offset; + __u16 offset_len; + __u32 size; + __u32 flags; + __u32 buffer; +}; +#endif /* CONFIG_COMPAT */ +#endif /* __KERNEL__ */ + +#endif /* __UAPI_ISC_DEV_H__ */ diff --git a/include/uapi/media/isc-mgr.h b/include/uapi/media/isc-mgr.h new file mode 100644 index 00000000..0bdc3db6 --- /dev/null +++ b/include/uapi/media/isc-mgr.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __UAPI_TEGRA_ISC_MGR_H__ +#define __UAPI_TEGRA_ISC_MGR_H__ + +#include +#include + +#define ISC_MGR_IOCTL_PWR_DN _IOW('o', 1, __s16) +#define ISC_MGR_IOCTL_PWR_UP _IOR('o', 2, __s16) +#define ISC_MGR_IOCTL_SET_PID _IOW('o', 3, struct isc_mgr_sinfo) +#define ISC_MGR_IOCTL_SIGNAL _IOW('o', 4, int) +#define ISC_MGR_IOCTL_DEV_ADD _IOW('o', 5, struct isc_mgr_new_dev) +#define ISC_MGR_IOCTL_DEV_DEL _IOW('o', 6, int) +#define ISC_MGR_IOCTL_PWR_INFO _IOW('o', 7, struct isc_mgr_pwr_info) +#define ISC_MGR_IOCTL_PWM_ENABLE _IOW('o', 8, int) +#define ISC_MGR_IOCTL_PWM_CONFIG _IOW('o', 9, struct isc_mgr_pwm_info) +#define ISC_MGR_IOCTL_WAIT_ERR _IO('o', 10) +#define ISC_MGR_IOCTL_ABORT_WAIT_ERR _IO('o', 11) +#define ISC_MGR_IOCTL_GET_EXT_PWR_CTRL _IOR('o', 12, __u8) + +#define ISC_MGR_POWER_ALL 5 +#define MAX_ISC_NAME_LENGTH 32 + +struct isc_mgr_new_dev { + __u16 addr; + __u8 reg_bits; + __u8 val_bits; + __u8 drv_name[MAX_ISC_NAME_LENGTH]; +}; + +struct isc_mgr_sinfo { + __s32 pid; + __s32 sig_no; + __u64 context; +}; + +struct isc_mgr_pwr_info { + __s32 pwr_gpio; + __s32 pwr_status; +}; + +struct isc_mgr_pwm_info { + __u64 duty_ns; + __u64 period_ns; +}; + +enum { + ISC_MGR_PWM_DISABLE = 0, + ISC_MGR_PWM_ENABLE, +}; + +enum { + ISC_MGR_SIGNAL_RESUME = 0, + ISC_MGR_SIGNAL_SUSPEND, +}; + +#endif /* __UAPI_TEGRA_ISC_MGR_H__ */ \ No newline at end of file