18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * SPEAr platform SPI chipselect abstraction over gpiolib
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2012 ST Microelectronics
58c2ecf20Sopenharmony_ci * Shiraz Hashim <shiraz.linux.kernel@gmail.com>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public
88c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any
98c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied.
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/err.h>
138c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h>
148c2ecf20Sopenharmony_ci#include <linux/io.h>
158c2ecf20Sopenharmony_ci#include <linux/init.h>
168c2ecf20Sopenharmony_ci#include <linux/of.h>
178c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
188c2ecf20Sopenharmony_ci#include <linux/types.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci/* maximum chipselects */
218c2ecf20Sopenharmony_ci#define NUM_OF_GPIO	4
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci/*
248c2ecf20Sopenharmony_ci * Provision is available on some SPEAr SoCs to control ARM PL022 spi cs
258c2ecf20Sopenharmony_ci * through system registers. This register lies outside spi (pl022)
268c2ecf20Sopenharmony_ci * address space into system registers.
278c2ecf20Sopenharmony_ci *
288c2ecf20Sopenharmony_ci * It provides control for spi chip select lines so that any chipselect
298c2ecf20Sopenharmony_ci * (out of 4 possible chipselects in pl022) can be made low to select
308c2ecf20Sopenharmony_ci * the particular slave.
318c2ecf20Sopenharmony_ci */
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci/**
348c2ecf20Sopenharmony_ci * struct spear_spics - represents spi chip select control
358c2ecf20Sopenharmony_ci * @base: base address
368c2ecf20Sopenharmony_ci * @perip_cfg: configuration register
378c2ecf20Sopenharmony_ci * @sw_enable_bit: bit to enable s/w control over chipselects
388c2ecf20Sopenharmony_ci * @cs_value_bit: bit to program high or low chipselect
398c2ecf20Sopenharmony_ci * @cs_enable_mask: mask to select bits required to select chipselect
408c2ecf20Sopenharmony_ci * @cs_enable_shift: bit pos of cs_enable_mask
418c2ecf20Sopenharmony_ci * @use_count: use count of a spi controller cs lines
428c2ecf20Sopenharmony_ci * @last_off: stores last offset caller of set_value()
438c2ecf20Sopenharmony_ci * @chip: gpio_chip abstraction
448c2ecf20Sopenharmony_ci */
458c2ecf20Sopenharmony_cistruct spear_spics {
468c2ecf20Sopenharmony_ci	void __iomem		*base;
478c2ecf20Sopenharmony_ci	u32			perip_cfg;
488c2ecf20Sopenharmony_ci	u32			sw_enable_bit;
498c2ecf20Sopenharmony_ci	u32			cs_value_bit;
508c2ecf20Sopenharmony_ci	u32			cs_enable_mask;
518c2ecf20Sopenharmony_ci	u32			cs_enable_shift;
528c2ecf20Sopenharmony_ci	unsigned long		use_count;
538c2ecf20Sopenharmony_ci	int			last_off;
548c2ecf20Sopenharmony_ci	struct gpio_chip	chip;
558c2ecf20Sopenharmony_ci};
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci/* gpio framework specific routines */
588c2ecf20Sopenharmony_cistatic int spics_get_value(struct gpio_chip *chip, unsigned offset)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	return -ENXIO;
618c2ecf20Sopenharmony_ci}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistatic void spics_set_value(struct gpio_chip *chip, unsigned offset, int value)
648c2ecf20Sopenharmony_ci{
658c2ecf20Sopenharmony_ci	struct spear_spics *spics = gpiochip_get_data(chip);
668c2ecf20Sopenharmony_ci	u32 tmp;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	/* select chip select from register */
698c2ecf20Sopenharmony_ci	tmp = readl_relaxed(spics->base + spics->perip_cfg);
708c2ecf20Sopenharmony_ci	if (spics->last_off != offset) {
718c2ecf20Sopenharmony_ci		spics->last_off = offset;
728c2ecf20Sopenharmony_ci		tmp &= ~(spics->cs_enable_mask << spics->cs_enable_shift);
738c2ecf20Sopenharmony_ci		tmp |= offset << spics->cs_enable_shift;
748c2ecf20Sopenharmony_ci	}
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	/* toggle chip select line */
778c2ecf20Sopenharmony_ci	tmp &= ~(0x1 << spics->cs_value_bit);
788c2ecf20Sopenharmony_ci	tmp |= value << spics->cs_value_bit;
798c2ecf20Sopenharmony_ci	writel_relaxed(tmp, spics->base + spics->perip_cfg);
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic int spics_direction_input(struct gpio_chip *chip, unsigned offset)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	return -ENXIO;
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic int spics_direction_output(struct gpio_chip *chip, unsigned offset,
888c2ecf20Sopenharmony_ci		int value)
898c2ecf20Sopenharmony_ci{
908c2ecf20Sopenharmony_ci	spics_set_value(chip, offset, value);
918c2ecf20Sopenharmony_ci	return 0;
928c2ecf20Sopenharmony_ci}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistatic int spics_request(struct gpio_chip *chip, unsigned offset)
958c2ecf20Sopenharmony_ci{
968c2ecf20Sopenharmony_ci	struct spear_spics *spics = gpiochip_get_data(chip);
978c2ecf20Sopenharmony_ci	u32 tmp;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	if (!spics->use_count++) {
1008c2ecf20Sopenharmony_ci		tmp = readl_relaxed(spics->base + spics->perip_cfg);
1018c2ecf20Sopenharmony_ci		tmp |= 0x1 << spics->sw_enable_bit;
1028c2ecf20Sopenharmony_ci		tmp |= 0x1 << spics->cs_value_bit;
1038c2ecf20Sopenharmony_ci		writel_relaxed(tmp, spics->base + spics->perip_cfg);
1048c2ecf20Sopenharmony_ci	}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	return 0;
1078c2ecf20Sopenharmony_ci}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic void spics_free(struct gpio_chip *chip, unsigned offset)
1108c2ecf20Sopenharmony_ci{
1118c2ecf20Sopenharmony_ci	struct spear_spics *spics = gpiochip_get_data(chip);
1128c2ecf20Sopenharmony_ci	u32 tmp;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	if (!--spics->use_count) {
1158c2ecf20Sopenharmony_ci		tmp = readl_relaxed(spics->base + spics->perip_cfg);
1168c2ecf20Sopenharmony_ci		tmp &= ~(0x1 << spics->sw_enable_bit);
1178c2ecf20Sopenharmony_ci		writel_relaxed(tmp, spics->base + spics->perip_cfg);
1188c2ecf20Sopenharmony_ci	}
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic int spics_gpio_probe(struct platform_device *pdev)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
1248c2ecf20Sopenharmony_ci	struct spear_spics *spics;
1258c2ecf20Sopenharmony_ci	int ret;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	spics = devm_kzalloc(&pdev->dev, sizeof(*spics), GFP_KERNEL);
1288c2ecf20Sopenharmony_ci	if (!spics)
1298c2ecf20Sopenharmony_ci		return -ENOMEM;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	spics->base = devm_platform_ioremap_resource(pdev, 0);
1328c2ecf20Sopenharmony_ci	if (IS_ERR(spics->base))
1338c2ecf20Sopenharmony_ci		return PTR_ERR(spics->base);
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	if (of_property_read_u32(np, "st-spics,peripcfg-reg",
1368c2ecf20Sopenharmony_ci				&spics->perip_cfg))
1378c2ecf20Sopenharmony_ci		goto err_dt_data;
1388c2ecf20Sopenharmony_ci	if (of_property_read_u32(np, "st-spics,sw-enable-bit",
1398c2ecf20Sopenharmony_ci				&spics->sw_enable_bit))
1408c2ecf20Sopenharmony_ci		goto err_dt_data;
1418c2ecf20Sopenharmony_ci	if (of_property_read_u32(np, "st-spics,cs-value-bit",
1428c2ecf20Sopenharmony_ci				&spics->cs_value_bit))
1438c2ecf20Sopenharmony_ci		goto err_dt_data;
1448c2ecf20Sopenharmony_ci	if (of_property_read_u32(np, "st-spics,cs-enable-mask",
1458c2ecf20Sopenharmony_ci				&spics->cs_enable_mask))
1468c2ecf20Sopenharmony_ci		goto err_dt_data;
1478c2ecf20Sopenharmony_ci	if (of_property_read_u32(np, "st-spics,cs-enable-shift",
1488c2ecf20Sopenharmony_ci				&spics->cs_enable_shift))
1498c2ecf20Sopenharmony_ci		goto err_dt_data;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, spics);
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	spics->chip.ngpio = NUM_OF_GPIO;
1548c2ecf20Sopenharmony_ci	spics->chip.base = -1;
1558c2ecf20Sopenharmony_ci	spics->chip.request = spics_request;
1568c2ecf20Sopenharmony_ci	spics->chip.free = spics_free;
1578c2ecf20Sopenharmony_ci	spics->chip.direction_input = spics_direction_input;
1588c2ecf20Sopenharmony_ci	spics->chip.direction_output = spics_direction_output;
1598c2ecf20Sopenharmony_ci	spics->chip.get = spics_get_value;
1608c2ecf20Sopenharmony_ci	spics->chip.set = spics_set_value;
1618c2ecf20Sopenharmony_ci	spics->chip.label = dev_name(&pdev->dev);
1628c2ecf20Sopenharmony_ci	spics->chip.parent = &pdev->dev;
1638c2ecf20Sopenharmony_ci	spics->chip.owner = THIS_MODULE;
1648c2ecf20Sopenharmony_ci	spics->last_off = -1;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	ret = devm_gpiochip_add_data(&pdev->dev, &spics->chip, spics);
1678c2ecf20Sopenharmony_ci	if (ret) {
1688c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "unable to add gpio chip\n");
1698c2ecf20Sopenharmony_ci		return ret;
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	dev_info(&pdev->dev, "spear spics registered\n");
1738c2ecf20Sopenharmony_ci	return 0;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_cierr_dt_data:
1768c2ecf20Sopenharmony_ci	dev_err(&pdev->dev, "DT probe failed\n");
1778c2ecf20Sopenharmony_ci	return -EINVAL;
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic const struct of_device_id spics_gpio_of_match[] = {
1818c2ecf20Sopenharmony_ci	{ .compatible = "st,spear-spics-gpio" },
1828c2ecf20Sopenharmony_ci	{}
1838c2ecf20Sopenharmony_ci};
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_cistatic struct platform_driver spics_gpio_driver = {
1868c2ecf20Sopenharmony_ci	.probe = spics_gpio_probe,
1878c2ecf20Sopenharmony_ci	.driver = {
1888c2ecf20Sopenharmony_ci		.name = "spear-spics-gpio",
1898c2ecf20Sopenharmony_ci		.of_match_table = spics_gpio_of_match,
1908c2ecf20Sopenharmony_ci	},
1918c2ecf20Sopenharmony_ci};
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_cistatic int __init spics_gpio_init(void)
1948c2ecf20Sopenharmony_ci{
1958c2ecf20Sopenharmony_ci	return platform_driver_register(&spics_gpio_driver);
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_cisubsys_initcall(spics_gpio_init);
198