imx8_pc.c 4.97 KB
/*
 * 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");