18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2013 TangoTec Ltd.
48c2ecf20Sopenharmony_ci * Author: Baruch Siach <baruch@tkos.co.il>
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Driver for the Xtensa LX4 GPIO32 Option
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Documentation: Xtensa LX4 Microprocessor Data Book, Section 2.22
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci * GPIO32 is a standard optional extension to the Xtensa architecture core that
118c2ecf20Sopenharmony_ci * provides preconfigured output and input ports for intra SoC signaling. The
128c2ecf20Sopenharmony_ci * GPIO32 option is implemented as 32bit Tensilica Instruction Extension (TIE)
138c2ecf20Sopenharmony_ci * output state called EXPSTATE, and 32bit input wire called IMPWIRE. This
148c2ecf20Sopenharmony_ci * driver treats input and output states as two distinct devices.
158c2ecf20Sopenharmony_ci *
168c2ecf20Sopenharmony_ci * Access to GPIO32 specific instructions is controlled by the CPENABLE
178c2ecf20Sopenharmony_ci * (Coprocessor Enable Bits) register. By default Xtensa Linux startup code
188c2ecf20Sopenharmony_ci * disables access to all coprocessors. This driver sets the CPENABLE bit
198c2ecf20Sopenharmony_ci * corresponding to GPIO32 before any GPIO32 specific instruction, and restores
208c2ecf20Sopenharmony_ci * CPENABLE state after that.
218c2ecf20Sopenharmony_ci *
228c2ecf20Sopenharmony_ci * This driver is currently incompatible with SMP. The GPIO32 extension is not
238c2ecf20Sopenharmony_ci * guaranteed to be available in all cores. Moreover, each core controls a
248c2ecf20Sopenharmony_ci * different set of IO wires. A theoretical SMP aware version of this driver
258c2ecf20Sopenharmony_ci * would need to have a per core workqueue to do the actual GPIO manipulation.
268c2ecf20Sopenharmony_ci */
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci#include <linux/err.h>
298c2ecf20Sopenharmony_ci#include <linux/module.h>
308c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h>
318c2ecf20Sopenharmony_ci#include <linux/bitops.h>
328c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#include <asm/coprocessor.h> /* CPENABLE read/write macros */
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#ifndef XCHAL_CP_ID_XTIOP
378c2ecf20Sopenharmony_ci#error GPIO32 option is not enabled for your xtensa core variant
388c2ecf20Sopenharmony_ci#endif
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#if XCHAL_HAVE_CP
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic inline unsigned long enable_cp(unsigned long *cpenable)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	unsigned long flags;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	local_irq_save(flags);
478c2ecf20Sopenharmony_ci	*cpenable = xtensa_get_sr(cpenable);
488c2ecf20Sopenharmony_ci	xtensa_set_sr(*cpenable | BIT(XCHAL_CP_ID_XTIOP), cpenable);
498c2ecf20Sopenharmony_ci	return flags;
508c2ecf20Sopenharmony_ci}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistatic inline void disable_cp(unsigned long flags, unsigned long cpenable)
538c2ecf20Sopenharmony_ci{
548c2ecf20Sopenharmony_ci	xtensa_set_sr(cpenable, cpenable);
558c2ecf20Sopenharmony_ci	local_irq_restore(flags);
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci#else
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic inline unsigned long enable_cp(unsigned long *cpenable)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	*cpenable = 0; /* avoid uninitialized value warning */
638c2ecf20Sopenharmony_ci	return 0;
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic inline void disable_cp(unsigned long flags, unsigned long cpenable)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci#endif /* XCHAL_HAVE_CP */
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_cistatic int xtensa_impwire_get_direction(struct gpio_chip *gc, unsigned offset)
738c2ecf20Sopenharmony_ci{
748c2ecf20Sopenharmony_ci	return GPIO_LINE_DIRECTION_IN; /* input only */
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistatic int xtensa_impwire_get_value(struct gpio_chip *gc, unsigned offset)
788c2ecf20Sopenharmony_ci{
798c2ecf20Sopenharmony_ci	unsigned long flags, saved_cpenable;
808c2ecf20Sopenharmony_ci	u32 impwire;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	flags = enable_cp(&saved_cpenable);
838c2ecf20Sopenharmony_ci	__asm__ __volatile__("read_impwire %0" : "=a" (impwire));
848c2ecf20Sopenharmony_ci	disable_cp(flags, saved_cpenable);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	return !!(impwire & BIT(offset));
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic void xtensa_impwire_set_value(struct gpio_chip *gc, unsigned offset,
908c2ecf20Sopenharmony_ci				    int value)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	BUG(); /* output only; should never be called */
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic int xtensa_expstate_get_direction(struct gpio_chip *gc, unsigned offset)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	return GPIO_LINE_DIRECTION_OUT; /* output only */
988c2ecf20Sopenharmony_ci}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_cistatic int xtensa_expstate_get_value(struct gpio_chip *gc, unsigned offset)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	unsigned long flags, saved_cpenable;
1038c2ecf20Sopenharmony_ci	u32 expstate;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	flags = enable_cp(&saved_cpenable);
1068c2ecf20Sopenharmony_ci	__asm__ __volatile__("rur.expstate %0" : "=a" (expstate));
1078c2ecf20Sopenharmony_ci	disable_cp(flags, saved_cpenable);
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	return !!(expstate & BIT(offset));
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic void xtensa_expstate_set_value(struct gpio_chip *gc, unsigned offset,
1138c2ecf20Sopenharmony_ci				     int value)
1148c2ecf20Sopenharmony_ci{
1158c2ecf20Sopenharmony_ci	unsigned long flags, saved_cpenable;
1168c2ecf20Sopenharmony_ci	u32 mask = BIT(offset);
1178c2ecf20Sopenharmony_ci	u32 val = value ? BIT(offset) : 0;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	flags = enable_cp(&saved_cpenable);
1208c2ecf20Sopenharmony_ci	__asm__ __volatile__("wrmsk_expstate %0, %1"
1218c2ecf20Sopenharmony_ci			     :: "a" (val), "a" (mask));
1228c2ecf20Sopenharmony_ci	disable_cp(flags, saved_cpenable);
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic struct gpio_chip impwire_chip = {
1268c2ecf20Sopenharmony_ci	.label		= "impwire",
1278c2ecf20Sopenharmony_ci	.base		= -1,
1288c2ecf20Sopenharmony_ci	.ngpio		= 32,
1298c2ecf20Sopenharmony_ci	.get_direction	= xtensa_impwire_get_direction,
1308c2ecf20Sopenharmony_ci	.get		= xtensa_impwire_get_value,
1318c2ecf20Sopenharmony_ci	.set		= xtensa_impwire_set_value,
1328c2ecf20Sopenharmony_ci};
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_cistatic struct gpio_chip expstate_chip = {
1358c2ecf20Sopenharmony_ci	.label		= "expstate",
1368c2ecf20Sopenharmony_ci	.base		= -1,
1378c2ecf20Sopenharmony_ci	.ngpio		= 32,
1388c2ecf20Sopenharmony_ci	.get_direction	= xtensa_expstate_get_direction,
1398c2ecf20Sopenharmony_ci	.get		= xtensa_expstate_get_value,
1408c2ecf20Sopenharmony_ci	.set		= xtensa_expstate_set_value,
1418c2ecf20Sopenharmony_ci};
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic int xtensa_gpio_probe(struct platform_device *pdev)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	int ret;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	ret = gpiochip_add_data(&impwire_chip, NULL);
1488c2ecf20Sopenharmony_ci	if (ret)
1498c2ecf20Sopenharmony_ci		return ret;
1508c2ecf20Sopenharmony_ci	return gpiochip_add_data(&expstate_chip, NULL);
1518c2ecf20Sopenharmony_ci}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_cistatic struct platform_driver xtensa_gpio_driver = {
1548c2ecf20Sopenharmony_ci	.driver		= {
1558c2ecf20Sopenharmony_ci		.name		= "xtensa-gpio",
1568c2ecf20Sopenharmony_ci	},
1578c2ecf20Sopenharmony_ci	.probe		= xtensa_gpio_probe,
1588c2ecf20Sopenharmony_ci};
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic int __init xtensa_gpio_init(void)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	struct platform_device *pdev;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	pdev = platform_device_register_simple("xtensa-gpio", 0, NULL, 0);
1658c2ecf20Sopenharmony_ci	if (IS_ERR(pdev))
1668c2ecf20Sopenharmony_ci		return PTR_ERR(pdev);
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	return platform_driver_register(&xtensa_gpio_driver);
1698c2ecf20Sopenharmony_ci}
1708c2ecf20Sopenharmony_cidevice_initcall(xtensa_gpio_init);
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ciMODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>");
1738c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Xtensa LX4 GPIO32 driver");
1748c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
175