162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci *  Copyright (C) 2012 John Crispin <john@phrozen.org>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/init.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/types.h>
1062306a36Sopenharmony_ci#include <linux/platform_device.h>
1162306a36Sopenharmony_ci#include <linux/mutex.h>
1262306a36Sopenharmony_ci#include <linux/gpio/driver.h>
1362306a36Sopenharmony_ci#include <linux/gpio/legacy-of-mm-gpiochip.h>
1462306a36Sopenharmony_ci#include <linux/of.h>
1562306a36Sopenharmony_ci#include <linux/io.h>
1662306a36Sopenharmony_ci#include <linux/slab.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include <lantiq_soc.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/*
2162306a36Sopenharmony_ci * By attaching hardware latches to the EBU it is possible to create output
2262306a36Sopenharmony_ci * only gpios. This driver configures a special memory address, which when
2362306a36Sopenharmony_ci * written to outputs 16 bit to the latches.
2462306a36Sopenharmony_ci */
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define LTQ_EBU_BUSCON	0x1e7ff		/* 16 bit access, slowest timing */
2762306a36Sopenharmony_ci#define LTQ_EBU_WP	0x80000000	/* write protect bit */
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistruct ltq_mm {
3062306a36Sopenharmony_ci	struct of_mm_gpio_chip mmchip;
3162306a36Sopenharmony_ci	u16 shadow;	/* shadow the latches state */
3262306a36Sopenharmony_ci};
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/**
3562306a36Sopenharmony_ci * ltq_mm_apply() - write the shadow value to the ebu address.
3662306a36Sopenharmony_ci * @chip:     Pointer to our private data structure.
3762306a36Sopenharmony_ci *
3862306a36Sopenharmony_ci * Write the shadow value to the EBU to set the gpios. We need to set the
3962306a36Sopenharmony_ci * global EBU lock to make sure that PCI/MTD don't break.
4062306a36Sopenharmony_ci */
4162306a36Sopenharmony_cistatic void ltq_mm_apply(struct ltq_mm *chip)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	unsigned long flags;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	spin_lock_irqsave(&ebu_lock, flags);
4662306a36Sopenharmony_ci	ltq_ebu_w32(LTQ_EBU_BUSCON, LTQ_EBU_BUSCON1);
4762306a36Sopenharmony_ci	__raw_writew(chip->shadow, chip->mmchip.regs);
4862306a36Sopenharmony_ci	ltq_ebu_w32(LTQ_EBU_BUSCON | LTQ_EBU_WP, LTQ_EBU_BUSCON1);
4962306a36Sopenharmony_ci	spin_unlock_irqrestore(&ebu_lock, flags);
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci/**
5362306a36Sopenharmony_ci * ltq_mm_set() - gpio_chip->set - set gpios.
5462306a36Sopenharmony_ci * @gc:     Pointer to gpio_chip device structure.
5562306a36Sopenharmony_ci * @gpio:   GPIO signal number.
5662306a36Sopenharmony_ci * @val:    Value to be written to specified signal.
5762306a36Sopenharmony_ci *
5862306a36Sopenharmony_ci * Set the shadow value and call ltq_mm_apply.
5962306a36Sopenharmony_ci */
6062306a36Sopenharmony_cistatic void ltq_mm_set(struct gpio_chip *gc, unsigned offset, int value)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct ltq_mm *chip = gpiochip_get_data(gc);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	if (value)
6562306a36Sopenharmony_ci		chip->shadow |= (1 << offset);
6662306a36Sopenharmony_ci	else
6762306a36Sopenharmony_ci		chip->shadow &= ~(1 << offset);
6862306a36Sopenharmony_ci	ltq_mm_apply(chip);
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci/**
7262306a36Sopenharmony_ci * ltq_mm_dir_out() - gpio_chip->dir_out - set gpio direction.
7362306a36Sopenharmony_ci * @gc:     Pointer to gpio_chip device structure.
7462306a36Sopenharmony_ci * @gpio:   GPIO signal number.
7562306a36Sopenharmony_ci * @val:    Value to be written to specified signal.
7662306a36Sopenharmony_ci *
7762306a36Sopenharmony_ci * Same as ltq_mm_set, always returns 0.
7862306a36Sopenharmony_ci */
7962306a36Sopenharmony_cistatic int ltq_mm_dir_out(struct gpio_chip *gc, unsigned offset, int value)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	ltq_mm_set(gc, offset, value);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	return 0;
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci/**
8762306a36Sopenharmony_ci * ltq_mm_save_regs() - Set initial values of GPIO pins
8862306a36Sopenharmony_ci * @mm_gc: pointer to memory mapped GPIO chip structure
8962306a36Sopenharmony_ci */
9062306a36Sopenharmony_cistatic void ltq_mm_save_regs(struct of_mm_gpio_chip *mm_gc)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	struct ltq_mm *chip =
9362306a36Sopenharmony_ci		container_of(mm_gc, struct ltq_mm, mmchip);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	/* tell the ebu controller which memory address we will be using */
9662306a36Sopenharmony_ci	ltq_ebu_w32(CPHYSADDR(chip->mmchip.regs) | 0x1, LTQ_EBU_ADDRSEL1);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	ltq_mm_apply(chip);
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic int ltq_mm_probe(struct platform_device *pdev)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	struct ltq_mm *chip;
10462306a36Sopenharmony_ci	u32 shadow;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
10762306a36Sopenharmony_ci	if (!chip)
10862306a36Sopenharmony_ci		return -ENOMEM;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	platform_set_drvdata(pdev, chip);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	chip->mmchip.gc.ngpio = 16;
11362306a36Sopenharmony_ci	chip->mmchip.gc.direction_output = ltq_mm_dir_out;
11462306a36Sopenharmony_ci	chip->mmchip.gc.set = ltq_mm_set;
11562306a36Sopenharmony_ci	chip->mmchip.save_regs = ltq_mm_save_regs;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	/* store the shadow value if one was passed by the devicetree */
11862306a36Sopenharmony_ci	if (!of_property_read_u32(pdev->dev.of_node, "lantiq,shadow", &shadow))
11962306a36Sopenharmony_ci		chip->shadow = shadow;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	return of_mm_gpiochip_add_data(pdev->dev.of_node, &chip->mmchip, chip);
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic int ltq_mm_remove(struct platform_device *pdev)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	struct ltq_mm *chip = platform_get_drvdata(pdev);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	of_mm_gpiochip_remove(&chip->mmchip);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	return 0;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic const struct of_device_id ltq_mm_match[] = {
13462306a36Sopenharmony_ci	{ .compatible = "lantiq,gpio-mm" },
13562306a36Sopenharmony_ci	{},
13662306a36Sopenharmony_ci};
13762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ltq_mm_match);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic struct platform_driver ltq_mm_driver = {
14062306a36Sopenharmony_ci	.probe = ltq_mm_probe,
14162306a36Sopenharmony_ci	.remove = ltq_mm_remove,
14262306a36Sopenharmony_ci	.driver = {
14362306a36Sopenharmony_ci		.name = "gpio-mm-ltq",
14462306a36Sopenharmony_ci		.of_match_table = ltq_mm_match,
14562306a36Sopenharmony_ci	},
14662306a36Sopenharmony_ci};
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic int __init ltq_mm_init(void)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	return platform_driver_register(&ltq_mm_driver);
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cisubsys_initcall(ltq_mm_init);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic void __exit ltq_mm_exit(void)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	platform_driver_unregister(&ltq_mm_driver);
15862306a36Sopenharmony_ci}
15962306a36Sopenharmony_cimodule_exit(ltq_mm_exit);
160