blob: a41e6c7213ba4436c5bc9e560b0a22f662aed85c [file] [log] [blame]
/*
* Copyright 2018 NXP
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <video/imx8-pc.h>
#define REG0 0x0
#define PIX_COMBINE_ENABLE BIT(0)
#define DISP_PIX_COMBINE_BYPASS(n) BIT(1 + 21 * (n))
#define DISP_HSYNC_POLARITY(n) BIT(2 + 11 * (n))
#define DISP_HSYNC_POLARITY_POS(n) DISP_HSYNC_POLARITY(n)
#define DISP_VSYNC_POLARITY(n) BIT(3 + 11 * (n))
#define DISP_VSYNC_POLARITY_POS(n) DISP_VSYNC_POLARITY(n)
#define DISP_DVALID_POLARITY(n) BIT(4 + 11 * (n))
#define DISP_DVALID_POLARITY_POS(n) DISP_DVALID_POLARITY(n)
#define VSYNC_MASK_ENABLE BIT(5)
#define SKIP_MODE BIT(6)
#define SKIP_NUMBER(n) (((n) & 0x3F) << 7)
#define DISP_PIX_DATA_FORMAT_MASK(n) (0x7 << (16 + (n) * 3))
#define DISP_PIX_DATA_FORMAT_SHIFT(n) (16 + (n) * 3)
enum {
RGB = 0,
YUV444,
YUV422,
SPLIT_RGB,
};
#define REG1 0x10
#define BUF_ACTIVE_DEPTH(n) ((n) & 0x7FF)
#define REG2 0x20
#define PC_SW_RESET_N BIT(0)
#define DISP_SW_RESET_N(n) BIT(1 + (n))
#define PC_FULL_RESET_N (PC_SW_RESET_N | \
DISP_SW_RESET_N(0) | \
DISP_SW_RESET_N(1))
struct pc {
struct device *dev;
void __iomem *base;
struct list_head list;
};
static DEFINE_MUTEX(pc_list_mutex);
static LIST_HEAD(pc_list);
static inline u32 pc_read(struct pc *pc, unsigned int offset)
{
return readl(pc->base + offset);
}
static inline void pc_write(struct pc *pc, u32 value, unsigned int offset)
{
writel(value, pc->base + offset);
}
static void pc_reset(struct pc *pc)
{
pc_write(pc, 0, REG2);
usleep_range(1000, 2000);
pc_write(pc, PC_FULL_RESET_N, REG2);
}
void pc_enable(struct pc *pc)
{
u32 val;
if (WARN_ON(!pc))
return;
val = pc_read(pc, REG0);
val |= PIX_COMBINE_ENABLE;
pc_write(pc, val, REG0);
dev_dbg(pc->dev, "enable\n");
}
EXPORT_SYMBOL_GPL(pc_enable);
void pc_disable(struct pc *pc)
{
if (WARN_ON(!pc))
return;
pc_reset(pc);
dev_dbg(pc->dev, "disable\n");
}
EXPORT_SYMBOL_GPL(pc_disable);
void pc_configure(struct pc *pc, unsigned int di, unsigned int frame_width,
u32 mode, u32 format)
{
u32 val;
if (WARN_ON(!pc))
return;
if (WARN_ON(di != 0 && di != 1))
return;
dev_dbg(pc->dev, "configure mode-0x%08x frame_width-%u\n",
mode, frame_width);
val = pc_read(pc, REG0);
if (mode == PC_BYPASS) {
val |= DISP_PIX_COMBINE_BYPASS(di);
} else if (mode == PC_COMBINE) {
val &= ~DISP_PIX_COMBINE_BYPASS(di);
frame_width /= 4;
}
pc_write(pc, val, REG0);
pc_write(pc, BUF_ACTIVE_DEPTH(frame_width), REG1);
}
EXPORT_SYMBOL_GPL(pc_configure);
struct pc *pc_lookup_by_phandle(struct device *dev, const char *name)
{
struct device_node *pc_node = of_parse_phandle(dev->of_node,
name, 0);
struct pc *pc;
mutex_lock(&pc_list_mutex);
list_for_each_entry(pc, &pc_list, list) {
if (pc_node == pc->dev->of_node) {
mutex_unlock(&pc_list_mutex);
device_link_add(dev, pc->dev, DL_FLAG_AUTOREMOVE);
return pc;
}
}
mutex_unlock(&pc_list_mutex);
return NULL;
}
EXPORT_SYMBOL_GPL(pc_lookup_by_phandle);
static int pc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
struct pc *pc;
u32 val;
pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL);
if (!pc)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pc->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(pc->base))
return PTR_ERR(pc->base);
pc->dev = dev;
platform_set_drvdata(pdev, pc);
mutex_lock(&pc_list_mutex);
list_add(&pc->list, &pc_list);
mutex_unlock(&pc_list_mutex);
pc_reset(pc);
/*
* assume data enable is active high and HSYNC/VSYNC are active low
* also, bypass combine at startup
*/
val = DISP_DVALID_POLARITY_POS(0) | DISP_DVALID_POLARITY_POS(1) |
DISP_PIX_COMBINE_BYPASS(0) | DISP_PIX_COMBINE_BYPASS(1) |
VSYNC_MASK_ENABLE;
pc_write(pc, val, REG0);
return 0;
}
static int pc_remove(struct platform_device *pdev)
{
struct pc *pc = platform_get_drvdata(pdev);
mutex_lock(&pc_list_mutex);
list_del(&pc->list);
mutex_unlock(&pc_list_mutex);
return 0;
}
static const struct of_device_id pc_dt_ids[] = {
{ .compatible = "fsl,imx8qm-pixel-combiner", },
{ .compatible = "fsl,imx8qxp-pixel-combiner", },
{ /* sentinel */ },
};
struct platform_driver pc_drv = {
.probe = pc_probe,
.remove = pc_remove,
.driver = {
.name = "imx8-pixel-combiner",
.of_match_table = pc_dt_ids,
},
};
module_platform_driver(pc_drv);
MODULE_DESCRIPTION("i.MX8 Pixel Combiner driver");
MODULE_AUTHOR("NXP Semiconductor");
MODULE_LICENSE("GPL");