mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
media: camera: Build ISC as OOT module
Convert automotive ISC kernel driver to OOT module. Jira CAMERASW-7592 Change-Id: I0a81b58df941ef38bf0fb3226086e13b6d68d012 Signed-off-by: Frank Chen <frankc@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2811344 Reviewed-by: Justin Kim (SW-TEGRA) <juskim@nvidia.com> Reviewed-by: Shiva Dubey <sdubey@nvidia.com> GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
This commit is contained in:
committed by
mobile promotions
parent
c126c2ee93
commit
968c9df390
@@ -2,3 +2,4 @@
|
|||||||
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||||
|
|
||||||
obj-m += cdi/
|
obj-m += cdi/
|
||||||
|
obj-m += isc/
|
||||||
|
|||||||
6
drivers/media/platform/tegra/isc/Makefile
Normal file
6
drivers/media/platform/tegra/isc/Makefile
Normal file
@@ -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
|
||||||
244
drivers/media/platform/tegra/isc/debugfs.c
Normal file
244
drivers/media/platform/tegra/isc/debugfs.c
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||||
|
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/uaccess.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/debugfs.h>
|
||||||
|
#include <linux/seq_file.h>
|
||||||
|
#include <asm/siginfo.h>
|
||||||
|
#include <media/isc-dev.h>
|
||||||
|
#include <media/isc-mgr.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
33
drivers/media/platform/tegra/isc/isc-dev-priv.h
Normal file
33
drivers/media/platform/tegra/isc/isc-dev-priv.h
Normal file
@@ -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 <linux/cdev.h>
|
||||||
|
|
||||||
|
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__ */
|
||||||
29
drivers/media/platform/tegra/isc/isc-gpio-priv.h
Normal file
29
drivers/media/platform/tegra/isc/isc-gpio-priv.h
Normal file
@@ -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__ */
|
||||||
44
drivers/media/platform/tegra/isc/isc-mgr-priv.h
Normal file
44
drivers/media/platform/tegra/isc/isc-mgr-priv.h
Normal file
@@ -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 <linux/cdev.h>
|
||||||
|
#include <linux/version.h>
|
||||||
|
|
||||||
|
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__ */
|
||||||
17
drivers/media/platform/tegra/isc/isc-pwm-priv.h
Normal file
17
drivers/media/platform/tegra/isc/isc-pwm-priv.h
Normal file
@@ -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__ */
|
||||||
626
drivers/media/platform/tegra/isc/isc_dev.c
Normal file
626
drivers/media/platform/tegra/isc/isc_dev.c
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Copyright (c) 2015-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||||
|
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/regulator/consumer.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/uaccess.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/debugfs.h>
|
||||||
|
#include <linux/seq_file.h>
|
||||||
|
#include <media/isc-dev.h>
|
||||||
|
#include <uapi/media/isc-mgr.h>
|
||||||
|
|
||||||
|
#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 <chahuang@nvidia.com>");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
|
MODULE_SOFTDEP("pre: isc_gpio");
|
||||||
337
drivers/media/platform/tegra/isc/isc_gpio.c
Normal file
337
drivers/media/platform/tegra/isc/isc_gpio.c
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Copyright (c) 2017-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||||
|
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/atomic.h>
|
||||||
|
#include <linux/gpio.h>
|
||||||
|
#include <linux/of_gpio.h>
|
||||||
|
#include <linux/nospec.h>
|
||||||
|
|
||||||
|
#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 <adosapati@nvidia.com>");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
1208
drivers/media/platform/tegra/isc/isc_mgr.c
Normal file
1208
drivers/media/platform/tegra/isc/isc_mgr.c
Normal file
File diff suppressed because it is too large
Load Diff
242
drivers/media/platform/tegra/isc/isc_pwm.c
Normal file
242
drivers/media/platform/tegra/isc/isc_pwm.c
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Copyright (c) 2016-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
|
#include <linux/pwm.h>
|
||||||
|
#include <linux/atomic.h>
|
||||||
|
|
||||||
|
#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 <juskim@nvidia.com>");
|
||||||
|
MODULE_DESCRIPTION("ISC PWM driver");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
|
MODULE_DEVICE_TABLE(of, isc_pwm_of_match);
|
||||||
|
MODULE_SOFTDEP("pre: isc_dev");
|
||||||
21
include/media/isc-dev.h
Normal file
21
include/media/isc-dev.h
Normal file
@@ -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 <uapi/media/isc-dev.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
|
||||||
|
#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__ */
|
||||||
41
include/media/isc-mgr.h
Normal file
41
include/media/isc-mgr.h
Normal file
@@ -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 <uapi/media/isc-mgr.h>
|
||||||
|
|
||||||
|
#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__ */
|
||||||
38
include/uapi/media/isc-dev.h
Normal file
38
include/uapi/media/isc-dev.h
Normal file
@@ -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 <linux/ioctl.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#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__ */
|
||||||
61
include/uapi/media/isc-mgr.h
Normal file
61
include/uapi/media/isc-mgr.h
Normal file
@@ -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 <linux/ioctl.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#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__ */
|
||||||
Reference in New Issue
Block a user