/* * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. * * The code contained herein is licensed under the GNU General Public * License. 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "prg-regs.h" #define PRG_CHAN_NUM 3 struct prg_chan { unsigned int pre_num; struct mutex mutex; /* for in_use */ bool in_use; }; struct ipu_prg_data { unsigned int id; void __iomem *base; unsigned long memory; struct clk *axi_clk; struct clk *apb_clk; struct list_head list; struct device *dev; struct prg_chan chan[PRG_CHAN_NUM]; struct regmap *regmap; struct regmap_field *pre_prg_sel[2]; spinlock_t lock; }; static LIST_HEAD(prg_list); static DEFINE_MUTEX(prg_lock); static inline void prg_write(struct ipu_prg_data *prg, u32 value, unsigned int offset) { writel(value, prg->base + offset); } static inline u32 prg_read(struct ipu_prg_data *prg, unsigned offset) { return readl(prg->base + offset); } static struct ipu_prg_data *get_prg(unsigned int ipu_id) { struct ipu_prg_data *prg; mutex_lock(&prg_lock); list_for_each_entry(prg, &prg_list, list) { if (prg->id == ipu_id) { mutex_unlock(&prg_lock); return prg; } } mutex_unlock(&prg_lock); return NULL; } static int assign_prg_chan(struct ipu_prg_data *prg, unsigned int pre_num, ipu_channel_t ipu_ch) { int prg_ch; if (!prg) return -EINVAL; switch (ipu_ch) { case MEM_BG_SYNC: prg_ch = 0; break; case MEM_FG_SYNC: prg_ch = 1; break; case MEM_DC_SYNC: prg_ch = 2; break; default: dev_err(prg->dev, "wrong ipu channel type\n"); return -EINVAL; } mutex_lock(&prg->chan[prg_ch].mutex); if (!prg->chan[prg_ch].in_use) { prg->chan[prg_ch].in_use = true; prg->chan[prg_ch].pre_num = pre_num; if (prg_ch != 0) { unsigned int pmux, psel; /* primary */ unsigned int smux, ssel; /* secondary */ struct regmap_field *pfield, *sfield; psel = pre_num - 1; ssel = psel ? 0 : 1; pfield = prg->pre_prg_sel[psel]; sfield = prg->pre_prg_sel[ssel]; pmux = (prg_ch - 1) + (prg->id << 1); mutex_lock(&prg_lock); regmap_field_write(pfield, pmux); /* * PRE1 and PRE2 cannot bind with a same channel of * one PRG even if one of the two PREs is disabled. */ regmap_field_read(sfield, &smux); if (smux == pmux) { smux = pmux ^ 0x1; regmap_field_write(sfield, smux); } mutex_unlock(&prg_lock); } mutex_unlock(&prg->chan[prg_ch].mutex); dev_dbg(prg->dev, "bind prg%u ch%d with pre%u\n", prg->id, prg_ch, pre_num); return prg_ch; } mutex_unlock(&prg->chan[prg_ch].mutex); return -EBUSY; } static inline int get_prg_chan(struct ipu_prg_data *prg, unsigned int pre_num) { int i; if (!prg) return -EINVAL; for (i = 0; i < PRG_CHAN_NUM; i++) { mutex_lock(&prg->chan[i].mutex); if (prg->chan[i].in_use && prg->chan[i].pre_num == pre_num) { mutex_unlock(&prg->chan[i].mutex); return i; } mutex_unlock(&prg->chan[i].mutex); } return -ENOENT; } int ipu_prg_config(struct ipu_prg_config *config) { struct ipu_prg_data *prg = get_prg(config->id); struct ipu_soc *ipu = ipu_get_soc(config->id); int prg_ch, axi_id; u32 reg; if (!prg || config->crop_line > 3 || !ipu) return -EINVAL; if (config->height & ~IPU_PR_CH_HEIGHT_MASK) return -EINVAL; prg_ch = assign_prg_chan(prg, config->pre_num, config->ipu_ch); if (prg_ch < 0) return prg_ch; axi_id = ipu_ch_param_get_axi_id(ipu, config->ipu_ch, IPU_INPUT_BUFFER); clk_prepare_enable(prg->axi_clk); clk_prepare_enable(prg->apb_clk); spin_lock(&prg->lock); /* clear all load enable to impact other channels */ reg = prg_read(prg, IPU_PR_CTRL); reg &= ~IPU_PR_CTRL_CH_CNT_LOAD_EN_MASK; prg_write(prg, reg, IPU_PR_CTRL); /* counter load enable */ reg = prg_read(prg, IPU_PR_CTRL); reg |= IPU_PR_CTRL_CH_CNT_LOAD_EN(prg_ch); prg_write(prg, reg, IPU_PR_CTRL); /* AXI ID */ reg = prg_read(prg, IPU_PR_CTRL); reg &= ~IPU_PR_CTRL_SOFT_CH_ARID_MASK(prg_ch); reg |= IPU_PR_CTRL_SOFT_CH_ARID(prg_ch, axi_id); prg_write(prg, reg, IPU_PR_CTRL); /* so */ reg = prg_read(prg, IPU_PR_CTRL); reg &= ~IPU_PR_CTRL_CH_SO_MASK(prg_ch); reg |= IPU_PR_CTRL_CH_SO(prg_ch, config->so); prg_write(prg, reg, IPU_PR_CTRL); /* vflip */ reg = prg_read(prg, IPU_PR_CTRL); reg &= ~IPU_PR_CTRL_CH_VFLIP_MASK(prg_ch); reg |= IPU_PR_CTRL_CH_VFLIP(prg_ch, config->vflip); prg_write(prg, reg, IPU_PR_CTRL); /* block mode */ reg = prg_read(prg, IPU_PR_CTRL); reg &= ~IPU_PR_CTRL_CH_BLOCK_MODE_MASK(prg_ch); reg |= IPU_PR_CTRL_CH_BLOCK_MODE(prg_ch, config->block_mode); prg_write(prg, reg, IPU_PR_CTRL); /* disable bypass */ reg = prg_read(prg, IPU_PR_CTRL); reg &= ~IPU_PR_CTRL_CH_BYPASS(prg_ch); prg_write(prg, reg, IPU_PR_CTRL); /* stride */ reg = prg_read(prg, IPU_PR_STRIDE(prg_ch)); reg &= ~IPU_PR_STRIDE_MASK; reg |= config->stride - 1; prg_write(prg, reg, IPU_PR_STRIDE(prg_ch)); /* ilo */ reg = prg_read(prg, IPU_PR_CH_ILO(prg_ch)); reg &= ~IPU_PR_CH_ILO_MASK; reg |= IPU_PR_CH_ILO_NUM(config->ilo); prg_write(prg, reg, IPU_PR_CH_ILO(prg_ch)); /* height */ reg = prg_read(prg, IPU_PR_CH_HEIGHT(prg_ch)); reg &= ~IPU_PR_CH_HEIGHT_MASK; reg |= IPU_PR_CH_HEIGHT_NUM(config->height); prg_write(prg, reg, IPU_PR_CH_HEIGHT(prg_ch)); /* ipu height */ reg = prg_read(prg, IPU_PR_CH_HEIGHT(prg_ch)); reg &= ~IPU_PR_CH_IPU_HEIGHT_MASK; reg |= IPU_PR_CH_IPU_HEIGHT_NUM(config->ipu_height); prg_write(prg, reg, IPU_PR_CH_HEIGHT(prg_ch)); /* crop */ reg = prg_read(prg, IPU_PR_CROP_LINE); reg &= ~IPU_PR_CROP_LINE_MASK(prg_ch); reg |= IPU_PR_CROP_LINE_NUM(prg_ch, config->crop_line); prg_write(prg, reg, IPU_PR_CROP_LINE); /* buffer address */ reg = prg_read(prg, IPU_PR_CH_BADDR(prg_ch)); reg &= ~IPU_PR_CH_BADDR_MASK; reg |= config->baddr; prg_write(prg, reg, IPU_PR_CH_BADDR(prg_ch)); /* offset */ reg = prg_read(prg, IPU_PR_CH_OFFSET(prg_ch)); reg &= ~IPU_PR_CH_OFFSET_MASK; reg |= config->offset; prg_write(prg, reg, IPU_PR_CH_OFFSET(prg_ch)); /* threshold */ reg = prg_read(prg, IPU_PR_ADDR_THD); reg &= ~IPU_PR_ADDR_THD_MASK; reg |= prg->memory; prg_write(prg, reg, IPU_PR_ADDR_THD); /* shadow enable */ reg = prg_read(prg, IPU_PR_CTRL); reg |= IPU_PR_CTRL_SHADOW_EN; prg_write(prg, reg, IPU_PR_CTRL); /* register update */ reg = prg_read(prg, IPU_PR_REG_UPDATE); reg |= IPU_PR_REG_UPDATE_EN; prg_write(prg, reg, IPU_PR_REG_UPDATE); spin_unlock(&prg->lock); clk_disable_unprepare(prg->apb_clk); return 0; } EXPORT_SYMBOL(ipu_prg_config); int ipu_prg_disable(unsigned int ipu_id, unsigned int pre_num) { struct ipu_prg_data *prg = get_prg(ipu_id); int prg_ch; u32 reg; if (!prg) return -EINVAL; prg_ch = get_prg_chan(prg, pre_num); if (prg_ch < 0) return prg_ch; clk_prepare_enable(prg->apb_clk); spin_lock(&prg->lock); /* clear all load enable to impact other channels */ reg = prg_read(prg, IPU_PR_CTRL); reg &= ~IPU_PR_CTRL_CH_CNT_LOAD_EN_MASK; prg_write(prg, reg, IPU_PR_CTRL); /* counter load enable */ reg = prg_read(prg, IPU_PR_CTRL); reg |= IPU_PR_CTRL_CH_CNT_LOAD_EN(prg_ch); prg_write(prg, reg, IPU_PR_CTRL); /* enable bypass */ reg = prg_read(prg, IPU_PR_CTRL); reg |= IPU_PR_CTRL_CH_BYPASS(prg_ch); prg_write(prg, reg, IPU_PR_CTRL); /* shadow enable */ reg = prg_read(prg, IPU_PR_CTRL); reg |= IPU_PR_CTRL_SHADOW_EN; prg_write(prg, reg, IPU_PR_CTRL); /* register update */ reg = prg_read(prg, IPU_PR_REG_UPDATE); reg |= IPU_PR_REG_UPDATE_EN; prg_write(prg, reg, IPU_PR_REG_UPDATE); spin_unlock(&prg->lock); clk_disable_unprepare(prg->apb_clk); clk_disable_unprepare(prg->axi_clk); mutex_lock(&prg->chan[prg_ch].mutex); prg->chan[prg_ch].in_use = false; mutex_unlock(&prg->chan[prg_ch].mutex); return 0; } EXPORT_SYMBOL(ipu_prg_disable); int ipu_prg_wait_buf_ready(unsigned int ipu_id, unsigned int pre_num, unsigned int hsk_line_num, int pre_store_out_height) { struct ipu_prg_data *prg = get_prg(ipu_id); int prg_ch, timeout = 1000; u32 reg; if (!prg) return -EINVAL; prg_ch = get_prg_chan(prg, pre_num); if (prg_ch < 0) return prg_ch; clk_prepare_enable(prg->apb_clk); spin_lock(&prg->lock); if (pre_store_out_height <= (4 << hsk_line_num)) { do { reg = prg_read(prg, IPU_PR_STATUS); udelay(1000); timeout--; } while (!(reg & IPU_PR_STATUS_BUF_RDY(prg_ch, 0)) && timeout); } else { do { reg = prg_read(prg, IPU_PR_STATUS); udelay(1000); timeout--; } while ((!(reg & IPU_PR_STATUS_BUF_RDY(prg_ch, 0)) || !(reg & IPU_PR_STATUS_BUF_RDY(prg_ch, 1))) && timeout); } spin_unlock(&prg->lock); clk_disable_unprepare(prg->apb_clk); if (!timeout) dev_err(prg->dev, "wait for buffer ready timeout\n"); return 0; } EXPORT_SYMBOL(ipu_prg_wait_buf_ready); static int ipu_prg_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node, *memory; struct ipu_prg_data *prg; struct resource *res; struct reg_field reg_field0 = REG_FIELD(IOMUXC_GPR5, IMX6Q_GPR5_PRE_PRG_SEL0_LSB, IMX6Q_GPR5_PRE_PRG_SEL0_MSB); struct reg_field reg_field1 = REG_FIELD(IOMUXC_GPR5, IMX6Q_GPR5_PRE_PRG_SEL1_LSB, IMX6Q_GPR5_PRE_PRG_SEL1_MSB); int id, i; prg = devm_kzalloc(&pdev->dev, sizeof(*prg), GFP_KERNEL); if (!prg) return -ENOMEM; prg->dev = &pdev->dev; for (i = 0; i < PRG_CHAN_NUM; i++) mutex_init(&prg->chan[i].mutex); spin_lock_init(&prg->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); prg->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(prg->base)) return PTR_ERR(prg->base); prg->axi_clk = devm_clk_get(&pdev->dev, "axi"); if (IS_ERR(prg->axi_clk)) { dev_err(&pdev->dev, "failed to get the axi clk\n"); return PTR_ERR(prg->axi_clk); } prg->apb_clk = devm_clk_get(&pdev->dev, "apb"); if (IS_ERR(prg->apb_clk)) { dev_err(&pdev->dev, "failed to get the apb clk\n"); return PTR_ERR(prg->apb_clk); } prg->regmap = syscon_regmap_lookup_by_phandle(np, "gpr"); if (IS_ERR(prg->regmap)) { dev_err(&pdev->dev, "failed to get regmap\n"); return PTR_ERR(prg->regmap); } prg->pre_prg_sel[0] = devm_regmap_field_alloc(&pdev->dev, prg->regmap, reg_field0); if (IS_ERR(prg->pre_prg_sel[0])) return PTR_ERR(prg->pre_prg_sel[0]); prg->pre_prg_sel[1] = devm_regmap_field_alloc(&pdev->dev, prg->regmap, reg_field1); if (IS_ERR(prg->pre_prg_sel[1])) return PTR_ERR(prg->pre_prg_sel[1]); memory = of_parse_phandle(np, "memory-region", 0); if (!memory) return -ENODEV; prg->memory = of_translate_address(memory, of_get_address(memory, 0, NULL, NULL)); id = of_alias_get_id(np, "prg"); if (id < 0) { dev_err(&pdev->dev, "failed to get PRG id\n"); return id; } prg->id = id; mutex_lock(&prg_lock); list_add_tail(&prg->list, &prg_list); mutex_unlock(&prg_lock); platform_set_drvdata(pdev, prg); dev_info(&pdev->dev, "driver probed\n"); return 0; } static int ipu_prg_remove(struct platform_device *pdev) { struct ipu_prg_data *prg = platform_get_drvdata(pdev); mutex_lock(&prg_lock); list_del(&prg->list); mutex_unlock(&prg_lock); return 0; } static const struct of_device_id imx_ipu_prg_dt_ids[] = { { .compatible = "fsl,imx6q-prg", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imx_ipu_prg_dt_ids); static struct platform_driver ipu_prg_driver = { .driver = { .name = "imx-prg", .of_match_table = of_match_ptr(imx_ipu_prg_dt_ids), }, .probe = ipu_prg_probe, .remove = ipu_prg_remove, }; static int __init ipu_prg_init(void) { return platform_driver_register(&ipu_prg_driver); } subsys_initcall(ipu_prg_init); static void __exit ipu_prg_exit(void) { platform_driver_unregister(&ipu_prg_driver); } module_exit(ipu_prg_exit); MODULE_DESCRIPTION("i.MX PRG driver"); MODULE_AUTHOR("Freescale Semiconductor, Inc."); MODULE_LICENSE("GPL");