// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2022 Rockchip Electronics Co., Ltd * * author: * Yandong Lin, yandong.lin@rock-chips.com */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include "rockchip_dvbm.h" #define RK_DVBM "rk_dvbm" unsigned int dvbm_debug; module_param(dvbm_debug, uint, 0644); MODULE_PARM_DESC(dvbm_debug, "bit switch for dvbm debug information"); static struct dvbm_ctx *g_ctx; #define DVBM_DEBUG 0x00000001 #define DVBM_DEBUG_IRQ 0x00000002 #define DVBM_DEBUG_REG 0x00000004 #define DVBM_DEBUG_DUMP 0x00000008 #define DVBM_DEBUG_FRM 0x00000010 #define dvbm_debug(fmt, args...) \ do { \ if (unlikely(dvbm_debug & (DVBM_DEBUG))) \ pr_info(fmt, ##args); \ } while (0) #define dvbm_debug_reg(fmt, args...) \ do { \ if (unlikely(dvbm_debug & (DVBM_DEBUG_REG))) \ pr_info(fmt, ##args); \ } while (0) #define dvbm_debug_irq(fmt, args...) \ do { \ if (unlikely(dvbm_debug & (DVBM_DEBUG_IRQ))) \ pr_info(fmt, ##args); \ } while (0) #define dvbm_debug_dump(fmt, args...) \ do { \ if (unlikely(dvbm_debug & (DVBM_DEBUG_DUMP))) \ pr_info(fmt, ##args); \ } while (0) #define dvbm_debug_frm(fmt, args...) \ do { \ if (unlikely(dvbm_debug & (DVBM_DEBUG_FRM))) \ pr_info(fmt, ##args); \ } while (0) #define dvbm_err(fmt, args...) \ pr_err(fmt, ##args) enum dvbm_flow { ISP_CFG = 1, ISP_CONNECT = 2, VEPU_CFG = 3, VEPU_CONNECT = 4, }; /* dvbm status reg bit value define */ #define BUF_OVERFLOW BIT(0) #define RESYNC_FINISH BIT(1) #define ISP_CNCT_TIMEOUT BIT(2) #define VEPU_CNCT_TIMEOUT BIT(3) #define VEPU_HANDSHAKE_TIMEOUT BIT(4) #define ISP_CNCT BIT(5) #define ISP_DISCNCT BIT(6) #define VEPU_CNCT BIT(7) #define VEPU_DISCNCT BIT(8) /* dvbm reg addr define */ #define DVBM_VERSION 0x0 #define DVBM_ISP_CNCT 0x4 #define DVBM_VEPU_CNCT 0x8 /* cfg regs */ #define DVBM_CFG 0xC #define DVBM_WDG_CFG0 0x10 #define DVBM_WDG_CFG1 0x14 #define DVBM_WDG_CFG2 0x18 /* interrupt regs */ #define DVBM_INT_EN 0x1c #define DVBM_INT_MSK 0x20 #define DVBM_INT_CLR 0x24 #define DVBM_INT_ST 0x28 /* addr regs */ #define DVBM_YBUF_BOT 0x2c #define DVBM_YBUF_TOP 0x30 #define DVBM_YBUF_SADR 0x34 #define DVBM_YBUF_LSTD 0x38 #define DVBM_YBUF_FSTD 0x3c #define DVBM_CBUF_BOT 0x40 #define DVBM_CBUF_TOP 0x44 #define DVBM_CBUF_SADR 0x48 #define DVBM_CBUF_LSTD 0x4c #define DVBM_CBUF_FSTD 0x50 #define DVBM_AFUL_THDY 0x54 #define DVBM_AFUL_THDC 0x58 #define DVBM_OVFL_THDY 0x5c #define DVBM_OVFL_THDC 0x60 /* status regs */ #define DVBM_ST 0x80 #define DVBM_OVFL_ST 0x84 #define DVBM_REG_OFFSET 0x2c #define SOFT_DVBM 1 #define UPDATE_LINE_CNT 0 static void rk_dvbm_set_reg(struct dvbm_ctx *ctx, u32 offset, u32 val) { if (!SOFT_DVBM) { dvbm_debug_reg("write reg[%d] 0x%x = 0x%08x\n", offset >> 2, offset, val); writel(val, ctx->reg_base + offset); } } static u32 rk_dvbm_read_reg(struct dvbm_ctx *ctx, u32 offset) { u32 val = 0; if (!SOFT_DVBM) { val = readl(ctx->reg_base + offset); dvbm_debug_reg("read reg[%d] 0x%x = 0x%08x\n", offset >> 2, offset, val); } return val; } static struct dvbm_ctx *port_to_ctx(struct dvbm_port *port) { struct dvbm_ctx *ctx = NULL; if (IS_ERR_OR_NULL(port)) return g_ctx; if (port->dir == DVBM_ISP_PORT) ctx = container_of(port, struct dvbm_ctx, port_isp); else if (port->dir == DVBM_VEPU_PORT) ctx = container_of(port, struct dvbm_ctx, port_vepu); return ctx; } static void dvbm2enc_callback(struct dvbm_ctx *ctx, enum dvbm_cb_event event, void *arg) { struct dvbm_cb *callback = &ctx->vepu_cb; dvbm_callback cb = callback->cb; if (!ctx->port_vepu.linked) return; if (cb) cb(callback->ctx, event, arg); } static void rk_dvbm_dump_regs(struct dvbm_ctx *ctx) { u32 start = ctx->dump_s;//0x80; u32 end = ctx->dump_e;//0xb8; u32 i; dvbm_debug_dump("=== %s ===\n", __func__); for (i = start; i <= end; i += 4) dvbm_debug_dump("reg[0x%0x] = 0x%08x\n", i, readl(ctx->reg_base + i)); dvbm_debug_dump("=== %s ===\n", __func__); } static int rk_dvbm_clk_on(struct dvbm_ctx *ctx) { int ret = 0; if (ctx->clk) ret = clk_prepare_enable(ctx->clk); if (ret) dev_err(ctx->dev, "clk on failed\n"); return ret; } static int rk_dvbm_clk_off(struct dvbm_ctx *ctx) { if (ctx->clk) clk_disable_unprepare(ctx->clk); return 0; } static void init_isp_infos(struct dvbm_ctx *ctx) { ctx->isp_frm_start = 0; ctx->isp_frm_end = 0; ctx->isp_frm_time = 0; } static void rk_dvbm_show_time(struct dvbm_ctx *ctx) { ktime_t time = ktime_get(); if (ctx->isp_frm_time) dvbm_debug("isp frame start[%d : %d] times %lld us\n", ctx->isp_frm_start, ctx->isp_frm_end, ktime_us_delta(time, ctx->isp_frm_time)); ctx->isp_frm_time = time; } static void rk_dvbm_update_isp_frm_info(struct dvbm_ctx *ctx, u32 line_cnt) { #if UPDATE_LINE_CNT struct dvbm_isp_frm_info *frm_info = &ctx->isp_frm_info; frm_info->line_cnt = ALIGN(line_cnt, 32); dvbm_debug_frm("dvbm frame %d line %d\n", frm_info->frame_cnt, frm_info->line_cnt); dvbm2enc_callback(ctx, DVBM_VEPU_NOTIFY_FRM_INFO, frm_info); #endif } static int rk_dvbm_setup_iobuf(struct dvbm_ctx *ctx) { u32 *data; u32 i; struct rk_dvbm_base *addr_base = &ctx->regs.addr_base; struct dvbm_isp_cfg_t *cfg = &ctx->isp_cfg; addr_base->ybuf_bot = cfg->dma_addr + cfg->ybuf_bot; addr_base->ybuf_top = cfg->dma_addr + cfg->ybuf_top; addr_base->ybuf_sadr = cfg->dma_addr + cfg->ybuf_bot; addr_base->ybuf_fstd = cfg->ybuf_fstd; addr_base->ybuf_lstd = cfg->ybuf_lstd; addr_base->cbuf_bot = cfg->dma_addr + cfg->cbuf_bot; addr_base->cbuf_top = cfg->dma_addr + cfg->cbuf_top; addr_base->cbuf_sadr = cfg->dma_addr + cfg->cbuf_bot; addr_base->cbuf_fstd = cfg->cbuf_fstd; addr_base->cbuf_lstd = cfg->cbuf_lstd; addr_base->aful_thdy = cfg->ybuf_lstd; addr_base->aful_thdc = cfg->ybuf_lstd; addr_base->oful_thdy = cfg->ybuf_lstd; addr_base->oful_thdc = cfg->ybuf_lstd; ctx->isp_max_lcnt = cfg->ybuf_fstd / cfg->ybuf_lstd; ctx->wrap_line = (cfg->ybuf_top - cfg->ybuf_bot) / cfg->ybuf_lstd; ctx->isp_frm_info.frame_cnt = 0; ctx->isp_frm_info.line_cnt = 0; ctx->isp_frm_info.max_line_cnt = ALIGN(ctx->isp_max_lcnt, 32); ctx->isp_frm_info.wrap_line = ctx->wrap_line; dvbm_debug("dma_addr %pad y_lstd %d y_fstd %d\n", &cfg->dma_addr, cfg->ybuf_lstd, cfg->ybuf_fstd); dvbm_debug("ybot 0x%x top 0x%x cbuf bot 0x%x top 0x%x\n", addr_base->ybuf_bot, addr_base->ybuf_top, addr_base->cbuf_bot, addr_base->cbuf_top); data = (u32 *)addr_base; for (i = 0; i < sizeof(struct rk_dvbm_base) / sizeof(u32); i++) rk_dvbm_set_reg(ctx, i * sizeof(u32) + DVBM_REG_OFFSET, data[i]); for (i = 1; i < 65536; i++) if (!((addr_base->ybuf_fstd * i) % (cfg->ybuf_top - cfg->ybuf_bot))) break; ctx->loopcnt = i; return 0; } static void rk_dvbm_reg_init(struct dvbm_ctx *ctx) { struct rk_dvbm_regs *reg = &ctx->regs; u32 *val = (u32 *)reg; reg->int_en.buf_ovfl = 1; reg->int_en.isp_cnct = 1; reg->int_en.vepu_cnct = 1; reg->int_en.vepu_discnct = 1; reg->int_en.isp_discnct = 1; reg->int_en.resync_finish = 1; reg->int_en.isp_cnct_timeout = 1; reg->int_en.vepu_cnct_timeout = 1; reg->int_en.vepu_handshake_timeout = 1; reg->dvbm_cfg.fmt = 0; reg->dvbm_cfg.auto_resyn = 0; reg->dvbm_cfg.ignore_vepu_cnct_ack = 0; reg->dvbm_cfg.start_point_after_vepu_cnct = 0; reg->wdg_cfg0.wdg_isp_cnct_timeout = 0xfffff; reg->wdg_cfg1.wdg_vepu_cnct_timeout = 0xfffff; reg->wdg_cfg2.wdg_vepu_handshake_timeout = 0xfffff; rk_dvbm_set_reg(ctx, DVBM_WDG_CFG0, val[DVBM_WDG_CFG0 >> 2]); rk_dvbm_set_reg(ctx, DVBM_WDG_CFG1, val[DVBM_WDG_CFG1 >> 2]); rk_dvbm_set_reg(ctx, DVBM_WDG_CFG2, val[DVBM_WDG_CFG2 >> 2]); rk_dvbm_set_reg(ctx, DVBM_CFG, val[DVBM_CFG >> 2]); rk_dvbm_set_reg(ctx, DVBM_INT_EN, val[DVBM_INT_EN >> 2]); } struct dvbm_port *rk_dvbm_get_port(struct platform_device *pdev, enum dvbm_port_dir dir) { struct dvbm_ctx *ctx = NULL; struct dvbm_port *port = NULL; if (WARN_ON(!pdev)) return NULL; ctx = (struct dvbm_ctx *)platform_get_drvdata(pdev); WARN_ON(!ctx); dvbm_debug("%s dir %d\n", __func__, dir); if (dir == DVBM_ISP_PORT) port = &ctx->port_isp; else if (dir == DVBM_VEPU_PORT) port = &ctx->port_vepu; return port; } EXPORT_SYMBOL(rk_dvbm_get_port); int rk_dvbm_put(struct dvbm_port *port) { struct dvbm_ctx *ctx = NULL; if (WARN_ON(!port)) return -EINVAL; ctx = port_to_ctx(port); if (!ctx) return -EINVAL; return 0; } EXPORT_SYMBOL(rk_dvbm_put); int rk_dvbm_link(struct dvbm_port *port) { struct dvbm_ctx *ctx; enum dvbm_port_dir dir; struct rk_dvbm_regs *reg; int ret = 0; if (WARN_ON(!port)) return -EINVAL; ctx = port_to_ctx(port); dir = port->dir; reg = &ctx->regs; if (dir == DVBM_ISP_PORT) { if (port->linked) { rk_dvbm_unlink(port); udelay(5); } reg->isp_cnct.isp_cnct = 1; rk_dvbm_set_reg(ctx, DVBM_ISP_CNCT, 0x1); } else if (dir == DVBM_VEPU_PORT) { if (!port->linked) { reg->vepu_cnct.vepu_cnct = 1; rk_dvbm_set_reg(ctx, DVBM_VEPU_CNCT, 0x1); } port->linked = 1; dvbm_debug_dump("=== vepu link ===\n"); rk_dvbm_dump_regs(ctx); dvbm_debug_dump("=== vepu link ===\n"); } dvbm_debug("%s connect frm_cnt[%d : %d]\n", dir == DVBM_ISP_PORT ? "isp" : "vepu", ctx->isp_frm_start, ctx->isp_frm_end); return ret; } EXPORT_SYMBOL(rk_dvbm_link); int rk_dvbm_unlink(struct dvbm_port *port) { struct dvbm_ctx *ctx; enum dvbm_port_dir dir; struct rk_dvbm_regs *reg; if (WARN_ON(!port)) return -EINVAL; ctx = port_to_ctx(port); dir = port->dir; reg = &ctx->regs; if (dir == DVBM_ISP_PORT) { reg->isp_cnct.isp_cnct = 0; rk_dvbm_set_reg(ctx, DVBM_ISP_CNCT, 0); } else if (dir == DVBM_VEPU_PORT) { reg->vepu_cnct.vepu_cnct = 0; port->linked = 0; rk_dvbm_set_reg(ctx, DVBM_VEPU_CNCT, 0); if (!ctx->regs.dvbm_cfg.auto_resyn) { u32 connect = 0; dvbm2enc_callback(ctx, DVBM_VEPU_REQ_CONNECT, &connect); } } dvbm_debug("%s disconnect\n", dir == DVBM_ISP_PORT ? "isp" : "vepu"); return 0; } EXPORT_SYMBOL(rk_dvbm_unlink); int rk_dvbm_set_cb(struct dvbm_port *port, struct dvbm_cb *cb) { struct dvbm_ctx *ctx; enum dvbm_port_dir dir; if (WARN_ON(!port) || WARN_ON(!cb)) return -EINVAL; ctx = port_to_ctx(port); dir = port->dir; if (dir == DVBM_ISP_PORT) { } else if (dir == DVBM_VEPU_PORT) { ctx->vepu_cb.cb = cb->cb; ctx->vepu_cb.ctx = cb->ctx; } return 0; } EXPORT_SYMBOL(rk_dvbm_set_cb); static void rk_dvbm_update_next_adr(struct dvbm_ctx *ctx) { u32 frame_cnt = ctx->isp_frm_start; struct dvbm_isp_cfg_t *isp_cfg = &ctx->isp_cfg; struct dvbm_addr_cfg *vepu_cfg = &ctx->vepu_cfg; u32 y_wrap_size = isp_cfg->ybuf_top - isp_cfg->ybuf_bot; u32 c_wrap_size = isp_cfg->cbuf_top - isp_cfg->cbuf_bot; u32 s_off; frame_cnt = (frame_cnt + 1) % (ctx->loopcnt); s_off = (frame_cnt * isp_cfg->ybuf_fstd) % y_wrap_size; vepu_cfg->ybuf_sadr = isp_cfg->dma_addr + isp_cfg->ybuf_bot + s_off; s_off = (frame_cnt * isp_cfg->cbuf_fstd) % c_wrap_size; vepu_cfg->cbuf_sadr = isp_cfg->dma_addr + isp_cfg->cbuf_bot + s_off; } int rk_dvbm_ctrl(struct dvbm_port *port, enum dvbm_cmd cmd, void *arg) { struct dvbm_ctx *ctx; struct rk_dvbm_regs *reg; if ((cmd < DVBM_ISP_CMD_BASE) || (cmd > DVBM_VEPU_CMD_BUTT)) { dvbm_err("%s input cmd invalid\n", __func__); return -EINVAL; } ctx = port_to_ctx(port); reg = &ctx->regs; switch (cmd) { case DVBM_ISP_SET_CFG: { struct dvbm_isp_cfg_t *cfg = (struct dvbm_isp_cfg_t *)arg; memcpy(&ctx->isp_cfg, cfg, sizeof(struct dvbm_isp_cfg_t)); rk_dvbm_setup_iobuf(ctx); init_isp_infos(ctx); rk_dvbm_update_next_adr(ctx); } break; case DVBM_ISP_FRM_START: { rk_dvbm_update_isp_frm_info(ctx, 0); rk_dvbm_show_time(ctx); } break; case DVBM_ISP_FRM_END: { u32 line_cnt = ctx->isp_max_lcnt; ctx->isp_frm_end = *(u32 *)arg; /* wrap frame_cnt 0 - 255 */ ctx->isp_frm_info.frame_cnt = (ctx->isp_frm_start + 1) % 256; rk_dvbm_update_next_adr(ctx); rk_dvbm_update_isp_frm_info(ctx, line_cnt); ctx->isp_frm_start++; dvbm_debug("isp frame end[%d : %d]\n", ctx->isp_frm_start, ctx->isp_frm_end); } break; case DVBM_ISP_FRM_QUARTER: { u32 line_cnt; line_cnt = ctx->isp_max_lcnt >> 2; rk_dvbm_update_isp_frm_info(ctx, line_cnt); } break; case DVBM_ISP_FRM_HALF: { u32 line_cnt; line_cnt = ctx->isp_max_lcnt >> 1; rk_dvbm_update_isp_frm_info(ctx, line_cnt); } break; case DVBM_ISP_FRM_THREE_QUARTERS: { u32 line_cnt; line_cnt = (ctx->isp_max_lcnt >> 2) * 3; rk_dvbm_update_isp_frm_info(ctx, line_cnt); } break; case DVBM_VEPU_GET_ADR: { struct dvbm_addr_cfg *dvbm_adr = (struct dvbm_addr_cfg *)arg; struct rk_dvbm_base *addr_base = ®->addr_base; dvbm_adr->ybuf_top = addr_base->ybuf_top; dvbm_adr->ybuf_bot = addr_base->ybuf_bot; dvbm_adr->cbuf_top = addr_base->cbuf_top; dvbm_adr->cbuf_bot = addr_base->cbuf_bot; dvbm_adr->cbuf_sadr = ctx->vepu_cfg.cbuf_sadr; dvbm_adr->ybuf_sadr = ctx->vepu_cfg.ybuf_sadr; dvbm_adr->overflow = ctx->isp_frm_info.line_cnt >= ctx->wrap_line; dvbm_adr->frame_id = ctx->isp_frm_info.frame_cnt; dvbm_adr->line_cnt = ctx->isp_frm_info.line_cnt; } break; case DVBM_VEPU_GET_FRAME_INFO: { memcpy(arg, &ctx->isp_frm_info, sizeof(struct dvbm_isp_frm_info)); } break; case DVBM_VEPU_SET_RESYNC: { reg->dvbm_cfg.auto_resyn = *(u32 *)arg; dev_info(ctx->dev, "change resync %s\n", reg->dvbm_cfg.auto_resyn ? "auto" : "soft"); rk_dvbm_set_reg(ctx, DVBM_CFG, ((u32 *)®->dvbm_cfg)[0]); } break; case DVBM_VEPU_SET_CFG: { struct dvbm_vepu_cfg *cfg = (struct dvbm_vepu_cfg *)arg; reg->dvbm_cfg.auto_resyn = cfg->auto_resyn; reg->dvbm_cfg.ignore_vepu_cnct_ack = cfg->ignore_vepu_cnct_ack; reg->dvbm_cfg.start_point_after_vepu_cnct = cfg->start_point_after_vepu_cnct; rk_dvbm_set_reg(ctx, DVBM_CFG, ((u32 *)®->dvbm_cfg)[0]); } break; case DVBM_VEPU_DUMP_REGS: { rk_dvbm_dump_regs(ctx); } break; default: { } break; } return 0; } EXPORT_SYMBOL(rk_dvbm_ctrl); static void dvbm_check_irq(struct dvbm_ctx *ctx) { u32 irq_st = ctx->irq_status; u32 cur_st = ctx->dvbm_status; if (irq_st & ISP_CNCT) { dvbm_debug_irq("%s isp connect success! st 0x%08x\n", __func__, cur_st); ctx->port_isp.linked = 1; } if (irq_st & ISP_DISCNCT) { dvbm_debug_irq("%s isp disconnect success!\n", __func__); ctx->port_isp.linked = 0; } if (irq_st & VEPU_CNCT) { dvbm_debug_irq("%s vepu connect success! st 0x%08x\n", __func__, cur_st); ctx->port_vepu.linked = 1; } if (irq_st & VEPU_DISCNCT) { dvbm_debug_irq("%s vepu disconnect success! st 0x%08x\n", __func__, cur_st); ctx->port_vepu.linked = 0; } if (irq_st & BUF_OVERFLOW) { dvbm_debug_irq("%s buf overflow st 0x%08x auto_resync %d ignore %d\n", __func__, cur_st, ctx->regs.dvbm_cfg.auto_resyn, ctx->ignore_ovfl); if (!ctx->regs.dvbm_cfg.auto_resyn && !ctx->ignore_ovfl) rk_dvbm_unlink(&ctx->port_vepu); } if (irq_st & (ISP_CNCT_TIMEOUT | VEPU_CNCT_TIMEOUT)) rk_dvbm_dump_regs(ctx); } static irqreturn_t rk_dvbm_irq(int irq, void *param) { struct dvbm_ctx *ctx = param; u32 irq_st = 0; u32 cur_st = 0; if (ctx->reg_base) { /* read irq st */ irq_st = rk_dvbm_read_reg(ctx, DVBM_INT_ST); cur_st = rk_dvbm_read_reg(ctx, DVBM_ST); if (irq_st & BUF_OVERFLOW) { dvbm_debug_dump("=== dvbm overflow! dump reg st: 0x%08x===\n", irq_st); rk_dvbm_dump_regs(ctx); dvbm2enc_callback(ctx, DVBM_VEPU_NOTIFY_DUMP, NULL); dvbm_debug_dump("=== dvbm overflow! dump reg end===\n"); } /* clr irq */ rk_dvbm_set_reg(ctx, DVBM_INT_CLR, irq_st); rk_dvbm_set_reg(ctx, DVBM_INT_ST, 0); } ctx->irq_status = irq_st; ctx->dvbm_status = cur_st; dvbm_debug_irq("%s irq status 0x%08x\n", __func__, irq_st); return IRQ_WAKE_THREAD; } static irqreturn_t rk_dvbm_isr(int irq, void *param) { struct dvbm_ctx *ctx = param; dvbm_check_irq(ctx); return IRQ_HANDLED; } static int rk_dvbm_probe(struct platform_device *pdev) { int ret; struct dvbm_ctx *ctx = NULL; struct device *dev = &pdev->dev; struct resource *res = NULL; dev_info(dev, "probe start\n"); ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; dev_info(dev, "dvbm ctx %p\n", ctx); ctx->dev = dev; atomic_set(&ctx->isp_ref, 0); atomic_set(&ctx->vepu_ref, 0); ctx->port_isp.dir = DVBM_ISP_PORT; ctx->port_vepu.dir = DVBM_VEPU_PORT; platform_set_drvdata(pdev, ctx); pm_runtime_enable(dev); /* get irq */ ctx->irq = platform_get_irq(pdev, 0); if (ctx->irq < 0) { dev_err(&pdev->dev, "no interrupt resource found\n"); ret = -ENODEV; goto failed; } /* get mem resource */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "no memory resource defined\n"); ret = -ENODEV; goto failed; } ctx->reg_base = devm_ioremap_resource(dev, res); if (IS_ERR_OR_NULL(ctx->reg_base)) { dev_err(dev, "ioremap failed for resource %pR\n", res); ret = -ENODEV; goto failed; } ctx->clk = devm_clk_get(ctx->dev, "clk_core"); if (IS_ERR_OR_NULL(ctx->clk)) { dev_err(dev, "clk_get failed for resource %pR\n", res); ret = -ENODEV; goto failed; } ctx->rst = devm_reset_control_get(ctx->dev, "dvbm_rst"); if (IS_ERR_OR_NULL(ctx->rst)) { dev_err(dev, "clk_rst failed for resource %pR\n", res); ret = -ENODEV; goto failed; } if (!SOFT_DVBM) { ret = pm_runtime_get_sync(dev); if (ret) dev_err(dev, "pm get failed!\n"); ret = rk_dvbm_clk_on(ctx); if (ret) goto failed; } g_ctx = ctx; rk_dvbm_reg_init(ctx); ctx->ignore_ovfl = 1; ctx->dump_s = 0x80; ctx->dump_e = 0xb8; ret = devm_request_threaded_irq(dev, ctx->irq, rk_dvbm_irq, rk_dvbm_isr, IRQF_ONESHOT, dev_name(dev), ctx); if (ret) { dev_err(dev, "register interrupter failed\n"); goto failed; } dev_info(dev, "probe success\n"); return 0; failed: pm_runtime_disable(dev); return ret; } static int rk_dvbm_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; dev_info(dev, "remove device\n"); if (!SOFT_DVBM) { rk_dvbm_clk_off(g_ctx); pm_runtime_put(dev); } pm_runtime_disable(dev); return 0; } static const struct of_device_id rk_dvbm_dt_ids[] = { { .compatible = "rockchip,rk-dvbm", }, { }, }; static struct platform_driver rk_dvbm_driver = { .probe = rk_dvbm_probe, .remove = rk_dvbm_remove, .driver = { .name = "rk_dvbm", .of_match_table = of_match_ptr(rk_dvbm_dt_ids), }, }; static int __init rk_dvbm_init(void) { return platform_driver_register(&rk_dvbm_driver); } static __exit void rk_dvbm_exit(void) { platform_driver_unregister(&rk_dvbm_driver); } subsys_initcall(rk_dvbm_init); module_exit(rk_dvbm_exit); MODULE_LICENSE("Dual MIT/GPL"); MODULE_AUTHOR("Yandong Lin yandong.lin@rock-chips.com"); MODULE_DESCRIPTION("Rockchip dvbm driver");