| /***************************************************************************** |
| * The GPL License (GPL) |
| * |
| * Copyright (c) 2015-2018, VeriSilicon Inc. |
| * Copyright (c) 2011-2014, Google Inc. |
| * |
| * 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 may obtain a copy of the GNU General Public License |
| * Version 2 or later at the following locations: |
| * http://www.opensource.org/licenses/gpl-license.html |
| * http://www.gnu.org/copyleft/gpl.html |
| *****************************************************************************/ |
| #include <linux/hantrodec.h> |
| #include "dwl_defs.h" |
| #include <linux/io.h> |
| #include <linux/uaccess.h> |
| #include <linux/errno.h> |
| #include <linux/fs.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/ioport.h> |
| #include <linux/kernel.h> |
| #include <linux/mm.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/pci.h> |
| #include <linux/sched.h> |
| #include <linux/semaphore.h> |
| #include <linux/spinlock.h> |
| #include <linux/slab.h> |
| #include <linux/version.h> |
| #include <linux/wait.h> |
| #include <linux/timer.h> |
| #include <linux/clk.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/clk.h> |
| #include <linux/busfreq-imx.h> |
| |
| #ifdef CONFIG_DEVICE_THERMAL |
| #include <linux/device_cooling.h> |
| #define HANTRO_REG_THERMAL_NOTIFIER(a) register_devfreq_cooling_notifier(a) |
| #define HANTRO_UNREG_THERMAL_NOTIFIER(a) unregister_devfreq_cooling_notifier(a) |
| DEFINE_SPINLOCK(thermal_lock); |
| /*1:hot, 0: not hot*/ |
| static int thermal_event; |
| static int thermal_cur; |
| static int hantro_clock_ratio = 2; |
| static int hantro_dynamic_clock; |
| module_param(hantro_clock_ratio, int, 0644); |
| module_param(hantro_dynamic_clock, int, 0644); |
| MODULE_PARM_DESC(hantro_clock_ratio, "clock ratio 1/N"); |
| MODULE_PARM_DESC(hantro_dynamic_clock, "enable or disable dynamic clock rate"); |
| #endif |
| |
| /*hantro G1 regs config including dec and pp*/ |
| #define HANTRO_DEC_ORG_REGS 60 |
| #define HANTRO_PP_ORG_REGS 41 |
| |
| #define HANTRO_DEC_EXT_REGS 27 |
| #define HANTRO_PP_EXT_REGS 9 |
| |
| #define HANTRO_G1_DEC_TOTAL_REGS (HANTRO_DEC_ORG_REGS + HANTRO_DEC_EXT_REGS) |
| #define HANTRO_PP_TOTAL_REGS (HANTRO_PP_ORG_REGS + HANTRO_PP_EXT_REGS) |
| #define HANTRO_G1_TOTAL_REGS 155 /*G1 total regs*/ |
| |
| #define HANTRO_DEC_ORG_FIRST_REG 0 |
| #define HANTRO_DEC_ORG_LAST_REG 59 |
| #define HANTRO_DEC_EXT_FIRST_REG 119 |
| #define HANTRO_DEC_EXT_LAST_REG 145 |
| |
| #define HANTRO_PP_ORG_FIRST_REG 60 |
| #define HANTRO_PP_ORG_LAST_REG 100 |
| #define HANTRO_PP_EXT_FIRST_REG 146 |
| #define HANTRO_PP_EXT_LAST_REG 154 |
| |
| /*hantro G2 reg config*/ |
| #define HANTRO_G2_DEC_REGS 265 /*G2 total regs*/ |
| |
| #define HANTRO_G2_DEC_FIRST_REG 0 |
| #define HANTRO_G2_DEC_LAST_REG (HANTRO_G2_DEC_REGS - 1) |
| |
| #define MAX(a, b) (((a) > (b)) ? (a) : (b)) |
| |
| #define DEC_IO_SIZE_MAX (MAX(HANTRO_G2_DEC_REGS, HANTRO_G1_TOTAL_REGS) * 4) |
| |
| /******************************************************************** |
| * PORTING SEGMENT |
| * NOTES: customer should modify these configuration if do porting to own platform. |
| * Please guarantee the base_addr, io_size,dec_irq belong to same core. |
| ********************************************************************/ |
| |
| #define HXDEC_MAX_CORES 2 |
| |
| /* Logic module base address */ |
| #define SOCLE_LOGIC_0_BASE 0x38300000 |
| #define SOCLE_LOGIC_1_BASE 0x38310000 |
| #define BLK_CTL_BASE 0x38320000 |
| |
| #define VEXPRESS_LOGIC_0_BASE 0xFC010000 |
| #define VEXPRESS_LOGIC_1_BASE 0xFC020000 |
| |
| #define DEC_IO_SIZE_0 ((HANTRO_G2_DEC_REGS) * 4) /* bytes */ |
| #define DEC_IO_SIZE_1 ((HANTRO_G2_DEC_REGS) * 4) /* bytes */ |
| |
| #define HANTRO_G1_DEF_CLK (600000000) |
| #define HANTRO_G2_DEF_CLK (600000000) |
| #define HANTRO_BUS_DEF_CLK (800000000) |
| #define HANTRO_CLK_VOL_THR (600000000) |
| /***********************************************************************/ |
| |
| #define IS_G1(hw_id) ((hw_id == 0x6731) ? 1:0) |
| |
| static const int DecHwId[] = { |
| 0x8190, /* Legacy HW */ |
| 0x8170, |
| 0x9170, |
| 0x9190, |
| 0x6731, /* G1 */ |
| 0x6732 /* G2 */ |
| }; |
| |
| ulong multicorebase[HXDEC_MAX_CORES] = { |
| SOCLE_LOGIC_0_BASE, |
| SOCLE_LOGIC_1_BASE |
| }; |
| |
| |
| static struct class *hantro_class; |
| #define DEVICE_NAME "mxc_hantro" |
| |
| static struct device *hantro_dev; |
| static struct clk *hantro_clk_g1; |
| static struct clk *hantro_clk_g2; |
| static struct clk *hantro_clk_bus; |
| static struct regulator *hantro_regulator; |
| |
| static int hantro_dbg = -1; |
| module_param(hantro_dbg, int, 0644); |
| MODULE_PARM_DESC(hantro_dbg, "Debug level (0-1)"); |
| #undef PDEBUG |
| #define PDEBUG(fmt, arg...) \ |
| do { \ |
| if (hantro_dbg > 0) { \ |
| dev_info(hantro_dev, fmt, ## arg); \ |
| } \ |
| } while (0) |
| |
| |
| static int hantrodec_major; /* dynamic allocation */ |
| |
| /* here's all the must remember stuff */ |
| typedef struct { |
| char *buffer; |
| unsigned int iosize[HXDEC_MAX_CORES]; |
| volatile u8 *hwregs[HXDEC_MAX_CORES]; |
| int irq[HXDEC_MAX_CORES]; |
| int hw_id[HXDEC_MAX_CORES]; |
| int cores; |
| struct fasync_struct *async_queue_dec; |
| struct fasync_struct *async_queue_pp; |
| } hantrodec_t; |
| |
| static hantrodec_t hantrodec_data; /* dynamic allocation? */ |
| |
| static int ReserveIO(void); |
| static void ReleaseIO(void); |
| |
| static void ResetAsic(hantrodec_t *dev); |
| |
| #ifdef HANTRODEC_DEBUG |
| static void dump_regs(hantrodec_t *dev); |
| #endif |
| |
| /* IRQ handler */ |
| static irqreturn_t hantrodec_isr(int irq, void *dev_id); |
| |
| static u32 dec_regs[HXDEC_MAX_CORES][DEC_IO_SIZE_MAX/4]; |
| struct semaphore dec_core_sem; |
| struct semaphore pp_core_sem; |
| |
| static int dec_irq; |
| static int pp_irq; |
| |
| atomic_t irq_rx = ATOMIC_INIT(0); |
| atomic_t irq_tx = ATOMIC_INIT(0); |
| |
| static struct file *dec_owner[HXDEC_MAX_CORES]; |
| static struct file *pp_owner[HXDEC_MAX_CORES]; |
| |
| /* spinlock_t owner_lock = SPIN_LOCK_UNLOCKED; */ |
| DEFINE_SPINLOCK(owner_lock); |
| |
| DECLARE_WAIT_QUEUE_HEAD(dec_wait_queue); |
| DECLARE_WAIT_QUEUE_HEAD(pp_wait_queue); |
| |
| DECLARE_WAIT_QUEUE_HEAD(hw_queue); |
| |
| #define DWL_CLIENT_TYPE_H264_DEC 1U |
| #define DWL_CLIENT_TYPE_MPEG4_DEC 2U |
| #define DWL_CLIENT_TYPE_JPEG_DEC 3U |
| #define DWL_CLIENT_TYPE_PP 4U |
| #define DWL_CLIENT_TYPE_VC1_DEC 5U |
| #define DWL_CLIENT_TYPE_MPEG2_DEC 6U |
| #define DWL_CLIENT_TYPE_VP6_DEC 7U |
| #define DWL_CLIENT_TYPE_AVS_DEC 8U |
| #define DWL_CLIENT_TYPE_RV_DEC 9U |
| #define DWL_CLIENT_TYPE_VP8_DEC 10U |
| #define DWL_CLIENT_TYPE_VP9_DEC 11U |
| #define DWL_CLIENT_TYPE_HEVC_DEC 12U |
| |
| static u32 cfg[HXDEC_MAX_CORES]; |
| static u32 timeout; |
| |
| static int hantro_update_voltage(struct device *dev) |
| { |
| unsigned long new_vol, old_vol; |
| int ret; |
| unsigned long clk1, clk2; |
| |
| clk1 = clk_get_rate(hantro_clk_g1); |
| clk2 = clk_get_rate(hantro_clk_g2); |
| |
| if (!clk1 || !clk2) |
| return -1; |
| |
| old_vol = regulator_get_voltage(hantro_regulator); |
| if ((clk1 >= HANTRO_CLK_VOL_THR) || (clk2 >= HANTRO_CLK_VOL_THR)) |
| new_vol = 1000000; // 1.0v |
| else |
| new_vol = 900000; // 0.9v |
| |
| if (old_vol != new_vol) { |
| ret = regulator_set_voltage_tol(hantro_regulator, new_vol, 0); |
| if (ret) |
| pr_err("failed to set hantro voltage: %ld mV\n", new_vol/1000); |
| else |
| pr_info("update hantro voltage from %ld mV to %ld mV\n", old_vol/1000, new_vol/1000); |
| } |
| return 0; |
| } |
| |
| static int hantro_clk_enable(struct device *dev) |
| { |
| clk_prepare(hantro_clk_g1); |
| clk_enable(hantro_clk_g1); |
| clk_prepare(hantro_clk_g2); |
| clk_enable(hantro_clk_g2); |
| clk_prepare(hantro_clk_bus); |
| clk_enable(hantro_clk_bus); |
| return 0; |
| } |
| |
| static int hantro_clk_disable(struct device *dev) |
| { |
| if (hantro_clk_g1) { |
| clk_disable(hantro_clk_g1); |
| clk_unprepare(hantro_clk_g1); |
| } |
| if (hantro_clk_g2) { |
| clk_disable(hantro_clk_g2); |
| clk_unprepare(hantro_clk_g2); |
| } |
| if (hantro_clk_bus) { |
| clk_disable(hantro_clk_bus); |
| clk_unprepare(hantro_clk_bus); |
| } |
| return 0; |
| } |
| |
| static int hantro_ctrlblk_reset(struct device *dev) |
| { |
| volatile u8 *iobase; |
| |
| //config G1/G2 |
| hantro_clk_enable(dev); |
| iobase = (volatile u8 *)ioremap_nocache(BLK_CTL_BASE, 0x10000); |
| iowrite32(0x3, iobase); //VPUMIX G1/G2 block soft reset control |
| iowrite32(0x3, iobase+4); //VPUMIX G1/G2 block clock enable control |
| iowrite32(0xFFFFFFFF, iobase + 0x8); // all G1 fuse dec enable |
| iowrite32(0xFFFFFFFF, iobase + 0xC); // all G1 fuse pp enable |
| iowrite32(0xFFFFFFFF, iobase + 0x10); // all G2 fuse dec enable |
| iounmap(iobase); |
| hantro_clk_disable(dev); |
| return 0; |
| } |
| |
| #ifdef CONFIG_DEVICE_THERMAL |
| static int hantro_thermal_check(struct device *dev) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&thermal_lock, flags); |
| if (thermal_event == thermal_cur) { |
| /*nothing to do and return directly*/ |
| spin_unlock_irqrestore(&thermal_lock, flags); |
| return 0; |
| } |
| thermal_cur = thermal_event; |
| spin_unlock_irqrestore(&thermal_lock, flags); |
| |
| if (thermal_cur) { |
| int ratio = hantro_clock_ratio; |
| |
| pr_debug("hantro: too hot, need to decrease clock, ratio: 1/%d\n", ratio); |
| /*clock disable/enable are not required for vpu clock rate operation*/ |
| clk_set_rate(hantro_clk_g1, HANTRO_G1_DEF_CLK/ratio); |
| clk_set_rate(hantro_clk_g2, HANTRO_G2_DEF_CLK/ratio); |
| clk_set_rate(hantro_clk_bus, HANTRO_BUS_DEF_CLK/ratio); |
| } else { |
| pr_debug("hantro: not hot again, will restore default clock\n"); |
| clk_set_rate(hantro_clk_g1, HANTRO_G1_DEF_CLK); |
| clk_set_rate(hantro_clk_g2, HANTRO_G2_DEF_CLK); |
| clk_set_rate(hantro_clk_bus, HANTRO_BUS_DEF_CLK); |
| } |
| pr_info("hantro: event(%d), g1, g2, bus clock: %ld, %ld, %ld\n", thermal_cur, |
| clk_get_rate(hantro_clk_g1), clk_get_rate(hantro_clk_g2), clk_get_rate(hantro_clk_bus)); |
| hantro_update_voltage(dev); |
| return 0; |
| } |
| |
| static int hantro_thermal_hot_notify(struct notifier_block *nb, unsigned long event, void *dummy) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&thermal_lock, flags); |
| thermal_event = event; /*event: 1: hot, 0: cool*/ |
| spin_unlock_irqrestore(&thermal_lock, flags); |
| if (hantro_dynamic_clock) |
| pr_info("hantro receive hot notification event: %ld\n", event); |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block hantro_thermal_hot_notifier = { |
| .notifier_call = hantro_thermal_hot_notify, |
| }; |
| #endif //CONFIG_DEVICE_THERMAL |
| |
| static void ReadCoreConfig(hantrodec_t *dev) |
| { |
| int c; |
| u32 reg, tmp, mask; |
| |
| memset(cfg, 0, sizeof(cfg)); |
| |
| for (c = 0; c < dev->cores; c++) { |
| /* Decoder configuration */ |
| if (IS_G1(dev->hw_id[c])) { |
| reg = ioread32(dev->hwregs[c] + HANTRODEC_SYNTH_CFG * 4); |
| |
| tmp = (reg >> DWL_H264_E) & 0x3U; |
| if (tmp) |
| pr_debug("hantrodec: Core[%d] has H264\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_H264_DEC : 0; |
| |
| tmp = (reg >> DWL_JPEG_E) & 0x01U; |
| if (tmp) |
| pr_debug("hantrodec: Core[%d] has JPEG\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_JPEG_DEC : 0; |
| |
| tmp = (reg >> DWL_MPEG4_E) & 0x3U; |
| if (tmp) |
| pr_debug("hantrodec: Core[%d] has MPEG4\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_MPEG4_DEC : 0; |
| |
| tmp = (reg >> DWL_VC1_E) & 0x3U; |
| if (tmp) |
| pr_debug("hantrodec: Core[%d] has VC1\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_VC1_DEC : 0; |
| |
| tmp = (reg >> DWL_MPEG2_E) & 0x01U; |
| if (tmp) |
| pr_debug("hantrodec: Core[%d] has MPEG2\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_MPEG2_DEC : 0; |
| |
| tmp = (reg >> DWL_VP6_E) & 0x01U; |
| if (tmp) |
| pr_debug("hantrodec: Core[%d] has VP6\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_VP6_DEC : 0; |
| |
| reg = ioread32(dev->hwregs[c] + HANTRODEC_SYNTH_CFG_2 * 4); |
| |
| /* VP7 and WEBP is part of VP8 */ |
| mask = (1 << DWL_VP8_E) | (1 << DWL_VP7_E) | (1 << DWL_WEBP_E); |
| tmp = (reg & mask); |
| if (tmp & (1 << DWL_VP8_E)) |
| pr_debug("hantrodec: Core[%d] has VP8\n", c); |
| if (tmp & (1 << DWL_VP7_E)) |
| pr_debug("hantrodec: Core[%d] has VP7\n", c); |
| if (tmp & (1 << DWL_WEBP_E)) |
| pr_debug("hantrodec: Core[%d] has WebP\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_VP8_DEC : 0; |
| |
| tmp = (reg >> DWL_AVS_E) & 0x01U; |
| if (tmp) |
| pr_debug("hantrodec: Core[%d] has AVS\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_AVS_DEC : 0; |
| |
| tmp = (reg >> DWL_RV_E) & 0x03U; |
| if (tmp) |
| pr_debug("hantrodec: Core[%d] has RV\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_RV_DEC : 0; |
| |
| /* Post-processor configuration */ |
| //reg = ioread32(dev->hwregs[c] + HANTROPP_SYNTH_CFG * 4); |
| } else { |
| reg = ioread32(dev->hwregs[c] + HANTRODEC_SYNTH_CFG_2 * 4); |
| |
| tmp = (reg >> DWL_HEVC_E) & 0x3U; |
| if (tmp) |
| pr_debug("hantrodec: Core[%d] has HEVC\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_HEVC_DEC : 0; |
| |
| tmp = (reg >> DWL_VP9_E) & 0x03U; |
| if (tmp) |
| pr_debug("hantrodec: Core[%d] has VP9\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_VP9_DEC : 0; |
| } |
| |
| /* Post-processor configuration */ |
| reg = ioread32(dev->hwregs[c] + HANTRODECPP_SYNTH_CFG * 4); |
| |
| tmp = (reg >> DWL_PP_E) & 0x01U; |
| if (tmp) |
| pr_debug("hantrodec: Core[%d] has PP\n", c); |
| cfg[c] |= tmp ? 1 << DWL_CLIENT_TYPE_PP : 0; |
| } |
| } |
| |
| static int CoreHasFormat(const u32 *cfg, int Core, u32 format) |
| { |
| return (cfg[Core] & (1 << format)) ? 1 : 0; |
| } |
| |
| int GetDecCore(long Core, hantrodec_t *dev, struct file *filp) |
| { |
| int success = 0; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&owner_lock, flags); |
| if (dec_owner[Core] == NULL) { |
| dec_owner[Core] = filp; |
| success = 1; |
| } |
| |
| spin_unlock_irqrestore(&owner_lock, flags); |
| |
| return success; |
| } |
| |
| int GetDecCoreAny(long *Core, hantrodec_t *dev, struct file *filp, |
| unsigned long format) |
| { |
| int success = 0; |
| long c; |
| |
| *Core = -1; |
| |
| for (c = 0; c < dev->cores; c++) { |
| /* a free Core that has format */ |
| if (CoreHasFormat(cfg, c, format) && GetDecCore(c, dev, filp)) { |
| success = 1; |
| *Core = c; |
| break; |
| } |
| } |
| |
| return success; |
| } |
| int GetDecCoreID(hantrodec_t *dev, struct file *filp, |
| unsigned long format) |
| { |
| long c; |
| |
| int core_id = -1; |
| |
| for (c = 0; c < dev->cores; c++) { |
| /* a Core that has format */ |
| if (CoreHasFormat(cfg, c, format)) { |
| core_id = c; |
| break; |
| } |
| } |
| PDEBUG("GetDecCoreID=%d\n", core_id); |
| return core_id; |
| } |
| |
| static int hantrodec_choose_core(int is_g1) |
| { |
| volatile unsigned char *reg = NULL; |
| unsigned int blk_base = BLK_CTL_BASE; |
| |
| PDEBUG("hantrodec_choose_core\n"); |
| if (!request_mem_region(blk_base, 0x1000, "blk_ctl")) { |
| pr_err("blk_ctl: failed to reserve HW regs\n"); |
| return -EBUSY; |
| } |
| |
| reg = (volatile u8 *) ioremap_nocache(blk_base, 0x1000); |
| |
| if (reg == NULL) { |
| pr_err("blk_ctl: failed to ioremap HW regs\n"); |
| release_mem_region(blk_base, 0x1000); |
| return -EBUSY; |
| } |
| |
| // G1 use, set to 1; G2 use, set to 0, choose the one you are using |
| if (is_g1) |
| iowrite32(0x1, reg + 0x14); // VPUMIX only use G1 |
| else |
| iowrite32(0x0, reg + 0x14); // VPUMIX only use G2 |
| |
| if (reg) |
| iounmap((void *)reg); |
| release_mem_region(blk_base, 0x1000); |
| PDEBUG("hantrodec_choose_core OK!\n"); |
| return 0; |
| } |
| |
| |
| long ReserveDecoder(hantrodec_t *dev, struct file *filp, unsigned long format) |
| { |
| long Core = -1; |
| |
| /* reserve a Core */ |
| if (down_interruptible(&dec_core_sem)) |
| return -ERESTARTSYS; |
| |
| /* lock a Core that has specific format*/ |
| if (wait_event_interruptible(hw_queue, GetDecCoreAny(&Core, dev, filp, format) != 0)) |
| return -ERESTARTSYS; |
| |
| if (IS_G1(dev->hw_id[Core])) { |
| if (0 == hantrodec_choose_core(1)) |
| PDEBUG("G1 is reserved\n"); |
| else |
| return -1; |
| } else { |
| if (0 == hantrodec_choose_core(0)) |
| PDEBUG("G2 is reserved\n"); |
| else |
| return -1; |
| } |
| |
| #ifdef CONFIG_DEVICE_THERMAL |
| if (hantro_dynamic_clock) |
| hantro_thermal_check(hantro_dev); |
| #endif |
| |
| return Core; |
| } |
| |
| void ReleaseDecoder(hantrodec_t *dev, long Core) |
| { |
| u32 status; |
| unsigned long flags; |
| |
| status = ioread32(dev->hwregs[Core] + HANTRODEC_IRQ_STAT_DEC_OFF); |
| |
| /* make sure HW is disabled */ |
| if (status & HANTRODEC_DEC_E) { |
| pr_info("hantrodec: DEC[%li] still enabled -> reset\n", Core); |
| |
| /* abort decoder */ |
| status |= HANTRODEC_DEC_ABORT | HANTRODEC_DEC_IRQ_DISABLE; |
| iowrite32(status, dev->hwregs[Core] + HANTRODEC_IRQ_STAT_DEC_OFF); |
| } |
| |
| spin_lock_irqsave(&owner_lock, flags); |
| |
| dec_owner[Core] = NULL; |
| |
| spin_unlock_irqrestore(&owner_lock, flags); |
| |
| up(&dec_core_sem); |
| |
| wake_up_interruptible_all(&hw_queue); |
| |
| } |
| |
| long ReservePostProcessor(hantrodec_t *dev, struct file *filp) |
| { |
| unsigned long flags; |
| |
| long Core = 0; |
| |
| /* single Core PP only */ |
| if (down_interruptible(&pp_core_sem)) |
| return -ERESTARTSYS; |
| |
| spin_lock_irqsave(&owner_lock, flags); |
| |
| pp_owner[Core] = filp; |
| |
| spin_unlock_irqrestore(&owner_lock, flags); |
| |
| return Core; |
| } |
| |
| void ReleasePostProcessor(hantrodec_t *dev, long Core) |
| { |
| unsigned long flags; |
| |
| u32 status = ioread32(dev->hwregs[Core] + HANTRO_IRQ_STAT_PP_OFF); |
| |
| /* make sure HW is disabled */ |
| if (status & HANTRO_PP_E) { |
| pr_info("hantrodec: PP[%li] still enabled -> reset\n", Core); |
| |
| /* disable IRQ */ |
| status |= HANTRO_PP_IRQ_DISABLE; |
| |
| /* disable postprocessor */ |
| status &= (~HANTRO_PP_E); |
| iowrite32(0x10, dev->hwregs[Core] + HANTRO_IRQ_STAT_PP_OFF); |
| } |
| |
| spin_lock_irqsave(&owner_lock, flags); |
| |
| pp_owner[Core] = NULL; |
| |
| spin_unlock_irqrestore(&owner_lock, flags); |
| |
| up(&pp_core_sem); |
| } |
| |
| long ReserveDecPp(hantrodec_t *dev, struct file *filp, unsigned long format) |
| { |
| /* reserve Core 0, DEC+PP for pipeline */ |
| unsigned long flags; |
| |
| long Core = 0; |
| |
| /* check that Core has the requested dec format */ |
| if (!CoreHasFormat(cfg, Core, format)) |
| return -EFAULT; |
| |
| /* check that Core has PP */ |
| if (!CoreHasFormat(cfg, Core, DWL_CLIENT_TYPE_PP)) |
| return -EFAULT; |
| |
| /* reserve a Core */ |
| if (down_interruptible(&dec_core_sem)) |
| return -ERESTARTSYS; |
| |
| /* wait until the Core is available */ |
| if (wait_event_interruptible(hw_queue, GetDecCore(Core, dev, filp) != 0)) { |
| up(&dec_core_sem); |
| return -ERESTARTSYS; |
| } |
| |
| if (down_interruptible(&pp_core_sem)) { |
| ReleaseDecoder(dev, Core); |
| return -ERESTARTSYS; |
| } |
| |
| spin_lock_irqsave(&owner_lock, flags); |
| pp_owner[Core] = filp; |
| spin_unlock_irqrestore(&owner_lock, flags); |
| |
| return Core; |
| } |
| |
| long DecFlushRegs(hantrodec_t *dev, struct core_desc *Core) |
| { |
| long ret = 0, i; |
| |
| u32 id = Core->id; |
| |
| if (IS_G1(dev->hw_id[id])) { |
| /* copy original dec regs to kernal space*/ |
| ret = copy_from_user(dec_regs[id], Core->regs, HANTRO_DEC_ORG_REGS*4); |
| if (ret) { |
| pr_err("copy_from_user failed, returned %li\n", ret); |
| return -EFAULT; |
| } |
| #ifdef USE_64BIT_ENV |
| /* copy extended dec regs to kernal space*/ |
| ret = copy_from_user(dec_regs[id] + HANTRO_DEC_EXT_FIRST_REG, |
| Core->regs + HANTRO_DEC_EXT_FIRST_REG, HANTRO_DEC_EXT_REGS * 4); |
| #endif |
| if (ret) { |
| pr_err("copy_from_user failed, returned %li\n", ret); |
| return -EFAULT; |
| } |
| |
| /* write dec regs but the status reg[1] to hardware */ |
| /* both original and extended regs need to be written */ |
| for (i = 2; i <= HANTRO_DEC_ORG_LAST_REG; i++) |
| iowrite32(dec_regs[id][i], dev->hwregs[id] + i*4); |
| #ifdef USE_64BIT_ENV |
| for (i = HANTRO_DEC_EXT_FIRST_REG; i <= HANTRO_DEC_EXT_LAST_REG; i++) |
| iowrite32(dec_regs[id][i], dev->hwregs[id] + i*4); |
| #endif |
| } else { |
| ret = copy_from_user(dec_regs[id], Core->regs, HANTRO_G2_DEC_REGS*4); |
| if (ret) { |
| pr_err("copy_from_user failed, returned %li\n", ret); |
| return -EFAULT; |
| } |
| |
| /* write all regs but the status reg[1] to hardware */ |
| for (i = 2; i <= HANTRO_G2_DEC_LAST_REG; i++) |
| iowrite32(dec_regs[id][i], dev->hwregs[id] + i*4); |
| } |
| |
| /* write the status register, which may start the decoder */ |
| iowrite32(dec_regs[id][1], dev->hwregs[id] + 4); |
| |
| PDEBUG("flushed registers on Core %d\n", id); |
| |
| return 0; |
| } |
| |
| long DecRefreshRegs(hantrodec_t *dev, struct core_desc *Core) |
| { |
| long ret, i; |
| u32 id = Core->id; |
| |
| if (IS_G1(dev->hw_id[id])) { |
| /* user has to know exactly what they are asking for */ |
| //if(Core->size != (HANTRO_DEC_ORG_REGS * 4)) |
| // return -EFAULT; |
| |
| /* read all registers from hardware */ |
| /* both original and extended regs need to be read */ |
| for (i = 0; i <= HANTRO_DEC_ORG_LAST_REG; i++) |
| dec_regs[id][i] = ioread32(dev->hwregs[id] + i*4); |
| #ifdef USE_64BIT_ENV |
| for (i = HANTRO_DEC_EXT_FIRST_REG; i <= HANTRO_DEC_EXT_LAST_REG; i++) |
| dec_regs[id][i] = ioread32(dev->hwregs[id] + i*4); |
| #endif |
| |
| if (timeout) { |
| /* Enable TIMEOUT bits in Reg[1] */ |
| dec_regs[id][1] = 0x40100; |
| /* Reset HW */ |
| ResetAsic(dev); |
| timeout = 0; |
| } |
| |
| /* put registers to user space*/ |
| /* put original registers to user space*/ |
| ret = copy_to_user(Core->regs, dec_regs[id], HANTRO_DEC_ORG_REGS*4); |
| #ifdef USE_64BIT_ENV |
| /*put extended registers to user space*/ |
| ret = copy_to_user(Core->regs + HANTRO_DEC_EXT_FIRST_REG, |
| dec_regs[id] + HANTRO_DEC_EXT_FIRST_REG, HANTRO_DEC_EXT_REGS * 4); |
| #endif |
| if (ret) { |
| pr_err("copy_to_user failed, returned %li\n", ret); |
| return -EFAULT; |
| } |
| } else { |
| /* user has to know exactly what they are asking for */ |
| if (Core->size != (HANTRO_G2_DEC_REGS * 4)) |
| return -EFAULT; |
| |
| /* read all registers from hardware */ |
| for (i = 0; i <= HANTRO_G2_DEC_LAST_REG; i++) |
| dec_regs[id][i] = ioread32(dev->hwregs[id] + i*4); |
| |
| if (timeout) { |
| /* Enable TIMEOUT bits in Reg[1] */ |
| dec_regs[id][1] = 0x40100; |
| /* Reset HW */ |
| ResetAsic(dev); |
| timeout = 0; |
| } |
| |
| /* put registers to user space*/ |
| ret = copy_to_user(Core->regs, dec_regs[id], HANTRO_G2_DEC_REGS*4); |
| if (ret) { |
| pr_err("copy_to_user failed, returned %li\n", ret); |
| return -EFAULT; |
| } |
| } |
| return 0; |
| } |
| |
| static int CheckDecIrq(hantrodec_t *dev, int id) |
| { |
| unsigned long flags; |
| int rdy = 0; |
| |
| const u32 irq_mask = (1 << id); |
| |
| spin_lock_irqsave(&owner_lock, flags); |
| |
| if (dec_irq & irq_mask) { |
| /* reset the wait condition(s) */ |
| dec_irq &= ~irq_mask; |
| rdy = 1; |
| } |
| |
| spin_unlock_irqrestore(&owner_lock, flags); |
| |
| return rdy; |
| } |
| |
| long WaitDecReadyAndRefreshRegs(hantrodec_t *dev, struct core_desc *Core) |
| { |
| u32 id = Core->id; |
| long ret; |
| |
| PDEBUG("wait_event_interruptible DEC[%d]\n", id); |
| |
| //ret = wait_event_interruptible_timeout(dec_wait_queue, CheckDecIrq(dev, id), msecs_to_jiffies(200)); |
| ret = wait_event_timeout(dec_wait_queue, CheckDecIrq(dev, id), msecs_to_jiffies(200)); |
| if (ret == -ERESTARTSYS) { |
| pr_err("DEC[%d] failed to wait_event interrupted\n", id); |
| return -ERESTARTSYS; |
| } else if (ret == 0) { |
| pr_err("DEC[%d] wait_event timeout\n", id); |
| timeout = 1; |
| } |
| |
| atomic_inc(&irq_tx); |
| |
| /* refresh registers */ |
| return DecRefreshRegs(dev, Core); |
| } |
| |
| long PPFlushRegs(hantrodec_t *dev, struct core_desc *Core) |
| { |
| long ret = 0; |
| u32 id = Core->id; |
| u32 i; |
| |
| /* copy original dec regs to kernal space*/ |
| ret = copy_from_user(dec_regs[id] + HANTRO_PP_ORG_FIRST_REG, |
| Core->regs + HANTRO_PP_ORG_FIRST_REG, HANTRO_PP_ORG_REGS*4); |
| #ifdef USE_64BIT_ENV |
| /* copy extended dec regs to kernal space*/ |
| ret = copy_from_user(dec_regs[id] + HANTRO_PP_EXT_FIRST_REG, |
| Core->regs + HANTRO_PP_EXT_FIRST_REG, HANTRO_PP_EXT_REGS*4); |
| #endif |
| if (ret) { |
| pr_err("copy_from_user failed, returned %li\n", ret); |
| return -EFAULT; |
| } |
| |
| /* write all regs but the status reg[1] to hardware */ |
| /* both original and extended regs need to be written */ |
| for (i = HANTRO_PP_ORG_FIRST_REG + 1; i <= HANTRO_PP_ORG_LAST_REG; i++) |
| iowrite32(dec_regs[id][i], dev->hwregs[id] + i*4); |
| #ifdef USE_64BIT_ENV |
| for (i = HANTRO_PP_EXT_FIRST_REG; i <= HANTRO_PP_EXT_LAST_REG; i++) |
| iowrite32(dec_regs[id][i], dev->hwregs[id] + i*4); |
| #endif |
| /* write the stat reg, which may start the PP */ |
| iowrite32(dec_regs[id][HANTRO_PP_ORG_FIRST_REG], |
| dev->hwregs[id] + HANTRO_PP_ORG_FIRST_REG * 4); |
| |
| return 0; |
| } |
| |
| long PPRefreshRegs(hantrodec_t *dev, struct core_desc *Core) |
| { |
| long i, ret; |
| u32 id = Core->id; |
| #ifdef USE_64BIT_ENV |
| /* user has to know exactly what they are asking for */ |
| if (Core->size != (HANTRO_PP_TOTAL_REGS * 4)) |
| return -EFAULT; |
| #else |
| /* user has to know exactly what they are asking for */ |
| if (Core->size != (HANTRO_PP_ORG_REGS * 4)) |
| return -EFAULT; |
| #endif |
| |
| /* read all registers from hardware */ |
| /* both original and extended regs need to be read */ |
| for (i = HANTRO_PP_ORG_FIRST_REG; i <= HANTRO_PP_ORG_LAST_REG; i++) |
| dec_regs[id][i] = ioread32(dev->hwregs[id] + i*4); |
| #ifdef USE_64BIT_ENV |
| for (i = HANTRO_PP_EXT_FIRST_REG; i <= HANTRO_PP_EXT_LAST_REG; i++) |
| dec_regs[id][i] = ioread32(dev->hwregs[id] + i*4); |
| #endif |
| /* put registers to user space*/ |
| /* put original registers to user space*/ |
| ret = copy_to_user(Core->regs + HANTRO_PP_ORG_FIRST_REG, |
| dec_regs[id] + HANTRO_PP_ORG_FIRST_REG, HANTRO_PP_ORG_REGS*4); |
| #ifdef USE_64BIT_ENV |
| /* put extended registers to user space*/ |
| ret = copy_to_user(Core->regs + HANTRO_PP_EXT_FIRST_REG, |
| dec_regs[id] + HANTRO_PP_EXT_FIRST_REG, HANTRO_PP_EXT_REGS * 4); |
| #endif |
| if (ret) { |
| pr_err("copy_to_user failed, returned %li\n", ret); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| static int CheckPPIrq(hantrodec_t *dev, int id) |
| { |
| unsigned long flags; |
| int rdy = 0; |
| |
| const u32 irq_mask = (1 << id); |
| |
| spin_lock_irqsave(&owner_lock, flags); |
| |
| if (pp_irq & irq_mask) { |
| /* reset the wait condition(s) */ |
| pp_irq &= ~irq_mask; |
| rdy = 1; |
| } |
| |
| spin_unlock_irqrestore(&owner_lock, flags); |
| |
| return rdy; |
| } |
| |
| long WaitPPReadyAndRefreshRegs(hantrodec_t *dev, struct core_desc *Core) |
| { |
| u32 id = Core->id; |
| |
| PDEBUG("wait_event_interruptible PP[%d]\n", id); |
| |
| if (wait_event_interruptible(pp_wait_queue, CheckPPIrq(dev, id))) { |
| pr_err("PP[%d] failed to wait_event_interruptible interrupted\n", id); |
| return -ERESTARTSYS; |
| } |
| |
| atomic_inc(&irq_tx); |
| |
| /* refresh registers */ |
| return PPRefreshRegs(dev, Core); |
| } |
| |
| static int CheckCoreIrq(hantrodec_t *dev, const struct file *filp, int *id) |
| { |
| unsigned long flags; |
| int rdy = 0, n = 0; |
| |
| do { |
| u32 irq_mask = (1 << n); |
| |
| spin_lock_irqsave(&owner_lock, flags); |
| |
| if (dec_irq & irq_mask) { |
| if (dec_owner[n] == filp) { |
| /* we have an IRQ for our client */ |
| |
| /* reset the wait condition(s) */ |
| dec_irq &= ~irq_mask; |
| |
| /* signal ready Core no. for our client */ |
| *id = n; |
| |
| rdy = 1; |
| |
| spin_unlock_irqrestore(&owner_lock, flags); |
| break; |
| } else if (dec_owner[n] == NULL) { |
| /* zombie IRQ */ |
| pr_info("IRQ on Core[%d], but no owner!!!\n", n); |
| |
| /* reset the wait condition(s) */ |
| dec_irq &= ~irq_mask; |
| } |
| } |
| |
| spin_unlock_irqrestore(&owner_lock, flags); |
| |
| n++; /* next Core */ |
| } while (n < dev->cores); |
| |
| return rdy; |
| } |
| |
| long WaitCoreReady(hantrodec_t *dev, const struct file *filp, int *id) |
| { |
| PDEBUG("wait_event_interruptible CORE\n"); |
| |
| if (wait_event_interruptible(dec_wait_queue, CheckCoreIrq(dev, filp, id))) { |
| pr_err("CORE failed to wait_event_interruptible interrupted\n"); |
| return -ERESTARTSYS; |
| } |
| |
| atomic_inc(&irq_tx); |
| |
| return 0; |
| } |
| |
| /*------------------------------------------------------------------------- |
| *Function name : hantrodec_ioctl |
| *Description : communication method to/from the user space |
| * |
| *Return type : long |
| *------------------------------------------------------------------------- |
| */ |
| |
| static long hantrodec_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
| { |
| int err = 0; |
| long tmp; |
| |
| PDEBUG("ioctl cmd 0x%08x\n", cmd); |
| /* |
| * extract the type and number bitfields, and don't decode |
| * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() |
| */ |
| if (_IOC_TYPE(cmd) != HANTRODEC_IOC_MAGIC) |
| return -ENOTTY; |
| if (_IOC_NR(cmd) > HANTRODEC_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(HANTRODEC_IOC_CLI): { |
| __u32 id; |
| |
| __get_user(id, (__u32 *)arg); |
| if (id >= hantrodec_data.cores) |
| return -EFAULT; |
| disable_irq(hantrodec_data.irq[id]); |
| break; |
| } |
| case _IOC_NR(HANTRODEC_IOC_STI): { |
| __u32 id; |
| |
| __get_user(id, (__u32 *)arg); |
| if (id >= hantrodec_data.cores) |
| return -EFAULT; |
| enable_irq(hantrodec_data.irq[id]); |
| break; |
| } |
| case _IOC_NR(HANTRODEC_IOCGHWOFFSET): { |
| __u32 id; |
| |
| __get_user(id, (__u32 *)arg); |
| if (id >= hantrodec_data.cores) |
| return -EFAULT; |
| |
| __put_user(multicorebase[id], (unsigned long *) arg); |
| break; |
| } |
| case _IOC_NR(HANTRODEC_IOCGHWIOSIZE): { |
| __u32 id; |
| __u32 io_size; |
| |
| __get_user(id, (__u32 *)arg); |
| if (id >= hantrodec_data.cores) |
| return -EFAULT; |
| io_size = hantrodec_data.iosize[id]; |
| __put_user(io_size, (u32 *) arg); |
| |
| return 0; |
| } |
| case _IOC_NR(HANTRODEC_IOC_MC_OFFSETS): { |
| tmp = copy_to_user((u64 *) arg, multicorebase, sizeof(multicorebase)); |
| if (err) { |
| pr_err("copy_to_user failed, returned %li\n", tmp); |
| return -EFAULT; |
| } |
| break; |
| } |
| case _IOC_NR(HANTRODEC_IOC_MC_CORES): |
| __put_user(hantrodec_data.cores, (unsigned int *) arg); |
| PDEBUG("hantrodec_data.cores=%d\n", hantrodec_data.cores); |
| break; |
| case _IOC_NR(HANTRODEC_IOCS_DEC_PUSH_REG): { |
| struct core_desc Core; |
| |
| /* get registers from user space*/ |
| tmp = copy_from_user(&Core, (void *)arg, sizeof(struct core_desc)); |
| if (tmp) { |
| pr_err("copy_from_user failed, returned %li\n", tmp); |
| return -EFAULT; |
| } |
| |
| if (Core.id >= hantrodec_data.cores) |
| return -EFAULT; |
| |
| DecFlushRegs(&hantrodec_data, &Core); |
| break; |
| } |
| case _IOC_NR(HANTRODEC_IOCS_PP_PUSH_REG): { |
| struct core_desc Core; |
| |
| /* get registers from user space*/ |
| tmp = copy_from_user(&Core, (void *)arg, sizeof(struct core_desc)); |
| if (tmp) { |
| pr_err("copy_from_user failed, returned %li\n", tmp); |
| return -EFAULT; |
| } |
| |
| if (Core.id >= hantrodec_data.cores) |
| return -EFAULT; |
| |
| PPFlushRegs(&hantrodec_data, &Core); |
| break; |
| } |
| case _IOC_NR(HANTRODEC_IOCS_DEC_PULL_REG): { |
| struct core_desc Core; |
| |
| /* get registers from user space*/ |
| tmp = copy_from_user(&Core, (void *)arg, sizeof(struct core_desc)); |
| if (tmp) { |
| pr_err("copy_from_user failed, returned %li\n", tmp); |
| return -EFAULT; |
| } |
| |
| if (Core.id >= hantrodec_data.cores) |
| return -EFAULT; |
| |
| return DecRefreshRegs(&hantrodec_data, &Core); |
| } |
| case _IOC_NR(HANTRODEC_IOCS_PP_PULL_REG): { |
| struct core_desc Core; |
| |
| /* get registers from user space*/ |
| tmp = copy_from_user(&Core, (void *)arg, sizeof(struct core_desc)); |
| if (tmp) { |
| pr_err("copy_from_user failed, returned %li\n", tmp); |
| return -EFAULT; |
| } |
| |
| if (Core.id >= hantrodec_data.cores) |
| return -EFAULT; |
| |
| return PPRefreshRegs(&hantrodec_data, &Core); |
| } |
| case _IOC_NR(HANTRODEC_IOCH_DEC_RESERVE): { |
| PDEBUG("Reserve DEC Core, format = %li\n", arg); |
| return ReserveDecoder(&hantrodec_data, filp, arg); |
| } |
| case _IOC_NR(HANTRODEC_IOCT_DEC_RELEASE): { |
| if (arg >= hantrodec_data.cores || dec_owner[arg] != filp) { |
| pr_err("bogus DEC release, Core = %li\n", arg); |
| return -EFAULT; |
| } |
| |
| PDEBUG("Release DEC, Core = %li\n", arg); |
| |
| ReleaseDecoder(&hantrodec_data, arg); |
| |
| break; |
| } |
| case _IOC_NR(HANTRODEC_IOCQ_PP_RESERVE): |
| return ReservePostProcessor(&hantrodec_data, filp); |
| case _IOC_NR(HANTRODEC_IOCT_PP_RELEASE): { |
| if (arg != 0 || pp_owner[arg] != filp) { |
| pr_err("bogus PP release %li\n", arg); |
| return -EFAULT; |
| } |
| |
| ReleasePostProcessor(&hantrodec_data, arg); |
| |
| break; |
| } |
| case _IOC_NR(HANTRODEC_IOCX_DEC_WAIT): { |
| struct core_desc Core; |
| |
| /* get registers from user space */ |
| tmp = copy_from_user(&Core, (void *)arg, sizeof(struct core_desc)); |
| if (tmp) { |
| pr_err("copy_from_user failed, returned %li\n", tmp); |
| return -EFAULT; |
| } |
| |
| if (Core.id >= hantrodec_data.cores) |
| return -EFAULT; |
| |
| return WaitDecReadyAndRefreshRegs(&hantrodec_data, &Core); |
| } |
| case _IOC_NR(HANTRODEC_IOCX_PP_WAIT): { |
| struct core_desc Core; |
| |
| /* get registers from user space */ |
| tmp = copy_from_user(&Core, (void *)arg, sizeof(struct core_desc)); |
| if (tmp) { |
| pr_err("copy_from_user failed, returned %li\n", tmp); |
| return -EFAULT; |
| } |
| |
| if (Core.id >= hantrodec_data.cores) |
| return -EFAULT; |
| |
| return WaitPPReadyAndRefreshRegs(&hantrodec_data, &Core); |
| } |
| case _IOC_NR(HANTRODEC_IOCG_CORE_WAIT): { |
| int id; |
| |
| tmp = WaitCoreReady(&hantrodec_data, filp, &id); |
| __put_user(id, (int *) arg); |
| return tmp; |
| } |
| case _IOC_NR(HANTRODEC_IOX_ASIC_ID): { |
| u32 id; |
| |
| __get_user(id, (u32 *)arg); |
| if (id >= hantrodec_data.cores) |
| return -EFAULT; |
| id = ioread32(hantrodec_data.hwregs[id]); |
| __put_user(id, (u32 *) arg); |
| return 0; |
| } |
| case _IOC_NR(HANTRODEC_IOCG_CORE_ID): { |
| PDEBUG("Get DEC Core_id, format = %li\n", arg); |
| return GetDecCoreID(&hantrodec_data, filp, arg); |
| } |
| case _IOC_NR(HANTRODEC_DEBUG_STATUS): { |
| PDEBUG("hantrodec: dec_irq = 0x%08x\n", dec_irq); |
| PDEBUG("hantrodec: pp_irq = 0x%08x\n", pp_irq); |
| |
| PDEBUG("hantrodec: IRQs received/sent2user = %d / %d\n", |
| atomic_read(&irq_rx), atomic_read(&irq_tx)); |
| |
| for (tmp = 0; tmp < hantrodec_data.cores; tmp++) { |
| PDEBUG("hantrodec: dec_core[%li] %s\n", |
| tmp, dec_owner[tmp] == NULL ? "FREE" : "RESERVED"); |
| PDEBUG("hantrodec: pp_core[%li] %s\n", |
| tmp, pp_owner[tmp] == NULL ? "FREE" : "RESERVED"); |
| } |
| } |
| default: |
| return -ENOTTY; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| struct core_desc_32 { |
| __u32 id; /* id of the Core */ |
| compat_caddr_t regs; /* pointer to user registers */ |
| __u32 size; /* size of register space */ |
| }; |
| |
| static int get_hantro_core_desc32(struct core_desc *kp, struct core_desc_32 __user *up) |
| { |
| u32 tmp; |
| |
| if (!access_ok(VERIFY_READ, up, sizeof(struct core_desc_32)) || |
| get_user(kp->id, &up->id) || |
| get_user(kp->size, &up->size) || |
| get_user(tmp, &up->regs)) { |
| return -EFAULT; |
| } |
| kp->regs = (__force u32 *)compat_ptr(tmp); |
| return 0; |
| } |
| |
| static int put_hantro_core_desc32(struct core_desc *kp, struct core_desc_32 __user *up) |
| { |
| u32 tmp = (u32)((unsigned long)kp->regs); |
| |
| if (!access_ok(VERIFY_WRITE, up, sizeof(struct core_desc_32)) || |
| put_user(kp->id, &up->id) || |
| put_user(kp->size, &up->size) || |
| put_user(tmp, &up->regs)) { |
| return -EFAULT; |
| } |
| return 0; |
| } |
| static long hantrodec_ioctl32(struct file *filp, unsigned int cmd, unsigned long arg) |
| { |
| #define HANTRO_IOCTL32(err, filp, cmd, arg) { \ |
| mm_segment_t old_fs = get_fs(); \ |
| set_fs(KERNEL_DS); \ |
| err = hantrodec_ioctl(filp, cmd, arg); \ |
| if (err) \ |
| return err; \ |
| set_fs(old_fs); \ |
| } |
| |
| union { |
| struct core_desc kcore; |
| unsigned long kux; |
| unsigned int kui; |
| } karg; |
| void __user *up = compat_ptr(arg); |
| long err = 0; |
| |
| switch (_IOC_NR(cmd)) { |
| case _IOC_NR(HANTRODEC_IOCGHWOFFSET): |
| case _IOC_NR(HANTRODEC_IOC_MC_OFFSETS): |
| err = get_user(karg.kux, (s32 __user *)up); |
| if (err) |
| return err; |
| HANTRO_IOCTL32(err, filp, cmd, (unsigned long)&karg); |
| err = put_user(((s32)karg.kux), (s32 __user *)up); |
| break; |
| case _IOC_NR(HANTRODEC_IOCGHWIOSIZE): |
| case _IOC_NR(HANTRODEC_IOC_MC_CORES): |
| case _IOC_NR(HANTRODEC_IOCG_CORE_WAIT): |
| case _IOC_NR(HANTRODEC_IOX_ASIC_ID): |
| err = get_user(karg.kui, (s32 __user *)up); |
| if (err) |
| return err; |
| HANTRO_IOCTL32(err, filp, cmd, (unsigned long)&karg); |
| err = put_user(((s32)karg.kui), (s32 __user *)up); |
| break; |
| case _IOC_NR(HANTRODEC_IOCS_DEC_PUSH_REG): |
| case _IOC_NR(HANTRODEC_IOCS_PP_PUSH_REG): |
| case _IOC_NR(HANTRODEC_IOCX_DEC_WAIT): |
| case _IOC_NR(HANTRODEC_IOCX_PP_WAIT): |
| case _IOC_NR(HANTRODEC_IOCS_DEC_PULL_REG): |
| case _IOC_NR(HANTRODEC_IOCS_PP_PULL_REG): |
| err = get_hantro_core_desc32(&karg.kcore, up); |
| if (err) |
| return err; |
| HANTRO_IOCTL32(err, filp, cmd, (unsigned long)&karg); |
| err = put_hantro_core_desc32(&karg.kcore, up); |
| break; |
| default: |
| err = hantrodec_ioctl(filp, cmd, (unsigned long)up); |
| break; |
| } |
| |
| return err; |
| } |
| |
| #endif //ifdef CONFIG_COMPAT |
| |
| /*-------------------------------------------------------------------------- |
| *Function name : hantrodec_open |
| *Description : open method |
| * |
| *Return type : int |
| *--------------------------------------------------------------------------- |
| */ |
| static int hantrodec_open(struct inode *inode, struct file *filp) |
| { |
| PDEBUG("dev opened\n"); |
| hantro_clk_enable(hantro_dev); |
| pm_runtime_get_sync(hantro_dev); |
| return 0; |
| } |
| |
| /*--------------------------------------------------------------------------- |
| *Function name : hantrodec_release |
| *Description : Release driver |
| * |
| *Return type : int |
| *---------------------------------------------------------------------------- |
| */ |
| static int hantrodec_release(struct inode *inode, struct file *filp) |
| { |
| int n; |
| hantrodec_t *dev = &hantrodec_data; |
| |
| PDEBUG("closing ...\n"); |
| |
| for (n = 0; n < dev->cores; n++) { |
| if (dec_owner[n] == filp) { |
| PDEBUG("releasing dec Core %i lock\n", n); |
| ReleaseDecoder(dev, n); |
| } |
| } |
| |
| for (n = 0; n < 1; n++) { |
| if (pp_owner[n] == filp) { |
| PDEBUG("releasing pp Core %i lock\n", n); |
| ReleasePostProcessor(dev, n); |
| } |
| } |
| |
| pm_runtime_put_sync(hantro_dev); |
| hantro_clk_disable(hantro_dev); |
| PDEBUG("closed\n"); |
| return 0; |
| } |
| |
| /*--------------------------------------------------------------------------- |
| *Function name : hantro_mmap |
| *Description : memory map interface for hantro file operation |
| * |
| *Return type : int |
| *--------------------------------------------------------------------------- |
| */ |
| static int hantro_mmap(struct file *fp, struct vm_area_struct *vm) |
| { |
| if (vm->vm_pgoff == (multicorebase[0] >> PAGE_SHIFT) || vm->vm_pgoff == (multicorebase[1] >> PAGE_SHIFT)) { |
| vm->vm_flags |= VM_IO; |
| vm->vm_page_prot = pgprot_noncached(vm->vm_page_prot); |
| PDEBUG("hantro 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; |
| } |
| } |
| |
| /* VFS methods */ |
| static const struct file_operations hantrodec_fops = { |
| .owner = THIS_MODULE, |
| .open = hantrodec_open, |
| .release = hantrodec_release, |
| .unlocked_ioctl = hantrodec_ioctl, |
| .fasync = NULL, |
| .mmap = hantro_mmap, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = hantrodec_ioctl32, |
| #endif |
| }; |
| |
| /*--------------------------------------------------------------------------- |
| *Function name : hantrodec_init |
| *Description : Initialize the driver |
| * |
| *Return type : int |
| *--------------------------------------------------------------------------- |
| */ |
| int hantrodec_init(struct platform_device *pdev) |
| { |
| int result; |
| int irq_0, irq_1; |
| |
| dec_irq = 0; |
| pp_irq = 0; |
| pr_debug("hantrodec: Init multi Core[0] at 0x%16lx\n" |
| " Core[1] at 0x%16lx\n", multicorebase[0], multicorebase[1]); |
| |
| hantrodec_data.cores = 0; |
| hantrodec_data.iosize[0] = DEC_IO_SIZE_0; |
| hantrodec_data.iosize[1] = DEC_IO_SIZE_1; |
| |
| hantrodec_data.async_queue_dec = NULL; |
| hantrodec_data.async_queue_pp = NULL; |
| |
| result = register_chrdev(hantrodec_major, "hantrodec", &hantrodec_fops); |
| if (result < 0) { |
| pr_err("hantrodec: unable to get major %d\n", hantrodec_major); |
| goto err; |
| } else if (result != 0) { /* this is for dynamic major */ |
| hantrodec_major = result; |
| } |
| |
| result = ReserveIO(); |
| if (result < 0) |
| goto err; |
| |
| memset(dec_owner, 0, sizeof(dec_owner)); |
| memset(pp_owner, 0, sizeof(pp_owner)); |
| |
| sema_init(&dec_core_sem, hantrodec_data.cores-1); |
| sema_init(&pp_core_sem, 1); |
| |
| /* read configuration fo all cores */ |
| ReadCoreConfig(&hantrodec_data); |
| |
| /* reset hardware */ |
| ResetAsic(&hantrodec_data); |
| |
| /* register irq for each core*/ |
| irq_0 = platform_get_irq_byname(pdev, "irq_hantro_g1"); |
| if (irq_0 > 0) { |
| hantrodec_data.irq[0] = irq_0; |
| result = request_irq(irq_0, hantrodec_isr, IRQF_SHARED, |
| "hantrodec", (void *) &hantrodec_data); |
| |
| if (result != 0) { |
| if (result == -EINVAL) |
| pr_err("hantrodec: Bad irq number or handler\n"); |
| else if (result == -EBUSY) { |
| pr_err("hantrodec: IRQ <%d> busy, change your config\n", |
| hantrodec_data.irq[0]); |
| } |
| ReleaseIO(); |
| goto err; |
| } |
| } else { |
| pr_err("hantrodec: IRQ0 not in use!\n"); |
| goto err; |
| } |
| |
| irq_1 = platform_get_irq_byname(pdev, "irq_hantro_g2"); |
| if (irq_1 > 0) { |
| hantrodec_data.irq[1] = irq_1; |
| result = request_irq(irq_1, hantrodec_isr, IRQF_SHARED, |
| "hantrodec", (void *) &hantrodec_data); |
| |
| if (result != 0) { |
| if (result == -EINVAL) |
| pr_err("hantrodec: Bad irq number or handler\n"); |
| else if (result == -EBUSY) { |
| pr_err("hantrodec: IRQ <%d> busy, change your config\n", |
| hantrodec_data.irq[1]); |
| } |
| |
| ReleaseIO(); |
| goto err; |
| } |
| } else { |
| pr_err("hantrodec: IRQ1 not in use!\n"); |
| goto err; |
| } |
| pr_info("hantrodec: module inserted. Major = %d\n", hantrodec_major); |
| |
| return 0; |
| |
| err: |
| pr_err("hantrodec: module not inserted\n"); |
| unregister_chrdev(hantrodec_major, "hantrodec"); |
| return result; |
| } |
| |
| /*--------------------------------------------------------------------------- |
| *Function name : hantrodec_cleanup |
| *Description : clean up |
| * |
| *Return type : int |
| *--------------------------------------------------------------------------- |
| */ |
| void hantrodec_cleanup(void) |
| { |
| hantrodec_t *dev = &hantrodec_data; |
| int n = 0; |
| /* reset hardware */ |
| ResetAsic(dev); |
| |
| /* free the IRQ */ |
| for (n = 0; n < dev->cores; n++) { |
| if (dev->irq[n] != -1) |
| free_irq(dev->irq[n], (void *) dev); |
| } |
| |
| ReleaseIO(); |
| |
| unregister_chrdev(hantrodec_major, "hantrodec"); |
| |
| PDEBUG("hantrodec: module removed\n"); |
| |
| } |
| |
| /*--------------------------------------------------------------------------- |
| *Function name : CheckHwId |
| *Return type : int |
| *--------------------------------------------------------------------------- |
| */ |
| static int CheckHwId(hantrodec_t *dev) |
| { |
| long int hwid; |
| int i; |
| size_t num_hw = sizeof(DecHwId) / sizeof(*DecHwId); |
| |
| int found = 0; |
| |
| for (i = 0; i < dev->cores; i++) { |
| if (dev->hwregs[i] != NULL) { |
| hwid = readl(dev->hwregs[i]); |
| pr_debug("hantrodec: Core %d HW ID=0x%16lx\n", i, hwid); |
| hwid = (hwid >> 16) & 0xFFFF; /* product version only */ |
| |
| while (num_hw--) { |
| if (hwid == DecHwId[num_hw]) { |
| pr_debug("hantrodec: Supported HW found at 0x%16lx\n", |
| multicorebase[i]); |
| found++; |
| dev->hw_id[i] = hwid; |
| break; |
| } |
| } |
| if (!found) { |
| pr_err("hantrodec: Unknown HW found at 0x%16lx\n", multicorebase[i]); |
| return 0; |
| } |
| found = 0; |
| num_hw = sizeof(DecHwId) / sizeof(*DecHwId); |
| } |
| } |
| |
| return 1; |
| } |
| |
| /*--------------------------------------------------------------------------- |
| *Function name : ReserveIO |
| *Description : IO reserve |
| * |
| *Return type : int |
| *--------------------------------------------------------------------------- |
| */ |
| static int ReserveIO(void) |
| { |
| int i; |
| |
| for (i = 0; i < HXDEC_MAX_CORES; i++) { |
| if (multicorebase[i] != -1) { |
| if (!request_mem_region(multicorebase[i], hantrodec_data.iosize[i], "hantrodec0")) { |
| pr_err("hantrodec: failed to reserve HW regs\n"); |
| return -EBUSY; |
| } |
| |
| hantrodec_data.hwregs[i] = (volatile u8 *) ioremap_nocache(multicorebase[i], |
| hantrodec_data.iosize[i]); |
| |
| if (hantrodec_data.hwregs[i] == NULL) { |
| pr_err("hantrodec: failed to ioremap HW regs\n"); |
| ReleaseIO(); |
| return -EBUSY; |
| } |
| hantrodec_data.cores++; |
| } |
| } |
| |
| /* check for correct HW */ |
| if (!CheckHwId(&hantrodec_data)) { |
| ReleaseIO(); |
| return -EBUSY; |
| } |
| |
| return 0; |
| } |
| |
| /*--------------------------------------------------------------------------- |
| *Function name : releaseIO |
| *Description : release |
| * |
| *Return type : void |
| *--------------------------------------------------------------------------- |
| */ |
| static void ReleaseIO(void) |
| { |
| int i; |
| |
| for (i = 0; i < hantrodec_data.cores; i++) { |
| if (hantrodec_data.hwregs[i]) |
| iounmap((void *) hantrodec_data.hwregs[i]); |
| release_mem_region(multicorebase[i], hantrodec_data.iosize[i]); |
| } |
| } |
| |
| /*--------------------------------------------------------------------------- |
| *Function name : hantrodec_isr |
| *Description : interrupt handler |
| * |
| *Return type : irqreturn_t |
| *--------------------------------------------------------------------------- |
| */ |
| irqreturn_t hantrodec_isr(int irq, void *dev_id) |
| { |
| unsigned long flags; |
| unsigned int handled = 0; |
| int i; |
| volatile u8 *hwregs; |
| |
| hantrodec_t *dev = (hantrodec_t *) dev_id; |
| u32 irq_status_dec; |
| |
| spin_lock_irqsave(&owner_lock, flags); |
| |
| for (i = 0; i < dev->cores; i++) { |
| volatile u8 *hwregs = dev->hwregs[i]; |
| |
| /* interrupt status register read */ |
| irq_status_dec = ioread32(hwregs + HANTRODEC_IRQ_STAT_DEC_OFF); |
| |
| if (irq_status_dec & HANTRODEC_DEC_IRQ) { |
| /* clear dec IRQ */ |
| irq_status_dec &= (~HANTRODEC_DEC_IRQ); |
| iowrite32(irq_status_dec, hwregs + HANTRODEC_IRQ_STAT_DEC_OFF); |
| |
| PDEBUG("decoder IRQ received! Core %d\n", i); |
| |
| atomic_inc(&irq_rx); |
| |
| dec_irq |= (1 << i); |
| |
| //wake_up_interruptible_all(&dec_wait_queue); |
| wake_up_all(&dec_wait_queue); |
| handled++; |
| } |
| } |
| |
| spin_unlock_irqrestore(&owner_lock, flags); |
| |
| if (!handled) |
| pr_info("IRQ received, but not hantrodec's!\n"); |
| |
| (void)hwregs; |
| return IRQ_RETVAL(handled); |
| } |
| |
| /*--------------------------------------------------------------------------- |
| *Function name : ResetAsic |
| *Description : reset asic |
| * |
| *Return type : |
| *--------------------------------------------------------------------------- |
| */ |
| void ResetAsic(hantrodec_t *dev) |
| { |
| int i, j; |
| u32 status; |
| |
| for (j = 0; j < dev->cores; j++) { |
| status = ioread32(dev->hwregs[j] + HANTRODEC_IRQ_STAT_DEC_OFF); |
| |
| if (status & HANTRODEC_DEC_E) { |
| /* abort with IRQ disabled */ |
| status = HANTRODEC_DEC_ABORT | HANTRODEC_DEC_IRQ_DISABLE; |
| iowrite32(status, dev->hwregs[j] + HANTRODEC_IRQ_STAT_DEC_OFF); |
| } |
| |
| if (IS_G1(dev->hw_id[j])) |
| /* reset PP */ |
| iowrite32(0, dev->hwregs[j] + HANTRO_IRQ_STAT_PP_OFF); |
| |
| for (i = 4; i < dev->iosize[j]; i += 4) |
| iowrite32(0, dev->hwregs[j] + i); |
| } |
| } |
| |
| /*--------------------------------------------------------------------------- |
| *Function name : dump_regs |
| *Description : Dump registers |
| * |
| *Return type : |
| *--------------------------------------------------------------------------- |
| */ |
| #ifdef HANTRODEC_DEBUG |
| void dump_regs(hantrodec_t *dev) |
| { |
| int i, c; |
| |
| PDEBUG("Reg Dump Start\n"); |
| for (c = 0; c < dev->cores; c++) { |
| for (i = 0; i < dev->iosize[c]; i += 4*4) { |
| PDEBUG("\toffset %04X: %08X %08X %08X %08X\n", i, |
| ioread32(dev->hwregs[c] + i), |
| ioread32(dev->hwregs[c] + i + 4), |
| ioread32(dev->hwregs[c] + i + 8), |
| ioread32(dev->hwregs[c] + i + 12)); |
| } |
| } |
| PDEBUG("Reg Dump End\n"); |
| } |
| #endif |
| |
| static int hantro_dev_probe(struct platform_device *pdev) |
| { |
| int err = 0; |
| struct device *temp_class; |
| struct resource *res; |
| unsigned long reg_base; |
| |
| hantro_dev = &pdev->dev; |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs_hantro"); |
| if (!res) { |
| pr_err("hantro: unable to get vpu base addr\n"); |
| return -ENODEV; |
| } |
| reg_base = res->start; |
| if ((ulong)reg_base != multicorebase[0]) { |
| pr_err("hantrodec: regbase(0x%lX) not equal to expected value(0x%lX)\n", reg_base, multicorebase[0]); |
| return -ENODEV; |
| } |
| |
| hantro_clk_g1 = clk_get(&pdev->dev, "clk_hantro_g1"); |
| hantro_clk_g2 = clk_get(&pdev->dev, "clk_hantro_g2"); |
| hantro_clk_bus = clk_get(&pdev->dev, "clk_hantro_bus"); |
| if (IS_ERR(hantro_clk_g1) || IS_ERR(hantro_clk_g2) || IS_ERR(hantro_clk_bus)) { |
| pr_err("hantro: get clock failed\n"); |
| return -ENODEV; |
| } |
| pr_debug("hantro: g1, g2, bus clock: 0x%lX, 0x%lX, 0x%lX\n", clk_get_rate(hantro_clk_g1), |
| clk_get_rate(hantro_clk_g2), clk_get_rate(hantro_clk_bus)); |
| |
| hantro_regulator = regulator_get(&pdev->dev, "regulator"); |
| if (IS_ERR(hantro_regulator)) { |
| pr_err("hantro: get regulator failed\n"); |
| return -ENODEV; |
| } |
| hantro_update_voltage(&pdev->dev); |
| |
| hantro_clk_enable(&pdev->dev); |
| pm_runtime_enable(&pdev->dev); |
| pm_runtime_get_sync(&pdev->dev); |
| hantro_ctrlblk_reset(&pdev->dev); |
| |
| err = hantrodec_init(pdev); |
| if (0 != err) { |
| pr_err("hantro: hantrodec_init failed\n"); |
| goto error; |
| } |
| |
| hantro_class = class_create(THIS_MODULE, "mxc_hantro"); |
| if (IS_ERR(hantro_class)) { |
| err = PTR_ERR(hantro_class); |
| goto error; |
| } |
| temp_class = device_create(hantro_class, NULL, MKDEV(hantrodec_major, 0), NULL, DEVICE_NAME); |
| if (IS_ERR(temp_class)) { |
| err = PTR_ERR(temp_class); |
| goto err_out_class; |
| } |
| |
| #ifdef CONFIG_DEVICE_THERMAL |
| HANTRO_REG_THERMAL_NOTIFIER(&hantro_thermal_hot_notifier); |
| thermal_event = 0; |
| thermal_cur = 0; |
| hantro_dynamic_clock = 0; |
| #endif |
| timeout = 0; |
| goto out; |
| |
| err_out_class: |
| device_destroy(hantro_class, MKDEV(hantrodec_major, 0)); |
| class_destroy(hantro_class); |
| error: |
| pr_err("hantro probe failed\n"); |
| out: |
| pm_runtime_put_sync(&pdev->dev); |
| hantro_clk_disable(&pdev->dev); |
| return err; |
| } |
| |
| static int hantro_dev_remove(struct platform_device *pdev) |
| { |
| hantro_clk_enable(&pdev->dev); |
| pm_runtime_get_sync(&pdev->dev); |
| if (hantrodec_major > 0) { |
| device_destroy(hantro_class, MKDEV(hantrodec_major, 0)); |
| class_destroy(hantro_class); |
| hantrodec_cleanup(); |
| hantrodec_major = 0; |
| } |
| pm_runtime_put_sync(&pdev->dev); |
| pm_runtime_disable(&pdev->dev); |
| hantro_clk_disable(&pdev->dev); |
| |
| #ifdef CONFIG_DEVICE_THERMAL |
| HANTRO_UNREG_THERMAL_NOTIFIER(&hantro_thermal_hot_notifier); |
| #endif |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM |
| static int hantro_suspend(struct device *dev) |
| { |
| pm_runtime_put_sync_suspend(dev); //power off |
| return 0; |
| } |
| static int hantro_resume(struct device *dev) |
| { |
| pm_runtime_get_sync(dev); //power on |
| hantro_ctrlblk_reset(dev); |
| return 0; |
| } |
| static int hantro_runtime_suspend(struct device *dev) |
| { |
| release_bus_freq(BUS_FREQ_HIGH); |
| return 0; |
| } |
| |
| static int hantro_runtime_resume(struct device *dev) |
| { |
| request_bus_freq(BUS_FREQ_HIGH); |
| hantro_ctrlblk_reset(dev); |
| return 0; |
| } |
| |
| static const struct dev_pm_ops hantro_pm_ops = { |
| SET_RUNTIME_PM_OPS(hantro_runtime_suspend, hantro_runtime_resume, NULL) |
| SET_SYSTEM_SLEEP_PM_OPS(hantro_suspend, hantro_resume) |
| }; |
| #endif //CONFIG_PM |
| |
| static const struct of_device_id hantro_of_match[] = { |
| { .compatible = "nxp,imx8mq-hantro", }, |
| {/* sentinel */} |
| }; |
| MODULE_DEVICE_TABLE(of, hantro_of_match); |
| |
| |
| static struct platform_driver mxchantro_driver = { |
| .driver = { |
| .name = "mxc_hantro", |
| .of_match_table = hantro_of_match, |
| #ifdef CONFIG_PM |
| .pm = &hantro_pm_ops, |
| #endif |
| }, |
| .probe = hantro_dev_probe, |
| .remove = hantro_dev_remove, |
| }; |
| |
| static int __init hantro_init(void) |
| { |
| int ret = platform_driver_register(&mxchantro_driver); |
| |
| return ret; |
| } |
| |
| static void __exit hantro_exit(void) |
| { |
| if (!IS_ERR(hantro_clk_g1)) |
| clk_put(hantro_clk_g1); |
| if (!IS_ERR(hantro_clk_g2)) |
| clk_put(hantro_clk_g2); |
| if (!IS_ERR(hantro_clk_bus)) |
| clk_put(hantro_clk_bus); |
| if (!IS_ERR(hantro_regulator)) |
| regulator_put(hantro_regulator); |
| platform_driver_unregister(&mxchantro_driver); |
| } |
| |
| module_init(hantro_init); |
| module_exit(hantro_exit); |
| |
| /* module description */ |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Google Finland Oy"); |
| MODULE_DESCRIPTION("Driver module for Hantro Decoder/Post-Processor"); |
| |