162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2014 Free Electrons
462306a36Sopenharmony_ci * Copyright (C) 2014 Atmel
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/clk.h>
1062306a36Sopenharmony_ci#include <linux/iopoll.h>
1162306a36Sopenharmony_ci#include <linux/mfd/atmel-hlcdc.h>
1262306a36Sopenharmony_ci#include <linux/mfd/core.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/regmap.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define ATMEL_HLCDC_REG_MAX		(0x4000 - 0x4)
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistruct atmel_hlcdc_regmap {
2162306a36Sopenharmony_ci	void __iomem *regs;
2262306a36Sopenharmony_ci	struct device *dev;
2362306a36Sopenharmony_ci};
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic const struct mfd_cell atmel_hlcdc_cells[] = {
2662306a36Sopenharmony_ci	{
2762306a36Sopenharmony_ci		.name = "atmel-hlcdc-pwm",
2862306a36Sopenharmony_ci		.of_compatible = "atmel,hlcdc-pwm",
2962306a36Sopenharmony_ci	},
3062306a36Sopenharmony_ci	{
3162306a36Sopenharmony_ci		.name = "atmel-hlcdc-dc",
3262306a36Sopenharmony_ci		.of_compatible = "atmel,hlcdc-display-controller",
3362306a36Sopenharmony_ci	},
3462306a36Sopenharmony_ci};
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic int regmap_atmel_hlcdc_reg_write(void *context, unsigned int reg,
3762306a36Sopenharmony_ci					unsigned int val)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	struct atmel_hlcdc_regmap *hregmap = context;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	if (reg <= ATMEL_HLCDC_DIS) {
4262306a36Sopenharmony_ci		u32 status;
4362306a36Sopenharmony_ci		int ret;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci		ret = readl_poll_timeout_atomic(hregmap->regs + ATMEL_HLCDC_SR,
4662306a36Sopenharmony_ci						status,
4762306a36Sopenharmony_ci						!(status & ATMEL_HLCDC_SIP),
4862306a36Sopenharmony_ci						1, 100);
4962306a36Sopenharmony_ci		if (ret) {
5062306a36Sopenharmony_ci			dev_err(hregmap->dev,
5162306a36Sopenharmony_ci				"Timeout! Clock domain synchronization is in progress!\n");
5262306a36Sopenharmony_ci			return ret;
5362306a36Sopenharmony_ci		}
5462306a36Sopenharmony_ci	}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	writel(val, hregmap->regs + reg);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	return 0;
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic int regmap_atmel_hlcdc_reg_read(void *context, unsigned int reg,
6262306a36Sopenharmony_ci				       unsigned int *val)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	struct atmel_hlcdc_regmap *hregmap = context;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	*val = readl(hregmap->regs + reg);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	return 0;
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic const struct regmap_config atmel_hlcdc_regmap_config = {
7262306a36Sopenharmony_ci	.reg_bits = 32,
7362306a36Sopenharmony_ci	.val_bits = 32,
7462306a36Sopenharmony_ci	.reg_stride = 4,
7562306a36Sopenharmony_ci	.max_register = ATMEL_HLCDC_REG_MAX,
7662306a36Sopenharmony_ci	.reg_write = regmap_atmel_hlcdc_reg_write,
7762306a36Sopenharmony_ci	.reg_read = regmap_atmel_hlcdc_reg_read,
7862306a36Sopenharmony_ci	.fast_io = true,
7962306a36Sopenharmony_ci};
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic int atmel_hlcdc_probe(struct platform_device *pdev)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	struct atmel_hlcdc_regmap *hregmap;
8462306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
8562306a36Sopenharmony_ci	struct atmel_hlcdc *hlcdc;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	hregmap = devm_kzalloc(dev, sizeof(*hregmap), GFP_KERNEL);
8862306a36Sopenharmony_ci	if (!hregmap)
8962306a36Sopenharmony_ci		return -ENOMEM;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	hlcdc = devm_kzalloc(dev, sizeof(*hlcdc), GFP_KERNEL);
9262306a36Sopenharmony_ci	if (!hlcdc)
9362306a36Sopenharmony_ci		return -ENOMEM;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	hregmap->regs = devm_platform_ioremap_resource(pdev, 0);
9662306a36Sopenharmony_ci	if (IS_ERR(hregmap->regs))
9762306a36Sopenharmony_ci		return PTR_ERR(hregmap->regs);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	hregmap->dev = &pdev->dev;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	hlcdc->irq = platform_get_irq(pdev, 0);
10262306a36Sopenharmony_ci	if (hlcdc->irq < 0)
10362306a36Sopenharmony_ci		return hlcdc->irq;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	hlcdc->periph_clk = devm_clk_get(dev, "periph_clk");
10662306a36Sopenharmony_ci	if (IS_ERR(hlcdc->periph_clk)) {
10762306a36Sopenharmony_ci		dev_err(dev, "failed to get peripheral clock\n");
10862306a36Sopenharmony_ci		return PTR_ERR(hlcdc->periph_clk);
10962306a36Sopenharmony_ci	}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	hlcdc->sys_clk = devm_clk_get(dev, "sys_clk");
11262306a36Sopenharmony_ci	if (IS_ERR(hlcdc->sys_clk)) {
11362306a36Sopenharmony_ci		dev_err(dev, "failed to get system clock\n");
11462306a36Sopenharmony_ci		return PTR_ERR(hlcdc->sys_clk);
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	hlcdc->slow_clk = devm_clk_get(dev, "slow_clk");
11862306a36Sopenharmony_ci	if (IS_ERR(hlcdc->slow_clk)) {
11962306a36Sopenharmony_ci		dev_err(dev, "failed to get slow clock\n");
12062306a36Sopenharmony_ci		return PTR_ERR(hlcdc->slow_clk);
12162306a36Sopenharmony_ci	}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	hlcdc->regmap = devm_regmap_init(dev, NULL, hregmap,
12462306a36Sopenharmony_ci					 &atmel_hlcdc_regmap_config);
12562306a36Sopenharmony_ci	if (IS_ERR(hlcdc->regmap))
12662306a36Sopenharmony_ci		return PTR_ERR(hlcdc->regmap);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	dev_set_drvdata(dev, hlcdc);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	return devm_mfd_add_devices(dev, -1, atmel_hlcdc_cells,
13162306a36Sopenharmony_ci				    ARRAY_SIZE(atmel_hlcdc_cells),
13262306a36Sopenharmony_ci				    NULL, 0, NULL);
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic const struct of_device_id atmel_hlcdc_match[] = {
13662306a36Sopenharmony_ci	{ .compatible = "atmel,at91sam9n12-hlcdc" },
13762306a36Sopenharmony_ci	{ .compatible = "atmel,at91sam9x5-hlcdc" },
13862306a36Sopenharmony_ci	{ .compatible = "atmel,sama5d2-hlcdc" },
13962306a36Sopenharmony_ci	{ .compatible = "atmel,sama5d3-hlcdc" },
14062306a36Sopenharmony_ci	{ .compatible = "atmel,sama5d4-hlcdc" },
14162306a36Sopenharmony_ci	{ .compatible = "microchip,sam9x60-hlcdc" },
14262306a36Sopenharmony_ci	{ /* sentinel */ },
14362306a36Sopenharmony_ci};
14462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, atmel_hlcdc_match);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cistatic struct platform_driver atmel_hlcdc_driver = {
14762306a36Sopenharmony_ci	.probe = atmel_hlcdc_probe,
14862306a36Sopenharmony_ci	.driver = {
14962306a36Sopenharmony_ci		.name = "atmel-hlcdc",
15062306a36Sopenharmony_ci		.of_match_table = atmel_hlcdc_match,
15162306a36Sopenharmony_ci	},
15262306a36Sopenharmony_ci};
15362306a36Sopenharmony_cimodule_platform_driver(atmel_hlcdc_driver);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ciMODULE_ALIAS("platform:atmel-hlcdc");
15662306a36Sopenharmony_ciMODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
15762306a36Sopenharmony_ciMODULE_DESCRIPTION("Atmel HLCDC driver");
15862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
159