162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for the ps-mode pin configuration.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2021 Xilinx, Inc.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/delay.h>
962306a36Sopenharmony_ci#include <linux/err.h>
1062306a36Sopenharmony_ci#include <linux/gpio/driver.h>
1162306a36Sopenharmony_ci#include <linux/io.h>
1262306a36Sopenharmony_ci#include <linux/kernel.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/platform_device.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include <linux/firmware/xlnx-zynqmp.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/* 4-bit boot mode pins */
1962306a36Sopenharmony_ci#define MODE_PINS			4
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/**
2262306a36Sopenharmony_ci * modepin_gpio_get_value - Get the state of the specified pin of GPIO device
2362306a36Sopenharmony_ci * @chip:	gpio_chip instance to be worked on
2462306a36Sopenharmony_ci * @pin:	gpio pin number within the device
2562306a36Sopenharmony_ci *
2662306a36Sopenharmony_ci * This function reads the state of the specified pin of the GPIO device.
2762306a36Sopenharmony_ci *
2862306a36Sopenharmony_ci * Return: 0 if the pin is low, 1 if pin is high, -EINVAL wrong pin configured
2962306a36Sopenharmony_ci *         or error value.
3062306a36Sopenharmony_ci */
3162306a36Sopenharmony_cistatic int modepin_gpio_get_value(struct gpio_chip *chip, unsigned int pin)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	u32 regval = 0;
3462306a36Sopenharmony_ci	int ret;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	ret = zynqmp_pm_bootmode_read(&regval);
3762306a36Sopenharmony_ci	if (ret)
3862306a36Sopenharmony_ci		return ret;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	/* When [0:3] corresponding bit is set, then read output bit [8:11],
4162306a36Sopenharmony_ci	 * if the bit is clear then read input bit [4:7] for status or value.
4262306a36Sopenharmony_ci	 */
4362306a36Sopenharmony_ci	if (regval & BIT(pin))
4462306a36Sopenharmony_ci		return !!(regval & BIT(pin + 8));
4562306a36Sopenharmony_ci	else
4662306a36Sopenharmony_ci		return !!(regval & BIT(pin + 4));
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci/**
5062306a36Sopenharmony_ci * modepin_gpio_set_value - Modify the state of the pin with specified value
5162306a36Sopenharmony_ci * @chip:	gpio_chip instance to be worked on
5262306a36Sopenharmony_ci * @pin:	gpio pin number within the device
5362306a36Sopenharmony_ci * @state:	value used to modify the state of the specified pin
5462306a36Sopenharmony_ci *
5562306a36Sopenharmony_ci * This function reads the state of the specified pin of the GPIO device, mask
5662306a36Sopenharmony_ci * with the capture state of GPIO pin, and update pin of GPIO device.
5762306a36Sopenharmony_ci *
5862306a36Sopenharmony_ci * Return:	None.
5962306a36Sopenharmony_ci */
6062306a36Sopenharmony_cistatic void modepin_gpio_set_value(struct gpio_chip *chip, unsigned int pin,
6162306a36Sopenharmony_ci				   int state)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	u32 bootpin_val = 0;
6462306a36Sopenharmony_ci	int ret;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	zynqmp_pm_bootmode_read(&bootpin_val);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	/* Configure pin as an output by set bit [0:3] */
6962306a36Sopenharmony_ci	bootpin_val |= BIT(pin);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	if (state)
7262306a36Sopenharmony_ci		bootpin_val |= BIT(pin + 8);
7362306a36Sopenharmony_ci	else
7462306a36Sopenharmony_ci		bootpin_val &= ~BIT(pin + 8);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	/* Configure bootpin value */
7762306a36Sopenharmony_ci	ret = zynqmp_pm_bootmode_write(bootpin_val);
7862306a36Sopenharmony_ci	if (ret)
7962306a36Sopenharmony_ci		pr_err("modepin: set value error %d for pin %d\n", ret, pin);
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci/**
8362306a36Sopenharmony_ci * modepin_gpio_dir_in - Set the direction of the specified GPIO pin as input
8462306a36Sopenharmony_ci * @chip:	gpio_chip instance to be worked on
8562306a36Sopenharmony_ci * @pin:	gpio pin number within the device
8662306a36Sopenharmony_ci *
8762306a36Sopenharmony_ci * Return: 0 always
8862306a36Sopenharmony_ci */
8962306a36Sopenharmony_cistatic int modepin_gpio_dir_in(struct gpio_chip *chip, unsigned int pin)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	return 0;
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci/**
9562306a36Sopenharmony_ci * modepin_gpio_dir_out - Set the direction of the specified GPIO pin as output
9662306a36Sopenharmony_ci * @chip:	gpio_chip instance to be worked on
9762306a36Sopenharmony_ci * @pin:	gpio pin number within the device
9862306a36Sopenharmony_ci * @state:	value to be written to specified pin
9962306a36Sopenharmony_ci *
10062306a36Sopenharmony_ci * Return: 0 always
10162306a36Sopenharmony_ci */
10262306a36Sopenharmony_cistatic int modepin_gpio_dir_out(struct gpio_chip *chip, unsigned int pin,
10362306a36Sopenharmony_ci				int state)
10462306a36Sopenharmony_ci{
10562306a36Sopenharmony_ci	return 0;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/**
10962306a36Sopenharmony_ci * modepin_gpio_probe - Initialization method for modepin_gpio
11062306a36Sopenharmony_ci * @pdev:		platform device instance
11162306a36Sopenharmony_ci *
11262306a36Sopenharmony_ci * Return: 0 on success, negative error otherwise.
11362306a36Sopenharmony_ci */
11462306a36Sopenharmony_cistatic int modepin_gpio_probe(struct platform_device *pdev)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct gpio_chip *chip;
11762306a36Sopenharmony_ci	int status;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
12062306a36Sopenharmony_ci	if (!chip)
12162306a36Sopenharmony_ci		return -ENOMEM;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	platform_set_drvdata(pdev, chip);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	/* configure the gpio chip */
12662306a36Sopenharmony_ci	chip->base = -1;
12762306a36Sopenharmony_ci	chip->ngpio = MODE_PINS;
12862306a36Sopenharmony_ci	chip->owner = THIS_MODULE;
12962306a36Sopenharmony_ci	chip->parent = &pdev->dev;
13062306a36Sopenharmony_ci	chip->get = modepin_gpio_get_value;
13162306a36Sopenharmony_ci	chip->set = modepin_gpio_set_value;
13262306a36Sopenharmony_ci	chip->direction_input = modepin_gpio_dir_in;
13362306a36Sopenharmony_ci	chip->direction_output = modepin_gpio_dir_out;
13462306a36Sopenharmony_ci	chip->label = dev_name(&pdev->dev);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	/* modepin gpio registration */
13762306a36Sopenharmony_ci	status = devm_gpiochip_add_data(&pdev->dev, chip, chip);
13862306a36Sopenharmony_ci	if (status)
13962306a36Sopenharmony_ci		return dev_err_probe(&pdev->dev, status,
14062306a36Sopenharmony_ci			      "Failed to add GPIO chip\n");
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	return status;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic const struct of_device_id modepin_platform_id[] = {
14662306a36Sopenharmony_ci	{ .compatible = "xlnx,zynqmp-gpio-modepin", },
14762306a36Sopenharmony_ci	{ }
14862306a36Sopenharmony_ci};
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic struct platform_driver modepin_platform_driver = {
15162306a36Sopenharmony_ci	.driver = {
15262306a36Sopenharmony_ci		.name = "modepin-gpio",
15362306a36Sopenharmony_ci		.of_match_table = modepin_platform_id,
15462306a36Sopenharmony_ci	},
15562306a36Sopenharmony_ci	.probe = modepin_gpio_probe,
15662306a36Sopenharmony_ci};
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cimodule_platform_driver(modepin_platform_driver);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ciMODULE_AUTHOR("Piyush Mehta <piyush.mehta@xilinx.com>");
16162306a36Sopenharmony_ciMODULE_DESCRIPTION("ZynqMP Boot PS_MODE Configuration");
16262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
163