diff options
Diffstat (limited to 'drivers/mxc/ipu3/prg.c')
-rw-r--r-- | drivers/mxc/ipu3/prg.c | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/drivers/mxc/ipu3/prg.c b/drivers/mxc/ipu3/prg.c new file mode 100644 index 000000000000..45363a843392 --- /dev/null +++ b/drivers/mxc/ipu3/prg.c @@ -0,0 +1,506 @@ +/* + * 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 <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ipu-v3-prg.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#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"); |