18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2018 BayLibre, SAS
48c2ecf20Sopenharmony_ci * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
58c2ecf20Sopenharmony_ci * Copyright (C) 2014 Endless Mobile
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/kernel.h>
98c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/regmap.h>
128c2ecf20Sopenharmony_ci#include <linux/soc/amlogic/meson-canvas.h>
138c2ecf20Sopenharmony_ci#include <linux/of_address.h>
148c2ecf20Sopenharmony_ci#include <linux/of_platform.h>
158c2ecf20Sopenharmony_ci#include <linux/io.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define NUM_CANVAS 256
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci/* DMC Registers */
208c2ecf20Sopenharmony_ci#define DMC_CAV_LUT_DATAL	0x00
218c2ecf20Sopenharmony_ci	#define CANVAS_WIDTH_LBIT	29
228c2ecf20Sopenharmony_ci	#define CANVAS_WIDTH_LWID	3
238c2ecf20Sopenharmony_ci#define DMC_CAV_LUT_DATAH	0x04
248c2ecf20Sopenharmony_ci	#define CANVAS_WIDTH_HBIT	0
258c2ecf20Sopenharmony_ci	#define CANVAS_HEIGHT_BIT	9
268c2ecf20Sopenharmony_ci	#define CANVAS_WRAP_BIT		22
278c2ecf20Sopenharmony_ci	#define CANVAS_BLKMODE_BIT	24
288c2ecf20Sopenharmony_ci	#define CANVAS_ENDIAN_BIT	26
298c2ecf20Sopenharmony_ci#define DMC_CAV_LUT_ADDR	0x08
308c2ecf20Sopenharmony_ci	#define CANVAS_LUT_WR_EN	BIT(9)
318c2ecf20Sopenharmony_ci	#define CANVAS_LUT_RD_EN	BIT(8)
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistruct meson_canvas {
348c2ecf20Sopenharmony_ci	struct device *dev;
358c2ecf20Sopenharmony_ci	void __iomem *reg_base;
368c2ecf20Sopenharmony_ci	spinlock_t lock; /* canvas device lock */
378c2ecf20Sopenharmony_ci	u8 used[NUM_CANVAS];
388c2ecf20Sopenharmony_ci	bool supports_endianness;
398c2ecf20Sopenharmony_ci};
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_cistatic void canvas_write(struct meson_canvas *canvas, u32 reg, u32 val)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	writel_relaxed(val, canvas->reg_base + reg);
448c2ecf20Sopenharmony_ci}
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic u32 canvas_read(struct meson_canvas *canvas, u32 reg)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	return readl_relaxed(canvas->reg_base + reg);
498c2ecf20Sopenharmony_ci}
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistruct meson_canvas *meson_canvas_get(struct device *dev)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	struct device_node *canvas_node;
548c2ecf20Sopenharmony_ci	struct platform_device *canvas_pdev;
558c2ecf20Sopenharmony_ci	struct meson_canvas *canvas;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	canvas_node = of_parse_phandle(dev->of_node, "amlogic,canvas", 0);
588c2ecf20Sopenharmony_ci	if (!canvas_node)
598c2ecf20Sopenharmony_ci		return ERR_PTR(-ENODEV);
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	canvas_pdev = of_find_device_by_node(canvas_node);
628c2ecf20Sopenharmony_ci	if (!canvas_pdev) {
638c2ecf20Sopenharmony_ci		of_node_put(canvas_node);
648c2ecf20Sopenharmony_ci		return ERR_PTR(-EPROBE_DEFER);
658c2ecf20Sopenharmony_ci	}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	of_node_put(canvas_node);
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	/*
708c2ecf20Sopenharmony_ci	 * If priv is NULL, it's probably because the canvas hasn't
718c2ecf20Sopenharmony_ci	 * properly initialized. Bail out with -EINVAL because, in the
728c2ecf20Sopenharmony_ci	 * current state, this driver probe cannot return -EPROBE_DEFER
738c2ecf20Sopenharmony_ci	 */
748c2ecf20Sopenharmony_ci	canvas = dev_get_drvdata(&canvas_pdev->dev);
758c2ecf20Sopenharmony_ci	if (!canvas) {
768c2ecf20Sopenharmony_ci		put_device(&canvas_pdev->dev);
778c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
788c2ecf20Sopenharmony_ci	}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	return canvas;
818c2ecf20Sopenharmony_ci}
828c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_canvas_get);
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ciint meson_canvas_config(struct meson_canvas *canvas, u8 canvas_index,
858c2ecf20Sopenharmony_ci			u32 addr, u32 stride, u32 height,
868c2ecf20Sopenharmony_ci			unsigned int wrap,
878c2ecf20Sopenharmony_ci			unsigned int blkmode,
888c2ecf20Sopenharmony_ci			unsigned int endian)
898c2ecf20Sopenharmony_ci{
908c2ecf20Sopenharmony_ci	unsigned long flags;
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	if (endian && !canvas->supports_endianness) {
938c2ecf20Sopenharmony_ci		dev_err(canvas->dev,
948c2ecf20Sopenharmony_ci			"Endianness is not supported on this SoC\n");
958c2ecf20Sopenharmony_ci		return -EINVAL;
968c2ecf20Sopenharmony_ci	}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	spin_lock_irqsave(&canvas->lock, flags);
998c2ecf20Sopenharmony_ci	if (!canvas->used[canvas_index]) {
1008c2ecf20Sopenharmony_ci		dev_err(canvas->dev,
1018c2ecf20Sopenharmony_ci			"Trying to setup non allocated canvas %u\n",
1028c2ecf20Sopenharmony_ci			canvas_index);
1038c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&canvas->lock, flags);
1048c2ecf20Sopenharmony_ci		return -EINVAL;
1058c2ecf20Sopenharmony_ci	}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	canvas_write(canvas, DMC_CAV_LUT_DATAL,
1088c2ecf20Sopenharmony_ci		     ((addr + 7) >> 3) |
1098c2ecf20Sopenharmony_ci		     (((stride + 7) >> 3) << CANVAS_WIDTH_LBIT));
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	canvas_write(canvas, DMC_CAV_LUT_DATAH,
1128c2ecf20Sopenharmony_ci		     ((((stride + 7) >> 3) >> CANVAS_WIDTH_LWID) <<
1138c2ecf20Sopenharmony_ci						CANVAS_WIDTH_HBIT) |
1148c2ecf20Sopenharmony_ci		     (height << CANVAS_HEIGHT_BIT) |
1158c2ecf20Sopenharmony_ci		     (wrap << CANVAS_WRAP_BIT) |
1168c2ecf20Sopenharmony_ci		     (blkmode << CANVAS_BLKMODE_BIT) |
1178c2ecf20Sopenharmony_ci		     (endian << CANVAS_ENDIAN_BIT));
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	canvas_write(canvas, DMC_CAV_LUT_ADDR,
1208c2ecf20Sopenharmony_ci		     CANVAS_LUT_WR_EN | canvas_index);
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	/* Force a read-back to make sure everything is flushed. */
1238c2ecf20Sopenharmony_ci	canvas_read(canvas, DMC_CAV_LUT_DATAH);
1248c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&canvas->lock, flags);
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	return 0;
1278c2ecf20Sopenharmony_ci}
1288c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_canvas_config);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ciint meson_canvas_alloc(struct meson_canvas *canvas, u8 *canvas_index)
1318c2ecf20Sopenharmony_ci{
1328c2ecf20Sopenharmony_ci	int i;
1338c2ecf20Sopenharmony_ci	unsigned long flags;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	spin_lock_irqsave(&canvas->lock, flags);
1368c2ecf20Sopenharmony_ci	for (i = 0; i < NUM_CANVAS; ++i) {
1378c2ecf20Sopenharmony_ci		if (!canvas->used[i]) {
1388c2ecf20Sopenharmony_ci			canvas->used[i] = 1;
1398c2ecf20Sopenharmony_ci			spin_unlock_irqrestore(&canvas->lock, flags);
1408c2ecf20Sopenharmony_ci			*canvas_index = i;
1418c2ecf20Sopenharmony_ci			return 0;
1428c2ecf20Sopenharmony_ci		}
1438c2ecf20Sopenharmony_ci	}
1448c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&canvas->lock, flags);
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	dev_err(canvas->dev, "No more canvas available\n");
1478c2ecf20Sopenharmony_ci	return -ENODEV;
1488c2ecf20Sopenharmony_ci}
1498c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_canvas_alloc);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ciint meson_canvas_free(struct meson_canvas *canvas, u8 canvas_index)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	unsigned long flags;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	spin_lock_irqsave(&canvas->lock, flags);
1568c2ecf20Sopenharmony_ci	if (!canvas->used[canvas_index]) {
1578c2ecf20Sopenharmony_ci		dev_err(canvas->dev,
1588c2ecf20Sopenharmony_ci			"Trying to free unused canvas %u\n", canvas_index);
1598c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&canvas->lock, flags);
1608c2ecf20Sopenharmony_ci		return -EINVAL;
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci	canvas->used[canvas_index] = 0;
1638c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&canvas->lock, flags);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	return 0;
1668c2ecf20Sopenharmony_ci}
1678c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_canvas_free);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_cistatic int meson_canvas_probe(struct platform_device *pdev)
1708c2ecf20Sopenharmony_ci{
1718c2ecf20Sopenharmony_ci	struct resource *res;
1728c2ecf20Sopenharmony_ci	struct meson_canvas *canvas;
1738c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	canvas = devm_kzalloc(dev, sizeof(*canvas), GFP_KERNEL);
1768c2ecf20Sopenharmony_ci	if (!canvas)
1778c2ecf20Sopenharmony_ci		return -ENOMEM;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1808c2ecf20Sopenharmony_ci	canvas->reg_base = devm_ioremap_resource(dev, res);
1818c2ecf20Sopenharmony_ci	if (IS_ERR(canvas->reg_base))
1828c2ecf20Sopenharmony_ci		return PTR_ERR(canvas->reg_base);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	canvas->supports_endianness = of_device_get_match_data(dev);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	canvas->dev = dev;
1878c2ecf20Sopenharmony_ci	spin_lock_init(&canvas->lock);
1888c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, canvas);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	return 0;
1918c2ecf20Sopenharmony_ci}
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_cistatic const struct of_device_id canvas_dt_match[] = {
1948c2ecf20Sopenharmony_ci	{ .compatible = "amlogic,meson8-canvas", .data = (void *)false, },
1958c2ecf20Sopenharmony_ci	{ .compatible = "amlogic,meson8b-canvas", .data = (void *)false, },
1968c2ecf20Sopenharmony_ci	{ .compatible = "amlogic,meson8m2-canvas", .data = (void *)false, },
1978c2ecf20Sopenharmony_ci	{ .compatible = "amlogic,canvas", .data = (void *)true, },
1988c2ecf20Sopenharmony_ci	{}
1998c2ecf20Sopenharmony_ci};
2008c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, canvas_dt_match);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_cistatic struct platform_driver meson_canvas_driver = {
2038c2ecf20Sopenharmony_ci	.probe = meson_canvas_probe,
2048c2ecf20Sopenharmony_ci	.driver = {
2058c2ecf20Sopenharmony_ci		.name = "amlogic-canvas",
2068c2ecf20Sopenharmony_ci		.of_match_table = canvas_dt_match,
2078c2ecf20Sopenharmony_ci	},
2088c2ecf20Sopenharmony_ci};
2098c2ecf20Sopenharmony_cimodule_platform_driver(meson_canvas_driver);
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Amlogic Canvas driver");
2128c2ecf20Sopenharmony_ciMODULE_AUTHOR("Maxime Jourdan <mjourdan@baylibre.com>");
2138c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
214