blob: bdf23fd59d20bd624b6de60560f28d9f55c4a36d [file] [log] [blame]
/*
* Encoder device driver (kernel module)
*
* Copyright (c) 2013-2018, VeriSilicon Inc.
* Copyright (C) 2012 Google Finland Oy.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
--------------------------------------------------------------------------------
--
-- Abstract : 6280/7280/8270/8290/H1 Encoder device driver (kernel module)
--
------------------------------------------------------------------------------*/
#include <linux/kernel.h>
#include <linux/module.h>
/* needed for __init,__exit directives */
#include <linux/init.h>
/* needed for remap_page_range
SetPageReserved
ClearPageReserved
*/
#include <linux/mm.h>
/* obviously, for kmalloc */
#include <linux/slab.h>
/* for struct file_operations, register_chrdev() */
#include <linux/fs.h>
/* standard error codes */
#include <linux/errno.h>
#include <linux/moduleparam.h>
/* request_irq(), free_irq() */
#include <linux/interrupt.h>
#include <linux/sched.h>
/* needed for virt_to_phys() */
#include <linux/io.h>
#include <linux/pci.h>
#include <linux/uaccess.h>
#include <linux/ioport.h>
#include <asm/irq.h>
#include <linux/of_irq.h>
#include <linux/version.h>
/* our own stuff */
#include <linux/hx280enc.h>
#ifndef VSI
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/clk.h>
//static int hantro_h1_major = -1; /* dynamic allocation */
static struct class *hantro_h1_class;
#define DEVICE_NAME "mxc_hantro_h1"
static struct device *hantro_h1_dev;
static struct clk *hantro_clk_h1;
static struct clk *hantro_clk_h1_bus;
#define IRQF_DISABLED 0x0
#endif
#include <linux/delay.h>
/* module description */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Google Finland Oy");
MODULE_DESCRIPTION("Hantro 6280/7280/8270/8290/H1 Encoder driver");
/* this is ARM Integrator specific stuff */
#ifndef VSI
#define INTEGRATOR_LOGIC_MODULE0_BASE 0x38320000
#else
#define INTEGRATOR_LOGIC_MODULE0_BASE 0xC0000000
#endif
#define BLK_CTL_BASE 0x38330000 //0x38320000
/*
#define INTEGRATOR_LOGIC_MODULE1_BASE 0xD0000000
#define INTEGRATOR_LOGIC_MODULE2_BASE 0xE0000000
#define INTEGRATOR_LOGIC_MODULE3_BASE 0xF0000000
*/
#define VP_PB_INT_LT 30
/*
#define INT_EXPINT1 10
#define INT_EXPINT2 11
#define INT_EXPINT3 12
*/
/* these could be module params in the future */
#define ENC_IO_BASE INTEGRATOR_LOGIC_MODULE0_BASE
#define ENC_IO_SIZE (500 * 4) /* bytes */
#define ENC_HW_ID1 0x62800000
#define ENC_HW_ID2 0x72800000
#define ENC_HW_ID3 0x82700000
#define ENC_HW_ID4 0x82900000
#define ENC_HW_ID5 0x48310000
#define HX280ENC_BUF_SIZE 0
static unsigned long base_port = INTEGRATOR_LOGIC_MODULE0_BASE;
static int irq = VP_PB_INT_LT;
/* for critical data access */
static DEFINE_SPINLOCK(owner_lock);
/* for irq wait */
static DECLARE_WAIT_QUEUE_HEAD(enc_wait_queue);
/* for reserve hw */
static DECLARE_WAIT_QUEUE_HEAD(enc_hw_queue);
/* module_param(name, type, perm) */
module_param(base_port, ulong, 0644);
module_param(irq, int, 0644);
/* and this is our MAJOR; use 0 for dynamic allocation (recommended)*/
static int hx280enc_major;
/* here's all the must remember stuff */
typedef struct {
u32 hw_id; //hw id to indicate project
u32 is_valid; //indicate this core is hantro's core or not
u32 is_reserved; //indicate this core is occupied by user or not
int pid; //indicate which process is occupying the core
u32 irq_received; //indicate this core receives irq
u32 irq_status;
int irq;
unsigned long iobaseaddr;
unsigned int iosize;
volatile u8 *hwregs;
struct fasync_struct *async_queue;
struct device *dev;
struct mutex dev_mutex;
} hx280enc_t;
/* dynamic allocation? */
static hx280enc_t hx280enc_data;
static int ReserveIO(void);
static void ReleaseIO(void);
static void ResetAsic(hx280enc_t *dev);
#ifdef HX280ENC_DEBUG
static void dump_regs(unsigned long data);
#endif
/* IRQ handler */
static irqreturn_t hx280enc_isr(int irq, void *dev_id);
#ifndef VSI
static int hantro_h1_clk_enable(struct device *dev)
{
clk_prepare(hantro_clk_h1);
clk_enable(hantro_clk_h1);
clk_prepare(hantro_clk_h1_bus);
clk_enable(hantro_clk_h1_bus);
return 0;
}
static int hantro_h1_clk_disable(struct device *dev)
{
if (hantro_clk_h1) {
clk_disable(hantro_clk_h1);
clk_unprepare(hantro_clk_h1);
}
if (hantro_clk_h1_bus) {
clk_disable(hantro_clk_h1_bus);
clk_unprepare(hantro_clk_h1_bus);
}
return 0;
}
static int hantro_h1_ctrlblk_reset(struct device *dev)
{
volatile u8 *iobase;
u32 val;
//config H1
hantro_h1_clk_enable(dev);
iobase = (volatile u8 *)ioremap_nocache(BLK_CTL_BASE, 0x10000);
val = ioread32(iobase);
val &= (~0x4);
iowrite32(val, iobase); // assert soft reset
udelay(2);
val = ioread32(iobase);
val |= 0x4;
iowrite32(val, iobase); // release soft reset
val = ioread32(iobase+0x4);
val |= 0x4;
iowrite32(val, iobase + 0x4); // enable clock
iowrite32(0xFFFFFFFF, iobase + 0x14); // H1 fuse encoder enable
iounmap(iobase);
hantro_h1_clk_disable(dev);
return 0;
}
static int hantro_h1_power_on_disirq(hx280enc_t *hx280enc)
{
//spin_lock_irq(&owner_lock);
mutex_lock(&hx280enc->dev_mutex);
disable_irq(hx280enc->irq);
pm_runtime_get_sync(hx280enc->dev);
enable_irq(hx280enc->irq);
mutex_unlock(&hx280enc->dev_mutex);
//spin_unlock_irq(&owner_lock);
return 0;
}
#endif
/* the device's mmap method. The VFS has kindly prepared the process's
* vm_area_struct for us, so we examine this to see what was requested.
*/
static int hx280enc_mmap(struct file *filp, struct vm_area_struct *vm)
{
#ifdef VSI
int result = -EINVAL;
result = -EINVAL;
vma->vm_ops = &hx280enc_vm_ops;
return result;
#else
if (vm->vm_pgoff == (hx280enc_data.iobaseaddr >> PAGE_SHIFT)) {
vm->vm_flags |= VM_IO;
vm->vm_page_prot = pgprot_noncached(vm->vm_page_prot);
PDEBUG("hx280enc mmap: size=0x%lX, page off=0x%lX\n", (vm->vm_end - vm->vm_start), vm->vm_pgoff);
return remap_pfn_range(vm, vm->vm_start, vm->vm_pgoff, vm->vm_end - vm->vm_start,
vm->vm_page_prot) ? -EAGAIN : 0;
} else {
pr_err("invalid map offset :0x%lX\n", vm->vm_pgoff);
return -EINVAL;
}
#endif
}
static int CheckEncIrq(hx280enc_t *dev)
{
unsigned long flags;
int rdy = 0;
spin_lock_irqsave(&owner_lock, flags);
if (dev->irq_received) {
/* reset the wait condition(s) */
PDEBUG("check irq ready\n");
dev->irq_received = 0;
rdy = 1;
}
spin_unlock_irqrestore(&owner_lock, flags);
//printk("rdy=%d\n",rdy);
return rdy;
}
unsigned int WaitEncReady(hx280enc_t *dev)
{
PDEBUG("WaitEncReady\n");
if (wait_event_interruptible(enc_wait_queue, CheckEncIrq(dev))) {
PDEBUG("ENC wait_event_interruptible interrupted\n");
return -ERESTARTSYS;
}
return 0;
}
int CheckCoreOccupation(hx280enc_t *dev)
{
int ret = 0;
unsigned long flags;
spin_lock_irqsave(&owner_lock, flags);
if (!dev->is_reserved) {
dev->is_reserved = 1;
dev->pid = current->pid;
ret = 1;
PDEBUG("CheckCoreOccupation pid=%d\n", dev->pid);
}
spin_unlock_irqrestore(&owner_lock, flags);
return ret;
}
int GetWorkableCore(hx280enc_t *dev)
{
int ret = 0;
PDEBUG("GetWorkableCore\n");
if (dev->is_valid && CheckCoreOccupation(dev))
ret = 1;
return ret;
}
long ReserveEncoder(hx280enc_t *dev)
{
/* lock a core that has specified core id*/
if (wait_event_interruptible(enc_hw_queue, GetWorkableCore(dev) != 0))
return -ERESTARTSYS;
return 0;
}
void ReleaseEncoder(hx280enc_t *dev)
{
unsigned long flags;
PDEBUG("ReleaseEncoder\n");
spin_lock_irqsave(&owner_lock, flags);
PDEBUG("relase reseve by pid=%d with current->pid=%d\n", dev->pid, current->pid);
if (dev->is_reserved && dev->pid == current->pid) {
dev->pid = -1;
dev->is_reserved = 0;
}
dev->irq_received = 0;
dev->irq_status = 0;
spin_unlock_irqrestore(&owner_lock, flags);
wake_up_interruptible_all(&enc_hw_queue);
}
static long hx280enc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0;
PDEBUG("ioctl cmd 0x%X\n", cmd);
/*
* extract the type and number bitfields, and don't encode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != HX280ENC_IOC_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > HX280ENC_IOC_MAXNR)
return -ENOTTY;
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void *) arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void *) arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
switch (_IOC_NR(cmd)) {
case _IOC_NR(HX280ENC_IOCGHWOFFSET):
__put_user(hx280enc_data.iobaseaddr, (unsigned long *) arg);
break;
case _IOC_NR(HX280ENC_IOCGHWIOSIZE):
__put_user(hx280enc_data.iosize, (unsigned int *) arg);
break;
case _IOC_NR(HX280ENC_IOCH_ENC_RESERVE): {
int ret;
PDEBUG("Reserve ENC Cores\n");
ret = ReserveEncoder(&hx280enc_data);
return ret;
}
case _IOC_NR(HX280ENC_IOCH_ENC_RELEASE):
PDEBUG("Release ENC Core\n");
ReleaseEncoder(&hx280enc_data);
break;
case _IOC_NR(HX280ENC_IOCG_CORE_WAIT): {
int ret;
ret = WaitEncReady(&hx280enc_data);
return ret;
}
}
return 0;
}
static int hx280enc_open(struct inode *inode, struct file *filp)
{
int result = 0;
hx280enc_t *dev = &hx280enc_data;
filp->private_data = (void *) dev;
#ifndef VSI
hantro_h1_clk_enable(dev->dev);
hantro_h1_power_on_disirq(dev);
#endif
PDEBUG("dev opened\n");
return result;
}
static int hx280enc_release(struct inode *inode, struct file *filp)
{
hx280enc_t *dev = (hx280enc_t *) filp->private_data;
unsigned long flags;
#ifdef HX280ENC_DEBUG
dump_regs((unsigned long) dev); /* dump the regs */
#endif
PDEBUG("dev closed\n");
spin_lock_irqsave(&owner_lock, flags);
if (dev->is_reserved == 1 && dev->pid == current->pid) {
dev->pid = -1;
dev->is_reserved = 0;
dev->irq_received = 0;
dev->irq_status = 0;
PDEBUG("release reserved core\n");
}
spin_unlock_irqrestore(&owner_lock, flags);
wake_up_interruptible_all(&enc_hw_queue);
#ifndef VSI
pm_runtime_put_sync(hantro_h1_dev);
hantro_h1_clk_disable(hantro_h1_dev);
#endif
return 0;
}
static long hx280enc_ioctl32(struct file *filp, unsigned int cmd, unsigned long arg)
{
long err = 0;
#define HX280ENC_IOCTL32(err, filp, cmd, arg) { \
mm_segment_t old_fs = get_fs(); \
set_fs(KERNEL_DS); \
err = hx280enc_ioctl(filp, cmd, arg); \
if (err) \
return err; \
set_fs(old_fs); \
}
union {
unsigned long kux;
unsigned int kui;
} karg;
void __user *up = compat_ptr(arg);
switch (_IOC_NR(cmd)) {
case _IOC_NR(HX280ENC_IOCGHWOFFSET):
err = get_user(karg.kux, (s32 __user *)up);
if (err)
return err;
HX280ENC_IOCTL32(err, filp, cmd, (unsigned long)&karg);
err = put_user(((s32)karg.kux), (s32 __user *)up);
break;
case _IOC_NR(HX280ENC_IOCGHWIOSIZE):
err = get_user(karg.kui, (s32 __user *)up);
if (err)
return err;
HX280ENC_IOCTL32(err, filp, cmd, (unsigned long)&karg);
err = put_user(((s32)karg.kui), (s32 __user *)up);
break;
case _IOC_NR(HX280ENC_IOCH_ENC_RESERVE): {
int ret;
PDEBUG("Reserve ENC Cores\n");
ret = ReserveEncoder(&hx280enc_data);
return ret;
}
case _IOC_NR(HX280ENC_IOCH_ENC_RELEASE): {
PDEBUG("Release ENC Core\n");
ReleaseEncoder(&hx280enc_data);
break;
}
case _IOC_NR(HX280ENC_IOCG_CORE_WAIT): {
int ret;
ret = WaitEncReady(&hx280enc_data);
return ret;
}
}
return 0;
}
/* VFS methods */
static struct file_operations hx280enc_fops = {
.owner = THIS_MODULE,
.open = hx280enc_open,
.release = hx280enc_release,
.unlocked_ioctl = hx280enc_ioctl,
.fasync = NULL,
.mmap = hx280enc_mmap,
#ifdef CONFIG_COMPAT
.compat_ioctl = hx280enc_ioctl32,
#endif
};
#ifndef VSI
static int hx280enc_init(void)
#else
static int __init hx280enc_init(void)
#endif
{
int result;
PDEBUG(KERN_INFO "hx280enc: module init - base_port=0x%08lx irq=%i\n",
base_port, irq);
hx280enc_data.iobaseaddr = base_port;
hx280enc_data.iosize = ENC_IO_SIZE;
hx280enc_data.irq = irq;
hx280enc_data.async_queue = NULL;
hx280enc_data.hwregs = NULL;
result = register_chrdev(hx280enc_major, "hx280enc", &hx280enc_fops);
if (result < 0) {
PDEBUG(KERN_INFO "hx280enc: unable to get major <%d>\n", hx280enc_major);
return result;
} else if (result != 0) /* this is for dynamic major */
hx280enc_major = result;
result = ReserveIO();
if (result < 0)
goto err;
ResetAsic(&hx280enc_data); /* reset hardware */
/* get the IRQ line */
if (irq != -1) {
result = request_irq(irq, hx280enc_isr,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18))
SA_INTERRUPT | SA_SHIRQ,
#else
//IRQF_DISABLED | IRQF_SHARED,
IRQF_SHARED,
#endif
"hx280enc", (void *) &hx280enc_data);
if (result == -EINVAL) {
PDEBUG(KERN_ERR "hx280enc: Bad irq number or handler\n");
ReleaseIO();
goto err;
} else if (result == -EBUSY) {
PDEBUG(KERN_ERR "hx280enc: IRQ <%d> busy, change your config\n",
hx280enc_data.irq);
ReleaseIO();
goto err;
}
} else
PDEBUG(KERN_INFO "hx280enc: IRQ not in use!\n");
irq_set_status_flags(irq, IRQ_DISABLE_UNLAZY);
pr_info("hx280enc: module inserted. Major <%d>\n", hx280enc_major);
return 0;
err:
unregister_chrdev(hx280enc_major, "hx280enc");
PDEBUG(KERN_ERR "hx280enc: module not inserted\n");
return result;
}
#ifndef VSI
static void hx280enc_cleanup(void)
#else
static void __exit hx280enc_cleanup(void)
#endif
{
writel(0, hx280enc_data.hwregs + 0x38); /* disable HW */
writel(0, hx280enc_data.hwregs + 0x04); /* clear enc IRQ */
/* free the encoder IRQ */
if (hx280enc_data.irq != -1)
free_irq(hx280enc_data.irq, (void *) &hx280enc_data);
ReleaseIO();
unregister_chrdev(hx280enc_major, "hx280enc");
PDEBUG(KERN_INFO "hx280enc: module removed\n");
}
#ifdef VSI
module_init(hx280enc_init);
module_exit(hx280enc_cleanup);
#endif
static int ReserveIO(void)
{
long int hwid;
if (!request_mem_region(hx280enc_data.iobaseaddr, hx280enc_data.iosize, "hx280enc")) {
PDEBUG(KERN_INFO "hx280enc: failed to reserve HW regs\n");
return -EBUSY;
}
hx280enc_data.hwregs = (volatile u8 *) ioremap_nocache(hx280enc_data.iobaseaddr, hx280enc_data.iosize);
if (hx280enc_data.hwregs == NULL) {
PDEBUG(KERN_INFO "hx280enc: failed to ioremap HW regs\n");
ReleaseIO();
return -EBUSY;
}
hwid = readl(hx280enc_data.hwregs);
/* check for encoder HW ID */
if ((((hwid >> 16) & 0xFFFF) != ((ENC_HW_ID1 >> 16) & 0xFFFF)) &&
(((hwid >> 16) & 0xFFFF) != ((ENC_HW_ID2 >> 16) & 0xFFFF)) &&
(((hwid >> 16) & 0xFFFF) != ((ENC_HW_ID3 >> 16) & 0xFFFF)) &&
(((hwid >> 16) & 0xFFFF) != ((ENC_HW_ID4 >> 16) & 0xFFFF)) &&
(((hwid >> 16) & 0xFFFF) != ((ENC_HW_ID5 >> 16) & 0xFFFF))) {
PDEBUG(KERN_ERR "hx280enc: HW not found at 0x%08lx\n", hx280enc_data.iobaseaddr);
#ifdef HX280ENC_DEBUG
dump_regs((unsigned long) &hx280enc_data);
#endif
ReleaseIO();
return -EBUSY;
}
hx280enc_data.hw_id = hwid;
hx280enc_data.is_valid = 1;
PDEBUG(KERN_INFO "hx280enc: HW at base <0x%08lx> with ID <0x%08lx>\n", hx280enc_data.iobaseaddr, hwid);
return 0;
}
static void ReleaseIO(void)
{
if (hx280enc_data.is_valid == 0)
return;
if (hx280enc_data.hwregs)
iounmap((void *) hx280enc_data.hwregs);
release_mem_region(hx280enc_data.iobaseaddr, hx280enc_data.iosize);
}
irqreturn_t hx280enc_isr(int irq, void *dev_id)
{
hx280enc_t *dev = (hx280enc_t *) dev_id;
u32 irq_status;
unsigned long flags;
u32 is_write1_clr;
spin_lock_irqsave(&owner_lock, flags);
if (!dev->is_reserved) {
spin_unlock_irqrestore(&owner_lock, flags);
return IRQ_HANDLED;
}
spin_unlock_irqrestore(&owner_lock, flags);
irq_status = readl(dev->hwregs + 0x04);
/* BASE_HWFuse2 = 0x4a0; HWCFGIrqClearSupport = 0x00800000 */
is_write1_clr = (readl(dev->hwregs + 0x4a0) & 0x00800000);
if (irq_status & 0x01) {
/* clear enc IRQ and slice ready interrupt bit */
if (is_write1_clr)
writel(irq_status & (0x101), dev->hwregs + 0x04);
else
writel(irq_status & (~0x101), dev->hwregs + 0x04);
/* Handle slice ready interrupts. The reference implementation
* doesn't signal slice ready interrupts to EWL.
* The EWL will poll the slices ready register value. */
if ((irq_status & 0x1FE) == 0x100) {
PDEBUG("Slice ready IRQ handled!\n");
return IRQ_HANDLED;
}
spin_lock_irqsave(&owner_lock, flags);
dev->irq_received = 1;
dev->irq_status = irq_status & (~0x01);
spin_unlock_irqrestore(&owner_lock, flags);
wake_up_interruptible_all(&enc_wait_queue);
PDEBUG("IRQ handled!\n");
return IRQ_HANDLED;
} else {
PDEBUG("IRQ received, but NOT handled!\n");
return IRQ_NONE;
}
}
static void ResetAsic(hx280enc_t *dev)
{
int i;
if (dev->is_valid == 0)
return;
writel(0, dev->hwregs + 0x38);
for (i = 4; i < dev->iosize; i += 4)
writel(0, dev->hwregs + i);
}
#ifdef HX280ENC_DEBUG
static void dump_regs(unsigned long data)
{
hx280enc_t *dev = (hx280enc_t *) data;
int i;
PDEBUG("Reg Dump Start\n");
for (i = 0; i < dev->iosize; i += 4)
PDEBUG("\toffset %02X = %08X\n", i, readl(dev->hwregs + i));
PDEBUG("Reg Dump End\n");
}
#endif
#ifndef VSI
static int hantro_h1_probe(struct platform_device *pdev)
{
int err = 0;
struct device *temp_class;
struct resource *res;
unsigned long reg_base;
hantro_h1_dev = &pdev->dev;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs_hantro_h1");
if (!res) {
pr_err("hantro h1: unable to get vpu base addr\n");
return -ENODEV;
}
reg_base = res->start;
if ((ulong)reg_base != base_port) {
pr_err("hantro h1: regbase(0x%lX) not equal to expected value(0x%lX)\n", reg_base, base_port);
return -ENODEV;
}
irq = platform_get_irq_byname(pdev, "irq_hantro_h1");
if (irq < 0) {
pr_err("hantro h1: not find valid irq\n");
return -ENODEV;
}
hantro_clk_h1 = clk_get(&pdev->dev, "clk_hantro_h1");
if (IS_ERR(hantro_clk_h1)) {
pr_err("hantro h1: get clock failed, %p\n", hantro_clk_h1);
err = -ENXIO;
goto error;
}
hantro_clk_h1_bus = clk_get(&pdev->dev, "clk_hantro_h1_bus");
if (IS_ERR(hantro_clk_h1_bus)) {
pr_err("hantro h1: get bus clock failed, %p\n", hantro_clk_h1_bus);
err = -ENXIO;
goto error;
}
PDEBUG("hantro: h1 clock: 0x%lX, 0x%lX\n", clk_get_rate(hantro_clk_h1), clk_get_rate(hantro_clk_h1_bus));
hantro_h1_clk_enable(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
hantro_h1_ctrlblk_reset(&pdev->dev);
err = hx280enc_init();
if (0 != err) {
pr_err("hantro h1: init failed\n");
goto error;
}
hantro_h1_class = class_create(THIS_MODULE, "mxc_hantro_h1");
if (IS_ERR(hantro_h1_class)) {
err = PTR_ERR(hantro_h1_class);
goto error;
}
temp_class = device_create(hantro_h1_class, NULL, MKDEV(hx280enc_major, 0), NULL, DEVICE_NAME);
if (IS_ERR(temp_class)) {
err = PTR_ERR(temp_class);
goto err_out_class;
}
hx280enc_data.dev = &pdev->dev;
platform_set_drvdata(pdev, &hx280enc_data);
mutex_init(&hx280enc_data.dev_mutex);
goto out;
err_out_class:
device_destroy(hantro_h1_class, MKDEV(hx280enc_major, 0));
class_destroy(hantro_h1_class);
error:
pr_err("hantro probe failed\n");
out:
pm_runtime_put_sync(&pdev->dev);
hantro_h1_clk_disable(&pdev->dev);
return err;
}
static int hantro_h1_dev_remove(struct platform_device *pdev)
{
hantro_h1_clk_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
if (hx280enc_major > 0) {
device_destroy(hantro_h1_class, MKDEV(hx280enc_major, 0));
class_destroy(hantro_h1_class);
hx280enc_cleanup();
hx280enc_major = 0;
}
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
hantro_h1_clk_disable(&pdev->dev);
return 0;
}
#ifdef CONFIG_PM
static int hantro_h1_suspend(struct device *dev)
{
pm_runtime_put_sync_suspend(dev); //power off
return 0;
}
static int hantro_h1_resume(struct device *dev)
{
hx280enc_t *hx280enc = dev_get_drvdata(dev);
hantro_h1_power_on_disirq(hx280enc);
hantro_h1_ctrlblk_reset(dev);
return 0;
}
static int hantro_h1_runtime_suspend(struct device *dev)
{
//release_bus_freq(BUS_FREQ_HIGH);
return 0;
}
static int hantro_h1_runtime_resume(struct device *dev)
{
//request_bus_freq(BUS_FREQ_HIGH);
hantro_h1_ctrlblk_reset(dev);
return 0;
}
static const struct dev_pm_ops hantro_h1_pm_ops = {
SET_RUNTIME_PM_OPS(hantro_h1_runtime_suspend, hantro_h1_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(hantro_h1_suspend, hantro_h1_resume)
};
#endif //CONFIG_PM
static const struct of_device_id hantro_h1_of_match[] = {
{ .compatible = "nxp,imx8mm-hantro-h1", },
{/* sentinel */}
};
MODULE_DEVICE_TABLE(of, hantro_h1_of_match);
static struct platform_driver mxchantro_h1_driver = {
.driver = {
.name = "mxc_hantro_h1",
.of_match_table = hantro_h1_of_match,
#ifdef CONFIG_PM
.pm = &hantro_h1_pm_ops,
#endif
},
.probe = hantro_h1_probe,
.remove = hantro_h1_dev_remove,
};
static int __init hantro_h1_init(void)
{
int ret = platform_driver_register(&mxchantro_h1_driver);
return ret;
}
static void __exit hantro_h1_exit(void)
{
//clk_put(hantro_clk);
platform_driver_unregister(&mxchantro_h1_driver);
}
module_init(hantro_h1_init);
module_exit(hantro_h1_exit);
#endif