// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2020 Rockchip Electronics Co., Ltd. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define DECOM_CTRL 0x0 #define DECOM_ENR 0x4 #define DECOM_RADDR 0x8 #define DECOM_WADDR 0xc #define DECOM_UDDSL 0x10 #define DECOM_UDDSH 0x14 #define DECOM_TXTHR 0x18 #define DECOM_RXTHR 0x1c #define DECOM_SLEN 0x20 #define DECOM_STAT 0x24 #define DECOM_ISR 0x28 #define DECOM_IEN 0x2c #define DECOM_AXI_STAT 0x30 #define DECOM_TSIZEL 0x34 #define DECOM_TSIZEH 0x38 #define DECOM_MGNUM 0x3c #define DECOM_FRAME 0x40 #define DECOM_DICTID 0x44 #define DECOM_CSL 0x48 #define DECOM_CSH 0x4c #define DECOM_LMTSL 0x50 #define DECOM_LMTSH 0x54 #define LZ4_HEAD_CSUM_CHECK_EN BIT(1) #define LZ4_BLOCK_CSUM_CHECK_EN BIT(2) #define LZ4_CONT_CSUM_CHECK_EN BIT(3) #define DSOLIEN BIT(19) #define ZDICTEIEN BIT(18) #define GCMEIEN BIT(17) #define GIDEIEN BIT(16) #define CCCEIEN BIT(15) #define BCCEIEN BIT(14) #define HCCEIEN BIT(13) #define CSEIEN BIT(12) #define DICTEIEN BIT(11) #define VNEIEN BIT(10) #define WNEIEN BIT(9) #define RDCEIEN BIT(8) #define WRCEIEN BIT(7) #define DISEIEN BIT(6) #define LENEIEN BIT(5) #define LITEIEN BIT(4) #define SQMEIEN BIT(3) #define SLCIEN BIT(2) #define HDEIEN BIT(1) #define DSIEN BIT(0) #define DECOM_STOP BIT(0) #define DECOM_COMPLETE BIT(0) #define DECOM_GZIP_MODE BIT(4) #define DECOM_ZLIB_MODE BIT(5) #define DECOM_DEFLATE_MODE BIT(0) #define DECOM_ENABLE 0x1 #define DECOM_DISABLE 0x0 #define DECOM_INT_MASK \ (DSOLIEN | ZDICTEIEN | GCMEIEN | GIDEIEN | \ CCCEIEN | BCCEIEN | HCCEIEN | CSEIEN | \ DICTEIEN | VNEIEN | WNEIEN | RDCEIEN | WRCEIEN | \ DISEIEN | LENEIEN | LITEIEN | SQMEIEN | SLCIEN | \ HDEIEN | DSIEN) struct rk_decom { struct device *dev; int irq; int num_clocks; struct clk_bulk_data *clocks; void __iomem *regs; phys_addr_t mem_start; size_t mem_size; struct reset_control *reset; }; static struct rk_decom *g_decom; static DECLARE_WAIT_QUEUE_HEAD(g_decom_wait); static bool g_decom_complete; static bool g_decom_noblocking; static u64 g_decom_data_len; void __init wait_initrd_hw_decom_done(void) { wait_event(g_decom_wait, g_decom_complete); } int rk_decom_wait_done(u32 timeout, u64 *decom_len) { int ret; if (!decom_len) return -EINVAL; ret = wait_event_timeout(g_decom_wait, g_decom_complete, timeout * HZ); if (!ret) { if (g_decom) clk_bulk_disable_unprepare(g_decom->num_clocks, g_decom->clocks); return -ETIMEDOUT; } *decom_len = g_decom_data_len; return 0; } EXPORT_SYMBOL(rk_decom_wait_done); static DECLARE_WAIT_QUEUE_HEAD(decom_init_done); int rk_decom_start(u32 mode, phys_addr_t src, phys_addr_t dst, u32 dst_max_size) { int ret; u32 irq_status; u32 decom_enr; u32 decom_mode = rk_get_decom_mode(mode); wait_event_timeout(decom_init_done, g_decom, HZ); if (!g_decom) return -EINVAL; if (g_decom->mem_start) pr_info("%s: mode %u src %pa dst %pa max_size %u\n", __func__, mode, &src, &dst, dst_max_size); ret = clk_bulk_prepare_enable(g_decom->num_clocks, g_decom->clocks); if (ret) return ret; g_decom_complete = false; g_decom_data_len = 0; g_decom_noblocking = rk_get_noblocking_flag(mode); decom_enr = readl(g_decom->regs + DECOM_ENR); if (decom_enr & 0x1) { pr_err("decompress busy\n"); ret = -EBUSY; goto error; } if (g_decom->reset) { reset_control_assert(g_decom->reset); udelay(10); reset_control_deassert(g_decom->reset); } irq_status = readl(g_decom->regs + DECOM_ISR); /* clear interrupts */ if (irq_status) writel(irq_status, g_decom->regs + DECOM_ISR); switch (decom_mode) { case LZ4_MOD: writel(LZ4_CONT_CSUM_CHECK_EN | LZ4_HEAD_CSUM_CHECK_EN | LZ4_BLOCK_CSUM_CHECK_EN | LZ4_MOD, g_decom->regs + DECOM_CTRL); break; case GZIP_MOD: writel(DECOM_DEFLATE_MODE | DECOM_GZIP_MODE, g_decom->regs + DECOM_CTRL); break; case ZLIB_MOD: writel(DECOM_DEFLATE_MODE | DECOM_ZLIB_MODE, g_decom->regs + DECOM_CTRL); break; default: pr_err("undefined mode : %d\n", decom_mode); ret = -EINVAL; goto error; } writel(src, g_decom->regs + DECOM_RADDR); writel(dst, g_decom->regs + DECOM_WADDR); writel(dst_max_size, g_decom->regs + DECOM_LMTSL); writel(0x0, g_decom->regs + DECOM_LMTSH); writel(DECOM_INT_MASK, g_decom->regs + DECOM_IEN); writel(DECOM_ENABLE, g_decom->regs + DECOM_ENR); return 0; error: clk_bulk_disable_unprepare(g_decom->num_clocks, g_decom->clocks); return ret; } EXPORT_SYMBOL(rk_decom_start); static irqreturn_t rk_decom_irq_handler(int irq, void *priv) { struct rk_decom *rk_dec = priv; u32 irq_status; u32 decom_status; irq_status = readl(rk_dec->regs + DECOM_ISR); /* clear interrupts */ writel(irq_status, rk_dec->regs + DECOM_ISR); if (irq_status & DECOM_STOP) { decom_status = readl(rk_dec->regs + DECOM_STAT); if (decom_status & DECOM_COMPLETE) { g_decom_complete = true; g_decom_data_len = readl(rk_dec->regs + DECOM_TSIZEH); g_decom_data_len = (g_decom_data_len << 32) | readl(rk_dec->regs + DECOM_TSIZEL); wake_up(&g_decom_wait); if (rk_dec->mem_start) dev_info(rk_dec->dev, "decom completed, decom_data_len = %llu\n", g_decom_data_len); } else { dev_info(rk_dec->dev, "decom failed, irq_status = 0x%x, decom_status = 0x%x, try again !\n", irq_status, decom_status); print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 32, 4, rk_dec->regs, 0x128, false); if (g_decom_noblocking) { dev_info(rk_dec->dev, "decom failed and exit in noblocking mode."); writel(DECOM_DISABLE, rk_dec->regs + DECOM_ENR); writel(0, g_decom->regs + DECOM_IEN); g_decom_complete = true; g_decom_data_len = 0; g_decom_noblocking = false; wake_up(&g_decom_wait); } else { writel(DECOM_ENABLE, rk_dec->regs + DECOM_ENR); } } } return IRQ_WAKE_THREAD; } static irqreturn_t rk_decom_irq_thread(int irq, void *priv) { struct rk_decom *rk_dec = priv; if (g_decom_complete) { void *start, *end; if (rk_dec->mem_start) { /* * Now it is safe to free reserve memory that * store the origin ramdisk file */ start = phys_to_virt(rk_dec->mem_start); end = start + rk_dec->mem_size; free_reserved_area(start, end, -1, "ramdisk gzip archive"); rk_dec->mem_start = 0; } clk_bulk_disable_unprepare(rk_dec->num_clocks, rk_dec->clocks); } return IRQ_HANDLED; } static int __init rockchip_decom_probe(struct platform_device *pdev) { struct rk_decom *rk_dec; struct resource *res = NULL; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct device_node *mem; struct resource reg; int ret = 0; rk_dec = devm_kzalloc(dev, sizeof(*rk_dec), GFP_KERNEL); if (!rk_dec) return -ENOMEM; rk_dec->dev = dev; rk_dec->irq = platform_get_irq(pdev, 0); if (rk_dec->irq < 0) { dev_err(dev, "failed to get rk_dec irq\n"); return -ENOENT; } mem = of_parse_phandle(np, "memory-region", 0); if (!mem) { dev_err(dev, "missing \"memory-region\" property\n"); return -ENODEV; } ret = of_address_to_resource(mem, 0, ®); of_node_put(mem); if (ret) { dev_err(dev, "missing \"reg\" property\n"); return -ENODEV; } rk_dec->mem_start = reg.start; rk_dec->mem_size = resource_size(®); rk_dec->num_clocks = devm_clk_bulk_get_all(dev, &rk_dec->clocks); if (rk_dec->num_clocks < 0) { dev_err(dev, "failed to get decompress clock\n"); return -ENODEV; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); rk_dec->regs = devm_ioremap_resource(dev, res); if (IS_ERR(rk_dec->regs)) { ret = PTR_ERR(rk_dec->regs); goto disable_clk; } dev_set_drvdata(dev, rk_dec); rk_dec->reset = devm_reset_control_get_exclusive(dev, "dresetn"); if (IS_ERR(rk_dec->reset)) { ret = PTR_ERR(rk_dec->reset); if (ret != -ENOENT) return ret; dev_dbg(dev, "no reset control found\n"); rk_dec->reset = NULL; } ret = devm_request_threaded_irq(dev, rk_dec->irq, rk_decom_irq_handler, rk_decom_irq_thread, IRQF_ONESHOT, dev_name(dev), rk_dec); if (ret < 0) { dev_err(dev, "failed to attach decompress irq\n"); goto disable_clk; } g_decom = rk_dec; wake_up(&decom_init_done); return 0; disable_clk: clk_bulk_disable_unprepare(rk_dec->num_clocks, rk_dec->clocks); return ret; } #ifdef CONFIG_OF static const struct of_device_id rockchip_decom_dt_match[] = { { .compatible = "rockchip,hw-decompress" }, {}, }; #endif static struct platform_driver rk_decom_driver = { .driver = { .name = "rockchip_hw_decompress", .of_match_table = rockchip_decom_dt_match, }, }; static int __init rockchip_hw_decompress_init(void) { struct device_node *node; node = of_find_matching_node(NULL, rockchip_decom_dt_match); if (node) { of_platform_device_create(node, NULL, NULL); of_node_put(node); return platform_driver_probe(&rk_decom_driver, rockchip_decom_probe); } return 0; } pure_initcall(rockchip_hw_decompress_init);