| // SPDX-License-Identifier: GPL-2.0+ | 
 |  | 
 | /* | 
 |  * GPIO driver for the AMD G series FCH (eg. GX-412TC) | 
 |  * | 
 |  * Copyright (C) 2018 metux IT consult | 
 |  * Author: Enrico Weigelt, metux IT consult <info@metux.net> | 
 |  * | 
 |  */ | 
 |  | 
 | #include <linux/err.h> | 
 | #include <linux/io.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/gpio/driver.h> | 
 | #include <linux/platform_data/gpio/gpio-amd-fch.h> | 
 | #include <linux/spinlock.h> | 
 |  | 
 | #define AMD_FCH_MMIO_BASE		0xFED80000 | 
 | #define AMD_FCH_GPIO_BANK0_BASE		0x1500 | 
 | #define AMD_FCH_GPIO_SIZE		0x0300 | 
 |  | 
 | #define AMD_FCH_GPIO_FLAG_DIRECTION	BIT(23) | 
 | #define AMD_FCH_GPIO_FLAG_WRITE		BIT(22) | 
 | #define AMD_FCH_GPIO_FLAG_READ		BIT(16) | 
 |  | 
 | static const struct resource amd_fch_gpio_iores = | 
 | 	DEFINE_RES_MEM_NAMED( | 
 | 		AMD_FCH_MMIO_BASE + AMD_FCH_GPIO_BANK0_BASE, | 
 | 		AMD_FCH_GPIO_SIZE, | 
 | 		"amd-fch-gpio-iomem"); | 
 |  | 
 | struct amd_fch_gpio_priv { | 
 | 	struct gpio_chip		gc; | 
 | 	void __iomem			*base; | 
 | 	struct amd_fch_gpio_pdata	*pdata; | 
 | 	spinlock_t			lock; | 
 | }; | 
 |  | 
 | static void __iomem *amd_fch_gpio_addr(struct amd_fch_gpio_priv *priv, | 
 | 				       unsigned int gpio) | 
 | { | 
 | 	return priv->base + priv->pdata->gpio_reg[gpio]*sizeof(u32); | 
 | } | 
 |  | 
 | static int amd_fch_gpio_direction_input(struct gpio_chip *gc, | 
 | 					unsigned int offset) | 
 | { | 
 | 	unsigned long flags; | 
 | 	struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); | 
 | 	void __iomem *ptr = amd_fch_gpio_addr(priv, offset); | 
 |  | 
 | 	spin_lock_irqsave(&priv->lock, flags); | 
 | 	writel_relaxed(readl_relaxed(ptr) & ~AMD_FCH_GPIO_FLAG_DIRECTION, ptr); | 
 | 	spin_unlock_irqrestore(&priv->lock, flags); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int amd_fch_gpio_direction_output(struct gpio_chip *gc, | 
 | 					 unsigned int gpio, int value) | 
 | { | 
 | 	unsigned long flags; | 
 | 	struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); | 
 | 	void __iomem *ptr = amd_fch_gpio_addr(priv, gpio); | 
 | 	u32 val; | 
 |  | 
 | 	spin_lock_irqsave(&priv->lock, flags); | 
 |  | 
 | 	val = readl_relaxed(ptr); | 
 | 	if (value) | 
 | 		val |= AMD_FCH_GPIO_FLAG_WRITE; | 
 | 	else | 
 | 		val &= ~AMD_FCH_GPIO_FLAG_WRITE; | 
 |  | 
 | 	writel_relaxed(val | AMD_FCH_GPIO_FLAG_DIRECTION, ptr); | 
 |  | 
 | 	spin_unlock_irqrestore(&priv->lock, flags); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int amd_fch_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio) | 
 | { | 
 | 	int ret; | 
 | 	unsigned long flags; | 
 | 	struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); | 
 | 	void __iomem *ptr = amd_fch_gpio_addr(priv, gpio); | 
 |  | 
 | 	spin_lock_irqsave(&priv->lock, flags); | 
 | 	ret = (readl_relaxed(ptr) & AMD_FCH_GPIO_FLAG_DIRECTION); | 
 | 	spin_unlock_irqrestore(&priv->lock, flags); | 
 |  | 
 | 	return ret ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; | 
 | } | 
 |  | 
 | static int amd_fch_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) | 
 | { | 
 | 	unsigned long flags; | 
 | 	struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); | 
 | 	void __iomem *ptr = amd_fch_gpio_addr(priv, gpio); | 
 | 	u32 mask; | 
 |  | 
 | 	spin_lock_irqsave(&priv->lock, flags); | 
 |  | 
 | 	mask = readl_relaxed(ptr); | 
 | 	if (value) | 
 | 		mask |= AMD_FCH_GPIO_FLAG_WRITE; | 
 | 	else | 
 | 		mask &= ~AMD_FCH_GPIO_FLAG_WRITE; | 
 | 	writel_relaxed(mask, ptr); | 
 |  | 
 | 	spin_unlock_irqrestore(&priv->lock, flags); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int amd_fch_gpio_get(struct gpio_chip *gc, | 
 | 			    unsigned int offset) | 
 | { | 
 | 	unsigned long flags; | 
 | 	int ret; | 
 | 	struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); | 
 | 	void __iomem *ptr = amd_fch_gpio_addr(priv, offset); | 
 |  | 
 | 	spin_lock_irqsave(&priv->lock, flags); | 
 | 	ret = (readl_relaxed(ptr) & AMD_FCH_GPIO_FLAG_READ); | 
 | 	spin_unlock_irqrestore(&priv->lock, flags); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int amd_fch_gpio_request(struct gpio_chip *chip, | 
 | 				unsigned int gpio_pin) | 
 | { | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int amd_fch_gpio_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct amd_fch_gpio_priv *priv; | 
 | 	struct amd_fch_gpio_pdata *pdata; | 
 |  | 
 | 	pdata = dev_get_platdata(&pdev->dev); | 
 | 	if (!pdata) { | 
 | 		dev_err(&pdev->dev, "no platform_data\n"); | 
 | 		return -ENOENT; | 
 | 	} | 
 |  | 
 | 	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | 
 | 	if (!priv) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	priv->pdata	= pdata; | 
 |  | 
 | 	priv->gc.owner			= THIS_MODULE; | 
 | 	priv->gc.parent			= &pdev->dev; | 
 | 	priv->gc.label			= dev_name(&pdev->dev); | 
 | 	priv->gc.ngpio			= priv->pdata->gpio_num; | 
 | 	priv->gc.names			= priv->pdata->gpio_names; | 
 | 	priv->gc.base			= -1; | 
 | 	priv->gc.request		= amd_fch_gpio_request; | 
 | 	priv->gc.direction_input	= amd_fch_gpio_direction_input; | 
 | 	priv->gc.direction_output	= amd_fch_gpio_direction_output; | 
 | 	priv->gc.get_direction		= amd_fch_gpio_get_direction; | 
 | 	priv->gc.get			= amd_fch_gpio_get; | 
 | 	priv->gc.set			= amd_fch_gpio_set; | 
 |  | 
 | 	spin_lock_init(&priv->lock); | 
 |  | 
 | 	priv->base = devm_ioremap_resource(&pdev->dev, &amd_fch_gpio_iores); | 
 | 	if (IS_ERR(priv->base)) | 
 | 		return PTR_ERR(priv->base); | 
 |  | 
 | 	platform_set_drvdata(pdev, priv); | 
 |  | 
 | 	return devm_gpiochip_add_data(&pdev->dev, &priv->gc, priv); | 
 | } | 
 |  | 
 | static struct platform_driver amd_fch_gpio_driver = { | 
 | 	.driver = { | 
 | 		.name = AMD_FCH_GPIO_DRIVER_NAME, | 
 | 	}, | 
 | 	.probe = amd_fch_gpio_probe, | 
 | }; | 
 |  | 
 | module_platform_driver(amd_fch_gpio_driver); | 
 |  | 
 | MODULE_AUTHOR("Enrico Weigelt, metux IT consult <info@metux.net>"); | 
 | MODULE_DESCRIPTION("AMD G-series FCH GPIO driver"); | 
 | MODULE_LICENSE("GPL"); | 
 | MODULE_ALIAS("platform:" AMD_FCH_GPIO_DRIVER_NAME); |