blob: 45363a84339263cf2973cb13518d255ed5d54420 [file] [log] [blame] [edit]
/*
* 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");