Files
linux-nv-oot/drivers/media/platform/tegra/cdi/cdi_mgr.c
Jon Hunter 27bc2f4757 media: camera: cdi-mgr: Fix crash freeing GPIOs
When booting Linux v6.10-rc5 on Tegra234 the CDI MGR driver was failing
to setup the GPIOs. If the driver fails to setup the GPIOs then we
expect the probe of the driver to fail. However, in this case when the
probe failed, it then crashed the kernel completely and the following
crash log was observed ...

 cdi-mgr sipl_devblk_3: cdi_mgr_configure_gpios Failed to allocate gpio
  desc
 cdi-mgr sipl_devblk_3: cdi_mgr_probe(): GPIO setup failed
 Unable to handle kernel NULL pointer dereference at virtual address
  0000000000000300
 ...
 Call trace:
  rt_spin_lock+0x24/0xa8
  devres_remove+0x38/0x174
  devres_release+0x1c/0x80
  devm_gpiod_put+0x24/0x4c
  cdi_delete_lst+0x18c/0x1200 [cdi_mgr]
  cdi_mgr_debugfs_init+0x608/0x11b0 [cdi_mgr]
  platform_probe+0x64/0xec
  really_probe+0xb8/0x284
  __driver_probe_device+0x74/0x12c
  driver_probe_device+0x38/0x114
  __driver_attach+0x74/0x13c
  bus_for_each_dev+0x74/0xf0
  driver_attach+0x20/0x30
  bus_add_driver+0xe4/0x20c
  driver_register+0x5c/0x130
  __platform_driver_register+0x24/0x34
  init_module+0x1c/0x1000 [cdi_mgr]
  do_one_initcall+0x44/0x2e8
  do_init_module+0x58/0x1f8
  load_module+0x1b94/0x1cf8
  init_module_from_file+0x84/0xc8
  __arm64_sys_finit_module+0x1b8/0x270
  invoke_syscall+0x40/0x11c
  el0_svc_common.constprop.0+0xc4/0xe4
  do_el0_svc+0x18/0x28
  el0_svc+0x30/0xe0
  el0t_64_sync_handler+0xc8/0xcc
  el0t_64_sync+0x16c/0x170
 Code: b9447420 d503201f 91006262 d2800000 (c8e07c41)
 ---[ end trace 0000000000000000]---

The problem is that the CDI MGR allocated the gpio descriptors by
calling the function devm_fwnode_gpiod_get_index(). This function
returns an ERR_PTR() on failure. When this does fail and cdi_mgr_del()
is called to clean-up, this does not check to see if the descriptor
IS_ERR(), but simply check if it is NULL or not, which is not correct.
Hence, we end up calling devm_gpiod_put() for an invalid descriptor and
the above crash is observed. Given that we are calling
devm_fwnode_gpiod_get_index() to allocate the descriptor, it is not
necessary to call devm_gpiod_put() at all, because the kernel will
automtically call this for us. Therefore, fix this by removing the call
to devm_gpiod_put() completely.

Please note that although this was caught with Linux v6.10-rc5, this is
applicable to all kernel versions.

Bug 4627271

Change-Id: I576a984ebaaeea590c5acea1b489284379832ae9
Signed-off-by: Jon Hunter <jonathanh@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3148004
Reviewed-by: Frank Chen <frankc@nvidia.com>
2024-07-12 22:38:49 -07:00

2100 lines
51 KiB
C

// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (C) 2015-2024 NVIDIA CORPORATION. All rights reserved.
#include <nvidia/conftest.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/regmap.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/of_irq.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <asm/siginfo.h>
#include <linux/rcupdate.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
#include <linux/i2c.h>
#include <linux/pwm.h>
#include <linux/debugfs.h>
#include <linux/nospec.h>
#include <linux/seq_file.h>
#include <media/cdi-dev.h>
#include <media/cdi-mgr.h>
#include <linux/gpio/consumer.h>
#include <linux/semaphore.h>
#include <asm/barrier.h>
#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 */
static struct semaphore tca9539_sem;
/* 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_wr(
struct cdi_mgr_priv *info, unsigned int offset, u8 val)
{
int ret = -ENODEV;
dev_dbg(info->dev, "%s\n", __func__);
mutex_lock(&info->mutex);
ret = tca9539_raw_wr(info->dev, &info->tca9539, offset, val);
mutex_unlock(&info->mutex);
return ret;
}
static int tca9539_rd(
struct cdi_mgr_priv *info, unsigned int offset, u8 *val)
{
int ret = -ENODEV;
dev_dbg(info->dev, "%s\n", __func__);
mutex_lock(&info->mutex);
ret = tca9539_raw_rd(info->dev, &info->tca9539, offset, val);
mutex_unlock(&info->mutex);
return ret;
}
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 int cdi_mgr_gpio_eq_event(
struct cdi_mgr_priv *cdi_mgr, struct cdi_mgr_gpio_intr evt)
{
struct cam_gpio_event_queue *queue;
u32 queue_cap;
if (!cdi_mgr)
return -EINVAL;
queue = &cdi_mgr->gpio_events;
queue_cap = ARRAY_SIZE(queue->events);
if (queue->count >= queue_cap) {
dev_err(cdi_mgr->dev,
"%s: failed to enqueue interrupt event, overflow\n",
__func__);
return -ENOMEM;
}
queue->events[(queue->head+queue->count)%queue_cap] = evt;
queue->count++;
return 0;
}
static int cdi_mgr_gpio_dq_event(
struct cdi_mgr_priv *cdi_mgr, struct cdi_mgr_gpio_intr *evt)
{
struct cam_gpio_event_queue *queue;
u32 queue_cap;
if (!cdi_mgr || !evt)
return -EINVAL;
queue = &cdi_mgr->gpio_events;
queue_cap = ARRAY_SIZE(queue->events);
if (queue->count == 0)
return -EIO;
*evt = queue->events[queue->head];
queue->head = (queue->head+1) % queue_cap;
queue->count--;
return 0;
}
static int cdi_mgr_gpio_eq_intr_timer(
struct cam_gpio_config *pin)
{
struct cdi_mgr_priv *cdi_mgr;
struct cam_gpio_timer_queue *queue;
u32 queue_cap;
ktime_t timeout_abs;
struct cdi_mgr_gpio_intr event;
if (!pin || !pin->mgr)
return -EINVAL;
/* Skip enqueue if timeout is disabled */
if (ktime_to_ms(pin->timeout) <= 0)
return 0;
cdi_mgr = pin->mgr;
queue = &pin->timers;
queue_cap = ARRAY_SIZE(queue->expires);
if (queue->count >= queue_cap) {
dev_err(cdi_mgr->dev,
"%s: failed to enqueue interrupt timer, overflow\n",
__func__);
/* Report cdi-mgr fault */
event.idx = pin->idx;
event.code = CDI_MGR_GPIO_INTR_FAULT;
if (cdi_mgr_gpio_eq_event(cdi_mgr, event) != 0)
dev_err(cdi_mgr->dev,
"%s: failed to enqueue cdi-mgr fault event, "
"idx %d\n", __func__, pin->idx);
return -ENOMEM;
}
timeout_abs = ktime_add(ktime_get(), pin->timeout);
if (!hrtimer_active(&queue->timer)) {
/* Start the interrupt timer immediately */
hrtimer_start(&queue->timer, timeout_abs, HRTIMER_MODE_ABS);
} else {
/* Enqueue interrupt timer */
queue->expires[(queue->head+queue->count)%queue_cap] = timeout_abs;
queue->count++;
}
return 0;
}
static int cdi_mgr_gpio_dq_intr_timer(
struct cam_gpio_config *pin)
{
unsigned long flags;
struct cdi_mgr_priv *cdi_mgr;
struct cam_gpio_timer_queue *queue;
u32 queue_cap;
ktime_t *expire;
struct cdi_mgr_gpio_intr event;
if (!pin || !pin->mgr)
return -EINVAL;
cdi_mgr = pin->mgr;
queue = &pin->timers;
queue_cap = ARRAY_SIZE(queue->expires);
spin_lock_irqsave(&cdi_mgr->spinlock, flags);
/* Report timer expiry */
event.idx = pin->idx;
event.code = CDI_MGR_GPIO_INTR_TIMEOUT;
if (cdi_mgr_gpio_eq_event(cdi_mgr, event) != 0)
dev_err(cdi_mgr->dev,
"%s: failed to enqueue interrupt timeout event, "
"idx %d\n", __func__, pin->idx);
/* Dequeue next interrupt timer */
if (queue->count == 0)
goto unlock;
expire = &queue->expires[queue->head];
hrtimer_start(&queue->timer, *expire, HRTIMER_MODE_ABS);
queue->head = (queue->head+1) % queue_cap;
queue->count--;
unlock:
spin_unlock_irqrestore(&cdi_mgr->spinlock, flags);
wake_up_interruptible(&cdi_mgr->gpio_events.wait);
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;
struct cam_gpio_config *pin;
struct cdi_mgr_gpio_intr event;
if (data) {
cdi_mgr = (struct cdi_mgr_priv *)data;
if (!cdi_mgr->intrs_enable)
return IRQ_HANDLED;
spin_lock_irqsave(&cdi_mgr->spinlock, flags);
for (i = 0; i < cdi_mgr->num_gpios; i++) {
pin = &cdi_mgr->gpios[i];
if (irq != pin->intr_irq)
continue;
event.idx = pin->idx;
event.code = CDI_MGR_GPIO_INTR;
if (cdi_mgr_gpio_eq_event(cdi_mgr, event) != 0)
pr_err("Failed to enqueue interrupt event, "
"idx %d\n", pin->idx);
if (cdi_mgr_gpio_eq_intr_timer(pin) != 0)
pr_err("Failed to enqueue interrupt timer, "
"idx %d\n", pin->idx);
}
spin_unlock_irqrestore(&cdi_mgr->spinlock, flags);
wake_up_interruptible(&cdi_mgr->gpio_events.wait);
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;
}
static enum hrtimer_restart cdi_mgr_intr_timer(struct hrtimer *timer)
{
struct cam_gpio_timer_queue *queue;
struct cam_gpio_config *pin;
if (timer == NULL)
goto error;
queue = container_of(timer, struct cam_gpio_timer_queue, timer);
pin = container_of(queue, struct cam_gpio_config, timers);
if (cdi_mgr_gpio_dq_intr_timer(pin) != 0)
goto error;
goto done;
error:
pr_err("Failed to handle GPIO interrupt timer expiry\n");
done:
return HRTIMER_NORESTART;
}
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]));
/* SW WAR for platform issue */
/* Add 5ms delay between two gpio toggles */
/* Bug 4125801*/
if (cdi_mgr->isP3898)
mdelay(5);
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 (gpio_request(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]));
gpio_free(pd->mcdi_gpios[i]);
}
}
return 0;
mcdi_ctrl_err:
for (; i >= 0; i--)
gpio_free(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 u32 gpio_to_idx(
struct cdi_mgr_priv *cdi_mgr, u32 idx)
{
int i;
for (i = 0; i < cdi_mgr->num_gpios; i++)
if (cdi_mgr->gpios[i].idx == idx)
return i;
return MAX_CDI_GPIOS;
}
static int cdi_mgr_gpio_config(
struct cdi_mgr_priv *cdi_mgr, const void __user *arg)
{
int err = 0;
struct cdi_mgr_gpio_info config;
u32 idx;
struct cam_gpio_config *pin;
if (!cdi_mgr)
return -EINVAL;
if (copy_from_user(&config, arg, sizeof(config))) {
dev_err(cdi_mgr->pdev,
"%s: failed to copy from user\n", __func__);
return -EFAULT;
}
idx = gpio_to_idx(cdi_mgr, config.idx);
if (idx >= MAX_CDI_GPIOS) {
dev_err(cdi_mgr->pdev, "%s: unknown gpio idx %u\n",
__func__, config.idx);
/* WAR: nvbugs/4087689 */
/* return -EFAULT; */
return 0;
}
pin = &cdi_mgr->gpios[idx];
/* Interrupt timeout */
if ((config.timeout_ms > 0) &&
(pin->gpio_dir != CAM_DEVBLK_GPIO_INPUT_INTR))
dev_warn(cdi_mgr->pdev,
"%s: gpio idx %u setting timeout for non-interrupt "
"type pin has no effect\n", __func__, idx);
pin->timeout = ms_to_ktime(config.timeout_ms);
return err;
}
static int cdi_mgr_gpio_intr_arm(
struct cdi_mgr_priv *cdi_mgr)
{
int i;
struct cam_gpio_config *pin;
if (!cdi_mgr)
return -EINVAL;
if (!atomic_xchg(&cdi_mgr->irq_in_use, 1))
for (i = 0; i < cdi_mgr->num_gpios; i++) {
pin = &cdi_mgr->gpios[i];
if ((pin->gpio_dir == CAM_DEVBLK_GPIO_INPUT_INTR) &&
(pin->intr_irq >= 0))
enable_irq(pin->intr_irq);
}
return 0;
}
static int cdi_mgr_gpio_intr_reset(struct cdi_mgr_priv *cdi_mgr)
{
unsigned long flags;
int i;
struct cam_gpio_config *pin;
struct cam_gpio_event_queue *evts_queue;
struct cam_gpio_timer_queue *tmrs_queue;
if (!cdi_mgr)
return -EINVAL;
evts_queue = &cdi_mgr->gpio_events;
spin_lock_irqsave(&cdi_mgr->spinlock, flags);
// Reset GPIO pin timer queues
for (i = 0; i < cdi_mgr->num_gpios; i++) {
pin = &cdi_mgr->gpios[i];
tmrs_queue = &pin->timers;
(void)hrtimer_try_to_cancel(&tmrs_queue->timer);
tmrs_queue->head = 0;
tmrs_queue->count = 0;
}
// Reset GPIO events queue
evts_queue->head = 0;
evts_queue->count = 0;
cdi_mgr->intrs_enable = false;
cdi_mgr->stop_err_irq_wait = false;
spin_unlock_irqrestore(&cdi_mgr->spinlock, flags);
return 0;
}
static int cdi_mgr_gpio_intr_wait(
struct cdi_mgr_priv *cdi_mgr, void __user *arg)
{
int err = 0;
int ret;
unsigned long flags;
struct cdi_mgr_gpio_intr event;
if (!cdi_mgr) {
err = -EINVAL;
goto done;
}
err = cdi_mgr_gpio_intr_arm(cdi_mgr);
if (err != 0) {
dev_err(cdi_mgr->pdev,
"%s: cdi_mgr_gpio_intr_arm failed %d\n",
__func__, err);
goto done;
}
ret = wait_event_interruptible(cdi_mgr->gpio_events.wait,
(cdi_mgr->stop_err_irq_wait
|| (cdi_mgr->gpio_events.count > 0)));
if (ret < 0) {
err = ret;
dev_err(cdi_mgr->pdev,
"%s: wait_event_interruptible failed %d\n",
__func__, err);
goto done;
}
if (cdi_mgr->stop_err_irq_wait) {
event.idx = 0;
event.code = CDI_MGR_GPIO_INTR_UNBLOCK;
if (copy_to_user(arg, &event, sizeof(event))) {
err = -EFAULT;
dev_err(cdi_mgr->pdev,
"%s: failed to copy to user\n", __func__);
}
cdi_mgr->stop_err_irq_wait = false;
goto done;
}
spin_lock_irqsave(&cdi_mgr->spinlock, flags);
ret = cdi_mgr_gpio_dq_event(cdi_mgr, &event);
spin_unlock_irqrestore(&cdi_mgr->spinlock, flags);
if (ret != 0) {
err = ret;
dev_err(cdi_mgr->pdev,
"%s: failed to dequeue interrupt event\n", __func__);
goto done;
}
if (copy_to_user(arg, &event, sizeof(event))) {
err = -EFAULT;
dev_err(cdi_mgr->pdev,
"%s: failed to copy to user\n", __func__);
goto done;
}
done:
return err;
}
static int cdi_mgr_gpio_intr_wait_abort(struct cdi_mgr_priv *cdi_mgr)
{
if (!cdi_mgr)
return -EINVAL;
cdi_mgr->stop_err_irq_wait = true;
wake_up_interruptible(&cdi_mgr->gpio_events.wait);
return 0;
}
static int cdi_mgr_des_power(
struct cdi_mgr_priv *cdi_mgr, bool enable)
{
u8 val;
/* if runtime_pwrctrl_off is not true, power on all here */
if (!cdi_mgr->pdata->runtime_pwrctrl_off) {
if (enable)
cdi_mgr_power_up(cdi_mgr, 0xffffffff);
else
cdi_mgr_power_down(cdi_mgr, 0xffffffff);
}
cdi_mgr_mcdi_ctrl(cdi_mgr, enable);
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_rd(cdi_mgr, 0x02, &val) != 0)
return -EFAULT;
if (enable)
val |= (0x10 << cdi_mgr->tca9539.power_port);
else
val &= ~(0x10 << cdi_mgr->tca9539.power_port);
if (tca9539_wr(cdi_mgr, 0x02, val) != 0)
return -EFAULT;
} else if (cdi_mgr->cim_ver == 2U) { /* P3714 A02 */
if (tca9539_rd(cdi_mgr, 0x03, &val) != 0)
return -EFAULT;
if (enable)
val |= (0x1 << cdi_mgr->tca9539.power_port);
else
val &= ~(0x1 << cdi_mgr->tca9539.power_port);
if (tca9539_wr(cdi_mgr, 0x03, val) != 0)
return -EFAULT;
}
up(&tca9539_sem);
}
return 0;
}
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->num_gpios; i++) {
if ((cdi_mgr->gpios[i].gpio_dir ==
CAM_DEVBLK_GPIO_INPUT_INTR) &&
cdi_mgr->gpios[i].intr_irq >= 0)
enable_irq(cdi_mgr->gpios[i].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_INTR_CONFIG:
err = cdi_mgr_gpio_config(cdi_mgr, (const void __user *)arg);
break;
case CDI_MGR_IOCTL_INTR_ENABLE:
err = cdi_mgr_gpio_intr_reset(cdi_mgr);
cdi_mgr->intrs_enable = true;
break;
case CDI_MGR_IOCTL_INTR_WAIT:
err = cdi_mgr_gpio_intr_wait(cdi_mgr, (void __user *)arg);
break;
case CDI_MGR_IOCTL_INTR_WAIT_ABORT:
err = cdi_mgr_gpio_intr_wait_abort(cdi_mgr);
break;
case CDI_MGR_IOCTL_GET_PWR_INFO:
err = cdi_mgr_get_pwr_ctrl_info(cdi_mgr, (void __user *)arg);
break;
case CDI_MGR_IOCTL_ENABLE_DES_POWER:
err = cdi_mgr_des_power(cdi_mgr, true);
break;
case CDI_MGR_IOCTL_DISABLE_DES_POWER:
err = cdi_mgr_des_power(cdi_mgr, false);
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 int cdi_mgr_open(struct inode *inode, struct file *file)
{
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;
dev_dbg(cdi_mgr->pdev, "%s\n", __func__);
file->private_data = cdi_mgr;
cdi_mgr->stop_err_irq_wait = false;
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;
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_rd(cdi_mgr, 0x02, &val) != 0)
return -EFAULT;
val &= ~(0x10 << cdi_mgr->tca9539.power_port);
if (tca9539_wr(cdi_mgr, 0x02, val) != 0)
return -EFAULT;
} else if (cdi_mgr->cim_ver == 2U) { /* P3714 A02 */
if (tca9539_rd(cdi_mgr, 0x03, &val) != 0)
return -EFAULT;
val &= ~(0x1 << cdi_mgr->tca9539.power_port);
if (tca9539_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 */
(void)cdi_mgr_gpio_intr_reset(cdi_mgr);
if (atomic_xchg(&cdi_mgr->irq_in_use, 0)) {
for (i = 0; i < cdi_mgr->num_gpios; i++) {
if ((cdi_mgr->gpios[i].gpio_dir == CAM_DEVBLK_GPIO_INPUT_INTR) &&
cdi_mgr->gpios[i].intr_irq >= 0)
disable_irq(cdi_mgr->gpios[i].intr_irq);
}
cdi_mgr->stop_err_irq_wait = true;
wake_up_interruptible(&cdi_mgr->gpio_events.wait);
}
/* 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);
}
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)
{
char prop_name[32]; /* 32 is max size of property name */
int num, i;
snprintf(prop_name, sizeof(prop_name), "%s-gpios", name);
num = gpiod_count(dev, name);
dev_dbg(dev, " num gpios of %s: %d\n", prop_name, num);
if (num < 0)
return 0;
for (i = 0; (i < num) && (i < size); i++) {
#if defined(NV_OF_GET_NAMED_GPIO_FLAGS_PRESENT) /* Linux 6.2 */
grp[i] = of_get_named_gpio_flags(np, prop_name, i, &flags[i]);
#else
grp[i] = of_get_named_gpio(np, prop_name, i);
flags[i] = 0;
#endif
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, np, "pwdn",
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, np, "mcdi",
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;
}
#if defined(NV_CLASS_STRUCT_DEVNODE_HAS_CONST_DEV_ARG) /* Linux v6.2 */
static char *cdi_mgr_devnode(const struct device *dev, umode_t *mode)
#else
static char *cdi_mgr_devnode(struct device *dev, umode_t *mode)
#endif
{
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_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_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->gpios[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->gpios[idx].desc);
if (gpio_irq < 0) {
dev_err(dev, "gpiod_to_irq() failed: %d\n", gpio_irq);
return gpio_irq;
}
cdi_mgr->gpios[idx].intr_irq = gpio_irq;
ret = devm_request_irq(dev,
cdi_mgr->gpios[idx].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->gpios[idx].intr_irq);
atomic_set(&cdi_mgr->irq_in_use, 0);
cdi_mgr->gpios[idx].gpio_dir = CAM_DEVBLK_GPIO_INPUT_INTR;
cdi_mgr->gpios[idx].idx = 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 num_gpios = 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;
num_gpios = of_get_child_count(node);
if (!num_gpios || num_gpios > MAX_CDI_GPIOS) {
dev_err(dev, "%s Invalid number of gpios : %d\n",
__func__, num_gpios);
return -EINVAL;
}
dev_dbg(dev, "%s gpio node count : %d\n", __func__, num_gpios);
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->num_gpios; j++) {
if (cdi_mgr->gpios[j].idx == gpio_index) {
dev_err(dev, "%s GPIO already in use\n", __func__);
return -EPERM;
}
}
cdi_mgr->gpios[i].desc = devm_fwnode_gpiod_get_index(dev,
&child->fwnode, "devblk", 0, GPIOD_ASIS, NULL);
if (IS_ERR(cdi_mgr->gpios[i].desc)) {
ret = PTR_ERR(cdi_mgr->gpios[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->num_gpios++;
}
}
} 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 cam_gpio_config *pin = NULL;
struct device_node *child = NULL;
struct device_node *child_tca9539 = NULL;
struct device_node *root_node = NULL;
const char *model;
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->gpio_events.wait);
cdi_mgr->pwm = NULL;
for (i = 0; i < ARRAY_SIZE(cdi_mgr->gpios); i++) {
pin = &cdi_mgr->gpios[i];
pin->mgr = cdi_mgr;
hrtimer_init(&pin->timers.timer, CLOCK_MONOTONIC,
HRTIMER_MODE_ABS);
pin->timers.timer.function = &cdi_mgr_intr_timer;
}
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;
}
cdi_mgr->isP3898 = false;
root_node = of_root;
if (root_node) {
model = of_get_property(root_node, "model", NULL);
dev_info(&pdev->dev, "platform name: %s\n", model);
if (!strcmp(model, "p3898-0010"))
cdi_mgr->isP3898 = true;
}
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 (gpio_request(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.%d.%c", pd->drv_name, pd->bus,
'a' + pd->csi_port);
else
err = snprintf(cdi_mgr->devname, sizeof(cdi_mgr->devname),
"cdi-mgr.%d.%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 */
#if defined(NV_CLASS_CREATE_HAS_NO_OWNER_ARG) /* Linux v6.4 */
cdi_mgr->cdi_class = class_create(cdi_mgr->devname);
#else
cdi_mgr->cdi_class = class_create(THIS_MODULE, cdi_mgr->devname);
#endif
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_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_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_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_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_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_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 <sbeak@nvidia.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:cdi_mgr");
MODULE_SOFTDEP("pre: cdi_pwm");