162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2018 BayLibre, SAS
462306a36Sopenharmony_ci * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
562306a36Sopenharmony_ci * Copyright (C) 2014 Endless Mobile
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/platform_device.h>
1262306a36Sopenharmony_ci#include <linux/regmap.h>
1362306a36Sopenharmony_ci#include <linux/soc/amlogic/meson-canvas.h>
1462306a36Sopenharmony_ci#include <linux/of_address.h>
1562306a36Sopenharmony_ci#include <linux/of_platform.h>
1662306a36Sopenharmony_ci#include <linux/io.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define NUM_CANVAS 256
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/* DMC Registers */
2162306a36Sopenharmony_ci#define DMC_CAV_LUT_DATAL	0x00
2262306a36Sopenharmony_ci	#define CANVAS_WIDTH_LBIT	29
2362306a36Sopenharmony_ci	#define CANVAS_WIDTH_LWID	3
2462306a36Sopenharmony_ci#define DMC_CAV_LUT_DATAH	0x04
2562306a36Sopenharmony_ci	#define CANVAS_WIDTH_HBIT	0
2662306a36Sopenharmony_ci	#define CANVAS_HEIGHT_BIT	9
2762306a36Sopenharmony_ci	#define CANVAS_WRAP_BIT		22
2862306a36Sopenharmony_ci	#define CANVAS_BLKMODE_BIT	24
2962306a36Sopenharmony_ci	#define CANVAS_ENDIAN_BIT	26
3062306a36Sopenharmony_ci#define DMC_CAV_LUT_ADDR	0x08
3162306a36Sopenharmony_ci	#define CANVAS_LUT_WR_EN	BIT(9)
3262306a36Sopenharmony_ci	#define CANVAS_LUT_RD_EN	BIT(8)
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistruct meson_canvas {
3562306a36Sopenharmony_ci	struct device *dev;
3662306a36Sopenharmony_ci	void __iomem *reg_base;
3762306a36Sopenharmony_ci	spinlock_t lock; /* canvas device lock */
3862306a36Sopenharmony_ci	u8 used[NUM_CANVAS];
3962306a36Sopenharmony_ci	bool supports_endianness;
4062306a36Sopenharmony_ci};
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic void canvas_write(struct meson_canvas *canvas, u32 reg, u32 val)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	writel_relaxed(val, canvas->reg_base + reg);
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic u32 canvas_read(struct meson_canvas *canvas, u32 reg)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	return readl_relaxed(canvas->reg_base + reg);
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistruct meson_canvas *meson_canvas_get(struct device *dev)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	struct device_node *canvas_node;
5562306a36Sopenharmony_ci	struct platform_device *canvas_pdev;
5662306a36Sopenharmony_ci	struct meson_canvas *canvas;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	canvas_node = of_parse_phandle(dev->of_node, "amlogic,canvas", 0);
5962306a36Sopenharmony_ci	if (!canvas_node)
6062306a36Sopenharmony_ci		return ERR_PTR(-ENODEV);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	canvas_pdev = of_find_device_by_node(canvas_node);
6362306a36Sopenharmony_ci	if (!canvas_pdev) {
6462306a36Sopenharmony_ci		of_node_put(canvas_node);
6562306a36Sopenharmony_ci		return ERR_PTR(-EPROBE_DEFER);
6662306a36Sopenharmony_ci	}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	of_node_put(canvas_node);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	/*
7162306a36Sopenharmony_ci	 * If priv is NULL, it's probably because the canvas hasn't
7262306a36Sopenharmony_ci	 * properly initialized. Bail out with -EINVAL because, in the
7362306a36Sopenharmony_ci	 * current state, this driver probe cannot return -EPROBE_DEFER
7462306a36Sopenharmony_ci	 */
7562306a36Sopenharmony_ci	canvas = dev_get_drvdata(&canvas_pdev->dev);
7662306a36Sopenharmony_ci	if (!canvas) {
7762306a36Sopenharmony_ci		put_device(&canvas_pdev->dev);
7862306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return canvas;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_canvas_get);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ciint meson_canvas_config(struct meson_canvas *canvas, u8 canvas_index,
8662306a36Sopenharmony_ci			u32 addr, u32 stride, u32 height,
8762306a36Sopenharmony_ci			unsigned int wrap,
8862306a36Sopenharmony_ci			unsigned int blkmode,
8962306a36Sopenharmony_ci			unsigned int endian)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	unsigned long flags;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	if (endian && !canvas->supports_endianness) {
9462306a36Sopenharmony_ci		dev_err(canvas->dev,
9562306a36Sopenharmony_ci			"Endianness is not supported on this SoC\n");
9662306a36Sopenharmony_ci		return -EINVAL;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	spin_lock_irqsave(&canvas->lock, flags);
10062306a36Sopenharmony_ci	if (!canvas->used[canvas_index]) {
10162306a36Sopenharmony_ci		dev_err(canvas->dev,
10262306a36Sopenharmony_ci			"Trying to setup non allocated canvas %u\n",
10362306a36Sopenharmony_ci			canvas_index);
10462306a36Sopenharmony_ci		spin_unlock_irqrestore(&canvas->lock, flags);
10562306a36Sopenharmony_ci		return -EINVAL;
10662306a36Sopenharmony_ci	}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	canvas_write(canvas, DMC_CAV_LUT_DATAL,
10962306a36Sopenharmony_ci		     ((addr + 7) >> 3) |
11062306a36Sopenharmony_ci		     (((stride + 7) >> 3) << CANVAS_WIDTH_LBIT));
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	canvas_write(canvas, DMC_CAV_LUT_DATAH,
11362306a36Sopenharmony_ci		     ((((stride + 7) >> 3) >> CANVAS_WIDTH_LWID) <<
11462306a36Sopenharmony_ci						CANVAS_WIDTH_HBIT) |
11562306a36Sopenharmony_ci		     (height << CANVAS_HEIGHT_BIT) |
11662306a36Sopenharmony_ci		     (wrap << CANVAS_WRAP_BIT) |
11762306a36Sopenharmony_ci		     (blkmode << CANVAS_BLKMODE_BIT) |
11862306a36Sopenharmony_ci		     (endian << CANVAS_ENDIAN_BIT));
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	canvas_write(canvas, DMC_CAV_LUT_ADDR,
12162306a36Sopenharmony_ci		     CANVAS_LUT_WR_EN | canvas_index);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	/* Force a read-back to make sure everything is flushed. */
12462306a36Sopenharmony_ci	canvas_read(canvas, DMC_CAV_LUT_DATAH);
12562306a36Sopenharmony_ci	spin_unlock_irqrestore(&canvas->lock, flags);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	return 0;
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_canvas_config);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ciint meson_canvas_alloc(struct meson_canvas *canvas, u8 *canvas_index)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	int i;
13462306a36Sopenharmony_ci	unsigned long flags;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	spin_lock_irqsave(&canvas->lock, flags);
13762306a36Sopenharmony_ci	for (i = 0; i < NUM_CANVAS; ++i) {
13862306a36Sopenharmony_ci		if (!canvas->used[i]) {
13962306a36Sopenharmony_ci			canvas->used[i] = 1;
14062306a36Sopenharmony_ci			spin_unlock_irqrestore(&canvas->lock, flags);
14162306a36Sopenharmony_ci			*canvas_index = i;
14262306a36Sopenharmony_ci			return 0;
14362306a36Sopenharmony_ci		}
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_ci	spin_unlock_irqrestore(&canvas->lock, flags);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	dev_err(canvas->dev, "No more canvas available\n");
14862306a36Sopenharmony_ci	return -ENODEV;
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_canvas_alloc);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ciint meson_canvas_free(struct meson_canvas *canvas, u8 canvas_index)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	unsigned long flags;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	spin_lock_irqsave(&canvas->lock, flags);
15762306a36Sopenharmony_ci	if (!canvas->used[canvas_index]) {
15862306a36Sopenharmony_ci		dev_err(canvas->dev,
15962306a36Sopenharmony_ci			"Trying to free unused canvas %u\n", canvas_index);
16062306a36Sopenharmony_ci		spin_unlock_irqrestore(&canvas->lock, flags);
16162306a36Sopenharmony_ci		return -EINVAL;
16262306a36Sopenharmony_ci	}
16362306a36Sopenharmony_ci	canvas->used[canvas_index] = 0;
16462306a36Sopenharmony_ci	spin_unlock_irqrestore(&canvas->lock, flags);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	return 0;
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_canvas_free);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_cistatic int meson_canvas_probe(struct platform_device *pdev)
17162306a36Sopenharmony_ci{
17262306a36Sopenharmony_ci	struct meson_canvas *canvas;
17362306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	canvas = devm_kzalloc(dev, sizeof(*canvas), GFP_KERNEL);
17662306a36Sopenharmony_ci	if (!canvas)
17762306a36Sopenharmony_ci		return -ENOMEM;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	canvas->reg_base = devm_platform_ioremap_resource(pdev, 0);
18062306a36Sopenharmony_ci	if (IS_ERR(canvas->reg_base))
18162306a36Sopenharmony_ci		return PTR_ERR(canvas->reg_base);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	canvas->supports_endianness = of_device_get_match_data(dev);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	canvas->dev = dev;
18662306a36Sopenharmony_ci	spin_lock_init(&canvas->lock);
18762306a36Sopenharmony_ci	dev_set_drvdata(dev, canvas);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return 0;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic const struct of_device_id canvas_dt_match[] = {
19362306a36Sopenharmony_ci	{ .compatible = "amlogic,meson8-canvas", .data = (void *)false, },
19462306a36Sopenharmony_ci	{ .compatible = "amlogic,meson8b-canvas", .data = (void *)false, },
19562306a36Sopenharmony_ci	{ .compatible = "amlogic,meson8m2-canvas", .data = (void *)false, },
19662306a36Sopenharmony_ci	{ .compatible = "amlogic,canvas", .data = (void *)true, },
19762306a36Sopenharmony_ci	{}
19862306a36Sopenharmony_ci};
19962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, canvas_dt_match);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic struct platform_driver meson_canvas_driver = {
20262306a36Sopenharmony_ci	.probe = meson_canvas_probe,
20362306a36Sopenharmony_ci	.driver = {
20462306a36Sopenharmony_ci		.name = "amlogic-canvas",
20562306a36Sopenharmony_ci		.of_match_table = canvas_dt_match,
20662306a36Sopenharmony_ci	},
20762306a36Sopenharmony_ci};
20862306a36Sopenharmony_cimodule_platform_driver(meson_canvas_driver);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ciMODULE_DESCRIPTION("Amlogic Canvas driver");
21162306a36Sopenharmony_ciMODULE_AUTHOR("Maxime Jourdan <mjourdan@baylibre.com>");
21262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
213