- 3fa71d3, [feat](saradc):Add pm api. - cfc34b, [fix](pwm):Change pwm setting don't need to disable pwm. Change-Id: If0f2ef94f163fd534ddcd8a07444081e7bc0fb5a
1126 lines
26 KiB
C
1126 lines
26 KiB
C
#include <linux/types.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/io.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/suspend.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/version.h>
|
|
#include <linux/ctype.h>
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0))
|
|
#include <linux/sched/signal.h>
|
|
#endif
|
|
#include <linux/security.h>
|
|
#include <linux/cred.h>
|
|
#include "tee_cv_private.h"
|
|
#include "base.h"
|
|
#include "vb.h"
|
|
#include "cvi_vb_proc.h"
|
|
#include "cvi_log_proc.h"
|
|
#include "cvi_sys_proc.h"
|
|
#include <linux/cvi_base_ctx.h>
|
|
#include <linux/semaphore.h>
|
|
#include <base_cb.h>
|
|
#include <sys_k.h>
|
|
#include <vip_common.h>
|
|
|
|
#include "linux/cv180x_efuse.h"
|
|
|
|
#ifndef UNUSED
|
|
#define UNUSED(x) ((void)(x))
|
|
#endif
|
|
|
|
#define BASE_CLASS_NAME "cvi-base"
|
|
#define BASE_DEV_NAME "cvi-base"
|
|
|
|
/* register bank */
|
|
#define TOP_BASE 0x03000000
|
|
#define TOP_REG_BANK_SIZE 0x10000
|
|
#define GP_REG3_OFFSET 0x8C
|
|
#define GP_REG_CHIP_ID_MASK 0xFFFF
|
|
|
|
#define CV183X_RTC_BASE 0x03005000
|
|
#define CV182X_RTC_BASE 0x05026000
|
|
#define RTC_REG_BANK_SIZE 0x140
|
|
#define RTC_ST_ON_REASON 0xF8
|
|
|
|
struct base_device {
|
|
struct device *dev;
|
|
struct miscdevice miscdev;
|
|
void *shared_mem;
|
|
u16 mmap_count;
|
|
struct list_head ps_list;
|
|
spinlock_t lock;
|
|
enum base_state_e state;
|
|
struct completion done;
|
|
struct notifier_block notifier;
|
|
u8 sig_hook;
|
|
};
|
|
|
|
struct base_state {
|
|
struct list_head list; /* state list */
|
|
struct base_device *ndev;
|
|
struct file *file;
|
|
unsigned int state_signr;
|
|
struct pid *state_pid;
|
|
const struct cred *cred;
|
|
void __user *state_context;
|
|
u32 secid;
|
|
};
|
|
|
|
static void __iomem *top_base;
|
|
static void __iomem *rtc_base;
|
|
static struct proc_dir_entry *proc_dir;
|
|
struct class *pbase_class;
|
|
|
|
const char * const CB_MOD_STR[] = CB_FOREACH_MOD(CB_GENERATE_STRING);
|
|
static struct base_m_cb_info base_m_cb[E_MODULE_BUTT];
|
|
|
|
#ifdef DRV_FPGA_PORTING
|
|
#define FPGA_EARLY_PORTING_CHIP_ID E_CHIPID_CV1822
|
|
#endif
|
|
|
|
static int __init base_init(void);
|
|
static void __exit base_exit(void);
|
|
|
|
uint32_t base_log_lv = CVI_BASE_DBG_ERR;
|
|
module_param(base_log_lv, int, 0644);
|
|
|
|
|
|
static ssize_t base_efuse_shadow_show(struct class *class,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
int ret = 0;
|
|
UNUSED(class);
|
|
UNUSED(attr);
|
|
|
|
ret = cvi_efuse_read_buf(0, buf, PAGE_SIZE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t base_efuse_shadow_store(struct class *class,
|
|
struct class_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned long addr;
|
|
uint32_t value = 0xDEAFBEEF;
|
|
int ret;
|
|
UNUSED(class);
|
|
UNUSED(attr);
|
|
|
|
ret = kstrtoul(buf, 0, &addr);
|
|
if (ret < 0) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "efuse_read: ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = cvi_efuse_read_buf(addr, &value, sizeof(value));
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_INFO, "efuse_read: 0x%04lx=0x%08x ret=%d\n", addr, value, ret);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t base_efuse_prog_show(struct class *class,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
UNUSED(class);
|
|
UNUSED(attr);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%s\n", "PROG_SHOW");
|
|
}
|
|
|
|
static ssize_t base_efuse_prog_store(struct class *class,
|
|
struct class_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int err;
|
|
uint32_t addr = 0, value = 0;
|
|
UNUSED(class);
|
|
UNUSED(attr);
|
|
|
|
if (sscanf(buf, "0x%x=0x%x", &addr, &value) != 2)
|
|
return -ENOMEM;
|
|
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_INFO, "%s: addr=0x%02x value=0x%08x\n", __func__, addr, value);
|
|
err = cvi_efuse_write(addr, value);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t base_uid_show(struct class *class,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
uint32_t uid_3 = 0xDEAFBEEF;
|
|
uint32_t uid_4 = 0xDEAFBEEF;
|
|
UNUSED(class);
|
|
UNUSED(attr);
|
|
|
|
uid_3 = cvi_efuse_read_from_shadow(0x0c);
|
|
uid_4 = cvi_efuse_read_from_shadow(0x10);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "UID: %08x_%08x\n", uid_3, uid_4);
|
|
}
|
|
|
|
static ssize_t base_rosc_show(struct class *class,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
int count = 0;
|
|
void __iomem *rosc_base;
|
|
UNUSED(class);
|
|
UNUSED(attr);
|
|
|
|
rosc_base = ioremap(0x030D0000, 0x2028);
|
|
|
|
count += scnprintf(buf + count, PAGE_SIZE - count,
|
|
"ROSC[0x030D0010]=0x%08X\n", ioread32(rosc_base + 0x0010));
|
|
count += scnprintf(buf + count, PAGE_SIZE - count,
|
|
"ROSC[0x030D0024]=0x%08X\n", ioread32(rosc_base + 0x0024));
|
|
count += scnprintf(buf + count, PAGE_SIZE - count,
|
|
"ROSC[0x030D0028]=0x%08X\n", ioread32(rosc_base + 0x0028));
|
|
count += scnprintf(buf + count, PAGE_SIZE - count,
|
|
"ROSC[0x030D2010]=0x%08X\n", ioread32(rosc_base + 0x2010));
|
|
count += scnprintf(buf + count, PAGE_SIZE - count,
|
|
"ROSC[0x030D2024]=0x%08X\n", ioread32(rosc_base + 0x2024));
|
|
count += scnprintf(buf + count, PAGE_SIZE - count,
|
|
"ROSC[0x030D2028]=0x%08X\n", ioread32(rosc_base + 0x2028));
|
|
|
|
iounmap(rosc_base);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t base_rosc_store(struct class *class,
|
|
struct class_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
uint32_t chip_id;
|
|
void __iomem *rosc_base;
|
|
UNUSED(class);
|
|
UNUSED(attr);
|
|
|
|
if (sysfs_streq(buf, "set")) {
|
|
chip_id = cvi_base_read_chip_id();
|
|
rosc_base = ioremap(0x030D0000, 0x2028);
|
|
if (IS_CHIP_CV182X(chip_id) || IS_CHIP_CV181X(chip_id)) {
|
|
iowrite32(0x00000000, rosc_base + 0x0004);
|
|
iowrite32(0x00000040, rosc_base + 0x000C);
|
|
iowrite32(0x01020009, rosc_base + 0x0014);
|
|
iowrite32(0x00FFFFFF, rosc_base + 0x001C);
|
|
iowrite32(0x00000001, rosc_base + 0x0004);
|
|
|
|
iowrite32(0x00000000, rosc_base + 0x2004);
|
|
iowrite32(0x00000040, rosc_base + 0x200C);
|
|
iowrite32(0x01020009, rosc_base + 0x2014);
|
|
iowrite32(0x00FFFFFF, rosc_base + 0x201C);
|
|
iowrite32(0x00000001, rosc_base + 0x2004);
|
|
} else {
|
|
iowrite32(0x01020009, rosc_base + 0x0014);
|
|
iowrite32(0x00FFFFFF, rosc_base + 0x001C);
|
|
iowrite32(0x00000000, rosc_base + 0x0004);
|
|
iowrite32(0x00000001, rosc_base + 0x0004);
|
|
|
|
iowrite32(0x01020009, rosc_base + 0x2014);
|
|
iowrite32(0x00FFFFFF, rosc_base + 0x201C);
|
|
iowrite32(0x00000000, rosc_base + 0x2004);
|
|
iowrite32(0x00000001, rosc_base + 0x2004);
|
|
}
|
|
iounmap(rosc_base);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
CLASS_ATTR_RW(base_efuse_shadow);
|
|
CLASS_ATTR_RW(base_efuse_prog);
|
|
CLASS_ATTR_RO(base_uid);
|
|
CLASS_ATTR_RW(base_rosc);
|
|
|
|
#ifndef FPGA_EARLY_PORTING_CHIP_ID
|
|
static unsigned int _cvi_base_read_by_kernel(unsigned int chip_id)
|
|
{
|
|
#ifdef __riscv
|
|
switch (chip_id) {
|
|
case 0x1810C:
|
|
return E_CHIPID_CV1810C;
|
|
case 0x1811C:
|
|
return E_CHIPID_CV1811C;
|
|
case 0x1812C:
|
|
return E_CHIPID_CV1812C;
|
|
case 0x1810F:
|
|
return E_CHIPID_CV1810H;
|
|
case 0x1811F:
|
|
return E_CHIPID_CV1811H;
|
|
case 0x1812F:
|
|
return E_CHIPID_CV1812H;
|
|
case 0x1813F:
|
|
return E_CHIPID_CV1813H;
|
|
}
|
|
#else
|
|
switch (chip_id) {
|
|
case 0x1810C:
|
|
return E_CHIPID_CV1820A;
|
|
case 0x1811C:
|
|
return E_CHIPID_CV1821A;
|
|
case 0x1812C:
|
|
return E_CHIPID_CV1822A;
|
|
case 0x1811F:
|
|
return E_CHIPID_CV1823A;
|
|
case 0x1812F:
|
|
return E_CHIPID_CV1825A;
|
|
case 0x1813F:
|
|
return E_CHIPID_CV1826A;
|
|
}
|
|
#endif
|
|
//cv181x default cv1810c
|
|
return E_CHIPID_CV1810C;
|
|
}
|
|
#endif
|
|
|
|
unsigned int cvi_base_read_chip_id(void)
|
|
{
|
|
#ifndef FPGA_EARLY_PORTING_CHIP_ID
|
|
|
|
unsigned int chip_id = ioread32(top_base + GP_REG3_OFFSET);
|
|
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_DEBUG, "chip_id=0x%x\n", chip_id);
|
|
|
|
switch (chip_id) {
|
|
case 0x1821:
|
|
return E_CHIPID_CV1821;
|
|
case 0x1822:
|
|
return E_CHIPID_CV1822;
|
|
case 0x1826:
|
|
return E_CHIPID_CV1826;
|
|
case 0x1832:
|
|
return E_CHIPID_CV1832;
|
|
case 0x1838:
|
|
return E_CHIPID_CV1838;
|
|
case 0x1829:
|
|
return E_CHIPID_CV1829;
|
|
case 0x1820:
|
|
return E_CHIPID_CV1820;
|
|
case 0x1823:
|
|
return E_CHIPID_CV1823;
|
|
case 0x1825:
|
|
return E_CHIPID_CV1825;
|
|
case 0x1835:
|
|
return E_CHIPID_CV1835;
|
|
|
|
case 0x1810C:
|
|
case 0x1811C:
|
|
case 0x1812C:
|
|
case 0x1810F:
|
|
case 0x1811F:
|
|
case 0x1812F:
|
|
case 0x1813F:
|
|
return _cvi_base_read_by_kernel(chip_id);
|
|
|
|
// cv180x
|
|
case 0x1800B:
|
|
return E_CHIPID_CV1800B;
|
|
case 0x1801B:
|
|
return E_CHIPID_CV1801B;
|
|
case 0x1800C:
|
|
return E_CHIPID_CV1800C;
|
|
case 0x1801C:
|
|
return E_CHIPID_CV1801C;
|
|
|
|
//default cv1835
|
|
default:
|
|
return E_CHIPID_CV1835;
|
|
}
|
|
#else
|
|
return FPGA_EARLY_PORTING_CHIP_ID;
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL_GPL(cvi_base_read_chip_id);
|
|
|
|
unsigned int cvi_base_read_chip_version(void)
|
|
{
|
|
unsigned int chip_version = 0;
|
|
|
|
chip_version = ioread32(top_base);
|
|
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_DEBUG, "chip_version=0x%x\n", chip_version);
|
|
|
|
switch (chip_version) {
|
|
case 0x18802000:
|
|
case 0x18220000:
|
|
case 0x18100000:
|
|
return E_CHIPVERSION_U01;
|
|
case 0x18802001:
|
|
case 0x18220001:
|
|
case 0x18100001:
|
|
return E_CHIPVERSION_U02;
|
|
default:
|
|
return E_CHIPVERSION_U01;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(cvi_base_read_chip_version);
|
|
|
|
unsigned int cvi_base_read_chip_pwr_on_reason(void)
|
|
{
|
|
unsigned int reason = 0;
|
|
|
|
reason = ioread32(rtc_base + RTC_ST_ON_REASON);
|
|
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_DEBUG, "pwr on reason = 0x%x\n", reason);
|
|
|
|
switch (reason) {
|
|
case 0x800d0000:
|
|
case 0x800f0000:
|
|
return E_CHIP_PWR_ON_COLDBOOT;
|
|
case 0x880d0003:
|
|
case 0x880f0003:
|
|
return E_CHIP_PWR_ON_WDT;
|
|
case 0x80050009:
|
|
case 0x800f0009:
|
|
return E_CHIP_PWR_ON_SUSPEND;
|
|
case 0x840d0003:
|
|
case 0x840f0003:
|
|
return E_CHIP_PWR_ON_WARM_RST;
|
|
default:
|
|
return E_CHIP_PWR_ON_COLDBOOT;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(cvi_base_read_chip_pwr_on_reason);
|
|
|
|
static int base_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct base_device *ndev = container_of(filp->private_data, struct base_device, miscdev);
|
|
struct base_state *ps;
|
|
int ret;
|
|
UNUSED(inode);
|
|
|
|
if (!ndev) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "cannot find base private data\n");
|
|
return -ENODEV;
|
|
}
|
|
ps = kzalloc(sizeof(struct base_state), GFP_KERNEL);
|
|
if (!ps) {
|
|
ret = -ENOMEM;
|
|
goto out_free_ps;
|
|
}
|
|
|
|
device_lock(ndev->miscdev.this_device);
|
|
ps->ndev = ndev;
|
|
INIT_LIST_HEAD(&ps->list);
|
|
ps->state_pid = get_pid(task_pid(current));
|
|
ps->cred = get_current_cred();
|
|
security_task_getsecid(current, &ps->secid);
|
|
/* memory barrier in smp case. */
|
|
smp_wmb();
|
|
/* replace the private data with base state */
|
|
filp->private_data = ps;
|
|
list_add_tail(&ps->list, &ndev->ps_list);
|
|
|
|
device_unlock(ndev->miscdev.this_device);
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_DEBUG, "base open ok\n");
|
|
|
|
return 0;
|
|
|
|
out_free_ps:
|
|
kfree(ps);
|
|
return ret;
|
|
}
|
|
|
|
static int base_release(struct inode *inode, struct file *filp)
|
|
{
|
|
struct base_state *ps = filp->private_data;
|
|
struct base_device *ndev = ps->ndev;
|
|
UNUSED(inode);
|
|
|
|
if (!ndev) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "cannot find base private data\n");
|
|
return -ENODEV;
|
|
}
|
|
device_lock(ndev->miscdev.this_device);
|
|
list_del_init(&ps->list);
|
|
device_unlock(ndev->miscdev.this_device);
|
|
put_pid(ps->state_pid);
|
|
put_cred(ps->cred);
|
|
kfree(ps);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void signal_state_change(struct base_device *ndev, enum base_state_e state)
|
|
{
|
|
struct base_state *ps;
|
|
struct siginfo sinfo;
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
|
|
sigval_t addr;
|
|
#endif
|
|
|
|
spin_lock(&ndev->lock);
|
|
ndev->state = state;
|
|
list_for_each_entry(ps, &ndev->ps_list, list) {
|
|
if (ps->state_signr) {
|
|
memset(&sinfo, 0, sizeof(sinfo));
|
|
sinfo.si_signo = ps->state_signr;
|
|
sinfo.si_errno = 0;
|
|
sinfo.si_code = SI_ASYNCIO;
|
|
sinfo.si_addr = ps->state_context;
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
|
|
addr.sival_ptr = sinfo.si_addr;
|
|
kill_pid_usb_asyncio(ps->state_signr, sinfo.si_errno, addr,
|
|
ps->state_pid, ps->cred);
|
|
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0))
|
|
kill_pid_info_as_cred(ps->state_signr, &sinfo,
|
|
ps->state_pid, ps->cred);
|
|
#else
|
|
kill_pid_info_as_cred(ps->state_signr, &sinfo,
|
|
ps->state_pid, ps->cred, ps->secid);
|
|
#endif
|
|
}
|
|
}
|
|
spin_unlock(&ndev->lock);
|
|
}
|
|
|
|
static int base_mmap(struct file *filp, struct vm_area_struct *vma)
|
|
{
|
|
struct base_state *ps = filp->private_data;
|
|
struct base_device *ndev = ps->ndev;
|
|
unsigned long vm_start = vma->vm_start;
|
|
unsigned int vm_size = vma->vm_end - vma->vm_start;
|
|
unsigned int offset = vma->vm_pgoff << PAGE_SHIFT;
|
|
void *pos = ndev->shared_mem;
|
|
|
|
if ((vm_size + offset) > BASE_SHARE_MEM_SIZE)
|
|
return -EINVAL;
|
|
|
|
while (vm_size > 0) {
|
|
if (remap_pfn_range(vma, vm_start, virt_to_pfn(pos), PAGE_SIZE, vma->vm_page_prot))
|
|
return -EAGAIN;
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_DEBUG, "mmap vir(%p) phys(%#llx)\n", pos, (u64)virt_to_phys((void *) pos));
|
|
vm_start += PAGE_SIZE;
|
|
pos += PAGE_SIZE;
|
|
vm_size -= PAGE_SIZE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long base_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct base_state *ps = filp->private_data;
|
|
struct base_device *ndev = ps->ndev;
|
|
long ret = 0;
|
|
|
|
switch (cmd) {
|
|
case IOCTL_READ_CHIP_ID: {
|
|
unsigned long chip_id = 0;
|
|
|
|
chip_id = cvi_base_read_chip_id();
|
|
if (copy_to_user((uint32_t *) arg, &chip_id, sizeof(unsigned int)))
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
case IOCTL_READ_CHIP_VERSION: {
|
|
unsigned long chip_version = 0;
|
|
|
|
chip_version = cvi_base_read_chip_version();
|
|
if (copy_to_user((uint32_t *) arg, &chip_version, sizeof(unsigned int)))
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
case IOCTL_READ_CHIP_PWR_ON_REASON: {
|
|
unsigned long reason = 0;
|
|
|
|
reason = cvi_base_read_chip_pwr_on_reason();
|
|
if (copy_to_user((uint32_t *) arg, &reason, sizeof(unsigned int)))
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
case IOCTL_STATESIG: {
|
|
struct base_statesignal ds;
|
|
|
|
if (copy_from_user(&ds, (void __user *)arg, sizeof(ds)))
|
|
return -EFAULT;
|
|
ps->state_signr = ds.signr;
|
|
ps->state_context = ds.context;
|
|
ndev->sig_hook = 1;
|
|
break;
|
|
}
|
|
#ifdef CONFIG_COMPAT
|
|
case IOCTL_STATESIG32: {
|
|
struct base_statesignal32 ds;
|
|
|
|
if (copy_from_user(&ds, arg, sizeof(ds)))
|
|
return -EFAULT;
|
|
ps->state_signr = ds.signr;
|
|
ps->state_context = compat_ptr(ds.context);
|
|
ndev->sig_hook = 1;
|
|
break;
|
|
}
|
|
#endif
|
|
case IOCTL_READ_STATE: {
|
|
unsigned int state;
|
|
|
|
spin_lock(&ndev->lock);
|
|
state = ndev->state;
|
|
spin_unlock(&ndev->lock);
|
|
if (copy_to_user((void __user *)arg, &state, sizeof(unsigned int)))
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
case IOCTL_USER_SUSPEND_DONE: {
|
|
spin_lock(&ndev->lock);
|
|
ndev->state = BASE_STATE_SUSPEND;
|
|
spin_unlock(&ndev->lock);
|
|
/* memory barrier in smp case. */
|
|
smp_wmb();
|
|
/* release the notifier */
|
|
complete(&ndev->done);
|
|
break;
|
|
}
|
|
case IOCTL_USER_RESUME_DONE: {
|
|
spin_lock(&ndev->lock);
|
|
ndev->state = BASE_STATE_NORMAL;
|
|
spin_unlock(&ndev->lock);
|
|
/* memory barrier in smp case. */
|
|
smp_wmb();
|
|
/* release the notifier */
|
|
complete(&ndev->done);
|
|
break;
|
|
}
|
|
case IOCTL_VB_CMD: {
|
|
struct vb_ext_control p;
|
|
|
|
if (copy_from_user(&p, (void __user *)arg, sizeof(struct vb_ext_control)))
|
|
return -EINVAL;
|
|
|
|
ret = vb_ctrl(&p);
|
|
if (copy_to_user((void __user *)arg, &p, sizeof(struct vb_ext_control)))
|
|
return -EINVAL;
|
|
break;
|
|
}
|
|
default:
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "Not support functions");
|
|
return -ENOTTY;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
static long compat_ptr_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
if (!file->f_op->unlocked_ioctl)
|
|
return -ENOIOCTLCMD;
|
|
|
|
return file->f_op->unlocked_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
|
|
}
|
|
#endif
|
|
|
|
static const struct file_operations base_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = base_open,
|
|
.release = base_release,
|
|
.mmap = base_mmap,
|
|
.unlocked_ioctl = base_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = compat_ptr_ioctl,
|
|
#endif
|
|
};
|
|
|
|
static int _register_dev(struct base_device *ndev)
|
|
{
|
|
int rc;
|
|
|
|
ndev->miscdev.minor = MISC_DYNAMIC_MINOR;
|
|
ndev->miscdev.name = BASE_DEV_NAME;
|
|
ndev->miscdev.fops = &base_fops;
|
|
|
|
rc = misc_register(&ndev->miscdev);
|
|
if (rc) {
|
|
dev_err(ndev->dev, "cvi_base: failed to register misc device.\n");
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define POWER_PROC_NAME "power"
|
|
#define POWER_PROC_PERMS (0644)
|
|
|
|
static u8 *sel_state[] = {
|
|
"suspend",
|
|
"resume",
|
|
};
|
|
|
|
static int proc_power_show(struct seq_file *m, void *v)
|
|
{
|
|
UNUSED(v);
|
|
seq_puts(m, "suspend resume\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_power_hdler(struct base_device *ndev, char const *input)
|
|
{
|
|
u32 num;
|
|
u8 str[80] = {0};
|
|
u8 t = 0;
|
|
u8 i, n;
|
|
u8 *p;
|
|
|
|
num = sscanf(input, "%s", str);
|
|
if (num > 1) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* convert to lower case for following type compare */
|
|
p = str;
|
|
for (; *p; ++p)
|
|
*p = tolower(*p);
|
|
n = ARRAY_SIZE(sel_state);
|
|
for (i = 0; i < n; i++) {
|
|
if (!strcmp(str, sel_state[i])) {
|
|
t = i;
|
|
break;
|
|
}
|
|
}
|
|
if (i == n)
|
|
return -EINVAL;
|
|
|
|
signal_state_change(ndev, t ? BASE_STATE_RESUME : BASE_STATE_SUSPEND_PREPARE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t power_proc_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
|
|
{
|
|
struct base_device *ndev = PDE_DATA(file_inode(file));
|
|
UNUSED(ppos);
|
|
|
|
set_power_hdler(ndev, user_buf);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int proc_power_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct base_device *ndev = PDE_DATA(inode);
|
|
|
|
return single_open(file, proc_power_show, ndev);
|
|
}
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
|
|
static const struct proc_ops _power_proc_fops = {
|
|
.proc_open = proc_power_open,
|
|
.proc_read = seq_read,
|
|
.proc_write = power_proc_write,
|
|
.proc_lseek = seq_lseek,
|
|
.proc_release = single_release,
|
|
};
|
|
#else
|
|
static const struct file_operations _power_proc_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = proc_power_open,
|
|
.read = seq_read,
|
|
.write = power_proc_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
#endif
|
|
|
|
static int power_proc_init(struct proc_dir_entry *_proc_dir, void *ndev)
|
|
{
|
|
int rc = 0;
|
|
|
|
/* create the /proc file */
|
|
if (proc_create_data(POWER_PROC_NAME, POWER_PROC_PERMS, _proc_dir, &_power_proc_fops, ndev) == NULL) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "power proc creation failed\n");
|
|
rc = -1;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int power_proc_remove(struct proc_dir_entry *_proc_dir)
|
|
{
|
|
remove_proc_entry(POWER_PROC_NAME, _proc_dir);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int base_suspend_user(struct base_device *ndev)
|
|
{
|
|
int ret;
|
|
|
|
if (!ndev->sig_hook)
|
|
return 0;
|
|
signal_state_change(ndev, BASE_STATE_SUSPEND_PREPARE);
|
|
ret = wait_for_completion_timeout(&ndev->done, usecs_to_jiffies(500000));
|
|
if (ret < 0) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_INFO, "user space suspend expired, state = %d\n", ndev->state);
|
|
ndev->state = BASE_STATE_SUSPEND;
|
|
return 0;
|
|
}
|
|
if (ndev->state != BASE_STATE_SUSPEND) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_INFO, "expect suspend(2) but state = %d\n", ndev->state);
|
|
ndev->state = BASE_STATE_SUSPEND;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int base_resume_user(struct base_device *ndev)
|
|
{
|
|
int ret;
|
|
|
|
if (!ndev->sig_hook)
|
|
return 0;
|
|
signal_state_change(ndev, BASE_STATE_RESUME);
|
|
ret = wait_for_completion_timeout(&ndev->done, usecs_to_jiffies(500000));
|
|
if (ret < 0) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_INFO, "user space resume expired, state = %d\n", ndev->state);
|
|
ndev->state = BASE_STATE_NORMAL;
|
|
return 0;
|
|
}
|
|
if (ndev->state != BASE_STATE_NORMAL) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_INFO, "expect normal(0) but state = %d\n", ndev->state);
|
|
ndev->state = BASE_STATE_NORMAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int base_pm_notif(struct notifier_block *b, unsigned long v, void *d)
|
|
{
|
|
struct base_device *ndev = container_of(b, struct base_device, notifier);
|
|
UNUSED(d);
|
|
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_INFO, "pm notif %lu\n", v);
|
|
switch (v) {
|
|
case PM_SUSPEND_PREPARE:
|
|
case PM_HIBERNATION_PREPARE:
|
|
case PM_RESTORE_PREPARE:
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_INFO, "suspending displays\n");
|
|
return base_suspend_user(ndev);
|
|
|
|
case PM_POST_SUSPEND:
|
|
case PM_POST_HIBERNATION:
|
|
case PM_POST_RESTORE:
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_INFO, "resuming displays\n");
|
|
return base_resume_user(ndev);
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int base_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct base_device *ndev;
|
|
struct resource *res;
|
|
void __iomem *reg_base;
|
|
int ret;
|
|
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_DEBUG, "start\n");
|
|
|
|
ndev = devm_kzalloc(&pdev->dev, sizeof(*ndev), GFP_KERNEL);
|
|
if (!ndev)
|
|
return -ENOMEM;
|
|
|
|
ndev->shared_mem = kzalloc(BASE_SHARE_MEM_SIZE, GFP_KERNEL);
|
|
if (!ndev->shared_mem)
|
|
return -ENOMEM;
|
|
|
|
proc_dir = proc_mkdir("cvitek", NULL);
|
|
if (vb_proc_init(proc_dir) < 0) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "vb proc init failed\n");
|
|
}
|
|
|
|
if (log_proc_init(proc_dir, ndev->shared_mem) < 0) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "log proc init failed\n");
|
|
}
|
|
|
|
if (sys_proc_init(proc_dir, ndev->shared_mem) < 0) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "sys proc init failed\n");
|
|
}
|
|
|
|
if (power_proc_init(proc_dir, ndev) < 0) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "power proc init failed\n");
|
|
}
|
|
|
|
spin_lock_init(&ndev->lock);
|
|
INIT_LIST_HEAD(&ndev->ps_list);
|
|
ndev->state = BASE_STATE_NORMAL;
|
|
ndev->dev = dev;
|
|
init_completion(&ndev->done);
|
|
|
|
/* Get vip_sys base address */
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
reg_base = devm_ioremap_resource(&pdev->dev, res);
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_INFO, "res-reg: start: 0x%llx, end: 0x%llx, virt-addr(%p).\n",
|
|
res->start, res->end, reg_base);
|
|
if (IS_ERR(reg_base)) {
|
|
ret = PTR_ERR(reg_base);
|
|
return ret;
|
|
}
|
|
|
|
vip_set_base_addr(reg_base);
|
|
|
|
vip_sys_set_offline(VIP_SYS_AXI_BUS_SC_TOP, true);
|
|
vip_sys_set_offline(VIP_SYS_AXI_BUS_ISP_RAW, true);
|
|
vip_sys_set_offline(VIP_SYS_AXI_BUS_ISP_YUV, false);
|
|
|
|
ndev->notifier.notifier_call = base_pm_notif;
|
|
register_pm_notifier(&ndev->notifier);
|
|
ret = _register_dev(ndev);
|
|
if (ret < 0) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "regsiter base chrdev error\n");
|
|
return ret;
|
|
}
|
|
|
|
memset(base_m_cb, 0, sizeof(struct base_m_cb_info) * E_MODULE_BUTT);
|
|
|
|
platform_set_drvdata(pdev, ndev);
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_DEBUG, "%s DONE\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int base_remove(struct platform_device *pdev)
|
|
{
|
|
struct base_device *ndev = platform_get_drvdata(pdev);
|
|
|
|
unregister_pm_notifier(&ndev->notifier);
|
|
vb_proc_remove(proc_dir);
|
|
log_proc_remove(proc_dir);
|
|
sys_proc_remove(proc_dir);
|
|
power_proc_remove(proc_dir);
|
|
proc_remove(proc_dir);
|
|
proc_dir = NULL;
|
|
kfree(ndev->shared_mem);
|
|
ndev->shared_mem = NULL;
|
|
|
|
misc_deregister(&ndev->miscdev);
|
|
platform_set_drvdata(pdev, NULL);
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_DEBUG, "%s DONE\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id cvi_base_dt_match[] = { { .compatible = "cvitek,base" }, {} };
|
|
|
|
static struct platform_driver base_driver = {
|
|
.probe = base_probe,
|
|
.remove = base_remove,
|
|
.driver = {
|
|
.name = BASE_DEV_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = cvi_base_dt_match,
|
|
},
|
|
};
|
|
|
|
static void base_cleanup(void)
|
|
{
|
|
class_remove_file(pbase_class, &class_attr_base_efuse_shadow);
|
|
class_remove_file(pbase_class, &class_attr_base_efuse_prog);
|
|
class_remove_file(pbase_class, &class_attr_base_uid);
|
|
class_remove_file(pbase_class, &class_attr_base_rosc);
|
|
class_destroy(pbase_class);
|
|
}
|
|
|
|
static int __init base_init(void)
|
|
{
|
|
int rc;
|
|
uint32_t chip_id;
|
|
|
|
top_base = ioremap(TOP_BASE, TOP_REG_BANK_SIZE);
|
|
|
|
pbase_class = class_create(THIS_MODULE, BASE_CLASS_NAME);
|
|
if (IS_ERR(pbase_class)) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "create class failed\n");
|
|
rc = PTR_ERR(pbase_class);
|
|
goto cleanup;
|
|
}
|
|
|
|
rc = class_create_file(pbase_class, &class_attr_base_efuse_shadow);
|
|
if (rc) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "base: can't create sysfs base_efuse_shadow file\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
rc = class_create_file(pbase_class, &class_attr_base_efuse_prog);
|
|
if (rc) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "base: can't create sysfs base_efuse_prog) file\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
rc = class_create_file(pbase_class, &class_attr_base_uid);
|
|
if (rc) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "base: can't create sysfs base_uid file\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
rc = class_create_file(pbase_class, &class_attr_base_rosc);
|
|
if (rc) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "base: can't create sysfs base_rosc file\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
rc = platform_driver_register(&base_driver);
|
|
chip_id = cvi_base_read_chip_id();
|
|
pr_notice("CVITEK CHIP ID = %d\n", chip_id);
|
|
|
|
sys_save_modules_cb(&base_m_cb[0]);
|
|
|
|
if (IS_CHIP_CV182X(chip_id) || IS_CHIP_CV181X(chip_id))
|
|
rtc_base = ioremap(CV182X_RTC_BASE, RTC_REG_BANK_SIZE);
|
|
else
|
|
rtc_base = ioremap(CV183X_RTC_BASE, RTC_REG_BANK_SIZE);
|
|
|
|
return 0;
|
|
|
|
cleanup:
|
|
base_cleanup();
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void __exit base_exit(void)
|
|
{
|
|
platform_driver_unregister(&base_driver);
|
|
vb_cleanup();
|
|
base_cleanup();
|
|
|
|
iounmap(top_base);
|
|
iounmap(rtc_base);
|
|
}
|
|
|
|
/* sensor cmm extern function. */
|
|
enum vip_sys_cmm {
|
|
VIP_CMM_I2C = 0,
|
|
VIP_CMM_SSP,
|
|
VIP_CMM_BUTT,
|
|
};
|
|
|
|
struct vip_sys_cmm_ops {
|
|
long (*cb)(void *hdlr, unsigned int cmd, void *arg);
|
|
};
|
|
|
|
struct vip_sys_cmm_dev {
|
|
enum vip_sys_cmm cmm_type;
|
|
void *hdlr;
|
|
struct vip_sys_cmm_ops ops;
|
|
};
|
|
static struct vip_sys_cmm_dev cmm_ssp;
|
|
static struct vip_sys_cmm_dev cmm_i2c;
|
|
|
|
int vip_sys_register_cmm_cb(unsigned long cmm, void *hdlr, void *cb)
|
|
{
|
|
struct vip_sys_cmm_dev *cmm_dev;
|
|
|
|
if ((cmm >= VIP_CMM_BUTT) || !hdlr || !cb)
|
|
return -1;
|
|
|
|
cmm_dev = (cmm == VIP_CMM_I2C) ? &cmm_i2c : &cmm_ssp;
|
|
|
|
cmm_dev->cmm_type = cmm;
|
|
cmm_dev->hdlr = hdlr;
|
|
cmm_dev->ops.cb = cb;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(vip_sys_register_cmm_cb);
|
|
|
|
int vip_sys_cmm_cb_i2c(unsigned int cmd, void *arg)
|
|
{
|
|
struct vip_sys_cmm_dev *cmm_dev = &cmm_i2c;
|
|
|
|
if (cmm_dev->cmm_type != VIP_CMM_I2C)
|
|
return -1;
|
|
|
|
return (cmm_dev->ops.cb) ? cmm_dev->ops.cb(cmm_dev->hdlr, cmd, arg) : (-1);
|
|
}
|
|
EXPORT_SYMBOL_GPL(vip_sys_cmm_cb_i2c);
|
|
|
|
int vip_sys_cmm_cb_ssp(unsigned int cmd, void *arg)
|
|
{
|
|
struct vip_sys_cmm_dev *cmm_dev = &cmm_ssp;
|
|
|
|
if (cmm_dev->cmm_type != VIP_CMM_SSP)
|
|
return -1;
|
|
|
|
return (cmm_dev->ops.cb) ? cmm_dev->ops.cb(cmm_dev->hdlr, cmd, arg) : (-1);
|
|
}
|
|
EXPORT_SYMBOL_GPL(vip_sys_cmm_cb_ssp);
|
|
|
|
int base_rm_module_cb(enum ENUM_MODULES_ID module_id)
|
|
{
|
|
if (module_id < 0 || module_id >= E_MODULE_BUTT) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "base rm cb error: wrong module_id\n");
|
|
return -1;
|
|
}
|
|
|
|
base_m_cb[module_id].dev = NULL;
|
|
base_m_cb[module_id].cb = NULL;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(base_rm_module_cb);
|
|
|
|
int base_reg_module_cb(struct base_m_cb_info *cb_info)
|
|
{
|
|
if (!cb_info || !cb_info->dev || !cb_info->cb) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "base reg cb error: no data\n");
|
|
return -1;
|
|
}
|
|
|
|
if (cb_info->module_id < 0 || cb_info->module_id >= E_MODULE_BUTT) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "base reg cb error: wrong module_id\n");
|
|
return -1;
|
|
}
|
|
|
|
base_m_cb[cb_info->module_id] = *cb_info;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(base_reg_module_cb);
|
|
|
|
int base_exe_module_cb(struct base_exe_m_cb *exe_cb)
|
|
{
|
|
struct base_m_cb_info *cb_info;
|
|
|
|
if (exe_cb->caller < 0 || exe_cb->caller >= E_MODULE_BUTT) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "base exe cb error: wrong caller\n");
|
|
return -1;
|
|
}
|
|
|
|
if (exe_cb->callee < 0 || exe_cb->callee >= E_MODULE_BUTT) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "base exe cb error: wrong callee\n");
|
|
return -1;
|
|
}
|
|
|
|
cb_info = &base_m_cb[exe_cb->callee];
|
|
|
|
if (!cb_info->cb) {
|
|
CVI_TRACE_BASE(CVI_BASE_DBG_ERR, "base exe cb error: cb of callee(%s) is null, caller(%s)\n",
|
|
IDTOSTR(exe_cb->callee), IDTOSTR(exe_cb->caller));
|
|
return -1;
|
|
}
|
|
|
|
return cb_info->cb(cb_info->dev, exe_cb->caller, exe_cb->cmd_id, exe_cb->data);
|
|
}
|
|
EXPORT_SYMBOL_GPL(base_exe_module_cb);
|
|
|
|
|
|
MODULE_DESCRIPTION("Cvitek base driver");
|
|
MODULE_LICENSE("GPL");
|
|
module_init(base_init);
|
|
module_exit(base_exit);
|