From 592694690526bff9b705c3d629bd9fbe891aae1a Mon Sep 17 00:00:00 2001
From: handsomeyingyan <handsomeyingyan@github.com>
Date: Sat, 1 May 2021 22:09:11 +0800
Subject: [PATCH 09/17] staging: loongson: LS2K PWM & GPIO

---
 drivers/staging/Kconfig             |   2 +
 drivers/staging/Makefile            |   1 +
 drivers/staging/loongson/Kconfig    |  27 ++++
 drivers/staging/loongson/Makefile   |   3 +
 drivers/staging/loongson/gpio-ls.c  | 217 ++++++++++++++++++++++++++++
 drivers/staging/loongson/pwm-ls2k.c | 193 +++++++++++++++++++++++++
 6 files changed, 443 insertions(+)
 create mode 100644 drivers/staging/loongson/Kconfig
 create mode 100644 drivers/staging/loongson/Makefile
 create mode 100644 drivers/staging/loongson/gpio-ls.c
 create mode 100644 drivers/staging/loongson/pwm-ls2k.c

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 2d0310448..ab959d7af 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -118,4 +118,6 @@ source "drivers/staging/wfx/Kconfig"
 
 source "drivers/staging/hikey9xx/Kconfig"
 
+source "drivers/staging/loongson/Kconfig"
+
 endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 757a892ab..84f64d97b 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -48,4 +48,5 @@ obj-$(CONFIG_FIELDBUS_DEV)     += fieldbus/
 obj-$(CONFIG_KPC2000)		+= kpc2000/
 obj-$(CONFIG_QLGE)		+= qlge/
 obj-$(CONFIG_WFX)		+= wfx/
+obj-$(CONFIG_STAGING_LOONGSON)		+= loongson/
 obj-y				+= hikey9xx/
diff --git a/drivers/staging/loongson/Kconfig b/drivers/staging/loongson/Kconfig
new file mode 100644
index 000000000..fb98a3a75
--- /dev/null
+++ b/drivers/staging/loongson/Kconfig
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0
+config STAGING_LOONGSON
+	bool "Loongson Staging Drivers"
+	depends on MACH_LOONGSON64
+	default MACH_LOONGSON64
+	help
+	  Loongson Staging Drivers.
+
+if STAGING_LOONGSON
+
+config LOONGSON_GPIO
+	bool "Loongson GPIO"
+	depends on MACH_LOONGSON64
+	depends on GPIOLIB
+	default MACH_LOONGSON64
+	help
+	  Loongson GPIO.
+
+config LOONGSON_LS2K_PWM
+	bool "Loongson LS2K PWM"
+	depends on MACH_LOONGSON64
+	select PWM
+	default MACH_LOONGSON64
+	help
+	  Loongson LS2K PWM.
+
+endif
diff --git a/drivers/staging/loongson/Makefile b/drivers/staging/loongson/Makefile
new file mode 100644
index 000000000..ba754d9fd
--- /dev/null
+++ b/drivers/staging/loongson/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_LOONGSON_GPIO) += gpio-ls.o
+obj-$(CONFIG_LOONGSON_LS2K_PWM) += pwm-ls2k.o
\ No newline at end of file
diff --git a/drivers/staging/loongson/gpio-ls.c b/drivers/staging/loongson/gpio-ls.c
new file mode 100644
index 000000000..52d8ffa65
--- /dev/null
+++ b/drivers/staging/loongson/gpio-ls.c
@@ -0,0 +1,217 @@
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/leds.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+
+#define GPIO_IO_CONF(x)	(x->base + 0)
+#define GPIO_OUT(x)	(x->base + 0x10)
+#define GPIO_IN(x)	(x->base + 0x20)
+struct ls2_gpio_chip {
+	struct gpio_chip	chip;
+	spinlock_t		lock;
+	void __iomem		*base;
+};
+
+static inline void
+__set_direction(struct ls2_gpio_chip *lgpio, unsigned pin, int input)
+{
+	u64 u;
+
+	u = readq(GPIO_IO_CONF(lgpio));
+	if (input)
+		u |= 1UL << pin;
+	else
+		u &= ~(1UL << pin);
+	writeq(u, GPIO_IO_CONF(lgpio));
+}
+
+static void __set_level(struct ls2_gpio_chip *lgpio, unsigned pin, int high)
+{
+	u64 u;
+
+	u = readq(GPIO_OUT(lgpio));
+	if (high)
+		u |= 1UL << pin;
+	else
+		u &= ~(1UL << pin);
+	writeq(u, GPIO_OUT(lgpio));
+}
+
+/*
+ * GPIO primitives.
+ */
+static int ls2_gpio_request(struct gpio_chip *chip, unsigned pin)
+{
+	if (pin >= chip->ngpio)
+		return -EINVAL;
+	else
+		return 0;
+}
+
+static int ls2_gpio_direction_input(struct gpio_chip *chip, unsigned pin)
+{
+	unsigned long flags;
+	struct ls2_gpio_chip *lgpio =
+		container_of(chip, struct ls2_gpio_chip, chip);
+
+	spin_lock_irqsave(&lgpio->lock, flags);
+	__set_direction(lgpio, pin, 1);
+	spin_unlock_irqrestore(&lgpio->lock, flags);
+
+	return 0;
+}
+
+static int ls2_gpio_get(struct gpio_chip *chip, unsigned pin)
+{
+	struct ls2_gpio_chip *lgpio =
+		container_of(chip, struct ls2_gpio_chip, chip);
+	u64 val;
+
+	if (readq(GPIO_IO_CONF(lgpio)) & (1UL << pin))
+		val = readq(GPIO_IN(lgpio));
+	else
+		val = readq(GPIO_OUT(lgpio));
+
+
+	return (val >> pin) & 1;
+}
+
+static int
+ls2_gpio_direction_output(struct gpio_chip *chip, unsigned pin, int value)
+{
+	struct ls2_gpio_chip *lgpio =
+		container_of(chip, struct ls2_gpio_chip, chip);
+	unsigned long flags;
+
+	spin_lock_irqsave(&lgpio->lock, flags);
+	__set_level(lgpio, pin, value);
+	__set_direction(lgpio, pin, 0);
+	spin_unlock_irqrestore(&lgpio->lock, flags);
+
+	return 0;
+}
+
+static void ls2_gpio_set(struct gpio_chip *chip, unsigned pin, int value)
+{
+	struct ls2_gpio_chip *lgpio =
+		container_of(chip, struct ls2_gpio_chip, chip);
+	unsigned long flags;
+
+	spin_lock_irqsave(&lgpio->lock, flags);
+	__set_level(lgpio, pin, value);
+	spin_unlock_irqrestore(&lgpio->lock, flags);
+}
+
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/seq_file.h>
+
+static void ls2_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+
+}
+#else
+#define ls2_gpio_dbg_show NULL
+#endif
+
+static struct ls2_gpio_chip ls_gpio;
+static void ls2_gpio_init(struct device_node *np,
+			    int gpio_base, int ngpio,
+			    void __iomem *base)
+{
+	struct ls2_gpio_chip *lgpio;
+
+	lgpio = &ls_gpio;
+	lgpio->chip.label = kstrdup("ls2-gpio", GFP_KERNEL);
+	lgpio->chip.request = ls2_gpio_request;
+	lgpio->chip.direction_input = ls2_gpio_direction_input;
+	lgpio->chip.get = ls2_gpio_get;
+	lgpio->chip.direction_output = ls2_gpio_direction_output;
+	lgpio->chip.set = ls2_gpio_set;
+	lgpio->chip.base = gpio_base;
+	lgpio->chip.ngpio = ngpio;
+	lgpio->chip.can_sleep = 0;
+	lgpio->chip.of_node = np;
+	lgpio->chip.dbg_show = ls2_gpio_dbg_show;
+
+	spin_lock_init(&lgpio->lock);
+	lgpio->base = (void __iomem *)base;
+
+	gpiochip_add(&lgpio->chip);
+}
+
+static int ls2_gpio_probe(struct platform_device *pdev)
+{
+	struct resource *iores;
+	int gpio_base;
+	unsigned int ngpio;
+	void __iomem *base;
+	struct device_node *np = pdev->dev.of_node;
+	int ret = 0;
+
+	iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!iores) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	if (!request_mem_region(iores->start, resource_size(iores),
+				pdev->name)) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	gpio_base = 0;
+
+	base = ioremap(iores->start, resource_size(iores));
+
+	if (!base) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpio)) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	ls2_gpio_init(np, gpio_base, ngpio, base);
+
+	return 0;
+out:
+	pr_err("%s: %s: missing mandatory property\n", __func__, np->name);
+	return ret;
+}
+
+
+static const struct of_device_id ls2_gpio_dt_ids[] = {
+	{ .compatible = "ls,ls-gpio"},
+	{ .compatible = "ls,ls2k-gpio"},
+	{}
+};
+
+static struct platform_driver ls2_gpio_driver = {
+	.driver 	= {
+		.name	= "gpio-ls",
+		.owner	= THIS_MODULE,
+		.of_match_table = ls2_gpio_dt_ids,
+	},
+	.probe		= ls2_gpio_probe,
+};
+
+static int __init gpio_ls_init(void)
+{
+	return platform_driver_register(&ls2_gpio_driver);
+}
+
+postcore_initcall(gpio_ls_init);
diff --git a/drivers/staging/loongson/pwm-ls2k.c b/drivers/staging/loongson/pwm-ls2k.c
new file mode 100644
index 000000000..832681470
--- /dev/null
+++ b/drivers/staging/loongson/pwm-ls2k.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2017 Loongson Technology Corporation Limited
+ *
+ * Author: Juxin Gao <gaojuxin@loongson.cn>
+ * License terms: GNU General Public License (GPL)
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/clocksource.h>
+#include <linux/clockchips.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+
+/* counter offest */
+#define LOW_BUFFER  0x004
+#define FULL_BUFFER 0x008
+#define CTRL		0x00c
+
+/* CTRL counter each bit */
+#define CTRL_EN		BIT(0)
+#define CTRL_OE		BIT(3)
+#define CTRL_SINGLE	BIT(4)
+#define CTRL_INTE	BIT(5)
+#define CTRL_INT	BIT(6)
+#define CTRL_RST	BIT(7)
+#define CTRL_CAPTE	BIT(8)
+#define CTRL_INVERT	BIT(9)
+#define CTRL_DZONE	BIT(10)
+
+#define to_ls2k_pwm_chip(_chip)		container_of(_chip, struct ls2k_pwm_chip, chip)
+#define NS_IN_HZ (1000000000UL)
+#define CPU_FRQ_PWM (125000000UL)
+
+struct ls2k_pwm_chip{
+	struct pwm_chip chip;
+	void __iomem		*mmio_base;
+};
+
+static int ls2k_pwm_set_polarity(struct pwm_chip *chip,
+				      struct pwm_device *pwm,
+				      enum pwm_polarity polarity)
+{
+	struct ls2k_pwm_chip *ls2k_pwm = to_ls2k_pwm_chip(chip);
+	u16 val;
+
+	val = readl(ls2k_pwm->mmio_base + CTRL);
+	val |= CTRL_INVERT;
+	writel(val, ls2k_pwm->mmio_base);
+	return 0;
+}
+
+static void ls2k_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct ls2k_pwm_chip *ls2k_pwm = to_ls2k_pwm_chip(chip);
+	u32 ret;
+
+	ret = readl(ls2k_pwm->mmio_base + CTRL);
+	ret &= ~CTRL_EN;
+	writel(ret, ls2k_pwm->mmio_base + CTRL);
+}
+
+static int ls2k_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct ls2k_pwm_chip *ls2k_pwm = to_ls2k_pwm_chip(chip);
+	int ret;
+
+	ret = readl(ls2k_pwm->mmio_base + CTRL);
+	ret |= CTRL_EN;
+	writel(ret, ls2k_pwm->mmio_base + CTRL);
+	return 0;
+}
+
+static int ls2k_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+				int duty_ns, int period_ns)
+{
+	struct ls2k_pwm_chip *ls2k_pwm = to_ls2k_pwm_chip(chip);
+	unsigned int period, duty;
+	unsigned long long val0,val1;
+
+	if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ)
+		return -ERANGE;
+
+	val0 = CPU_FRQ_PWM * period_ns;
+	do_div(val0, NSEC_PER_SEC);
+	if (val0 < 1)
+		val0 = 1;
+	period = val0;
+
+	val1 = CPU_FRQ_PWM * duty_ns;
+	do_div(val1, NSEC_PER_SEC);
+	if (val1 < 1)
+		val1 = 1;
+	duty = val1;
+
+	writel(duty,ls2k_pwm->mmio_base + LOW_BUFFER);
+	writel(period,ls2k_pwm->mmio_base + FULL_BUFFER);
+
+	return 0;
+}
+
+static const struct pwm_ops ls2k_pwm_ops = {
+	.config = ls2k_pwm_config,
+	.set_polarity = ls2k_pwm_set_polarity,
+	.enable = ls2k_pwm_enable,
+	.disable = ls2k_pwm_disable,
+	.owner = THIS_MODULE,
+};
+
+static int ls2k_pwm_probe(struct platform_device *pdev)
+{
+	struct ls2k_pwm_chip *pwm;
+	struct resource *mem;
+	int err;
+	if (pdev->id > 3)
+		dev_err(&pdev->dev, "Currently only four PWM controller is implemented,namely, 0,1,2,3.\n");
+	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
+	if(!pwm){
+		dev_err(&pdev->dev, "failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	pwm->chip.dev = &pdev->dev;
+	pwm->chip.ops = &ls2k_pwm_ops;
+	pwm->chip.base = -1;
+	pwm->chip.npwm = 1;
+
+	mem = platform_get_resource(pdev,IORESOURCE_MEM, 0);
+	if(!mem){
+		dev_err(&pdev->dev, "no mem resource?\n");
+		return -ENODEV;
+	}
+	pwm->mmio_base = devm_ioremap_resource(&pdev->dev, mem);
+	if(!pwm->mmio_base){
+		dev_err(&pdev->dev, "mmio_base is null\n");
+		return -ENOMEM;
+	}
+	err = pwmchip_add(&pwm->chip);
+	if(err < 0){
+		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n",err);
+		return err;
+	}
+
+	platform_set_drvdata(pdev, pwm);
+
+	dev_dbg(&pdev->dev, "pwm probe successful\n");
+	return 0;
+}
+
+static int ls2k_pwm_remove(struct platform_device *pdev)
+{
+	struct ls2k_pwm_chip *pwm = platform_get_drvdata(pdev);
+	int err;
+	if (!pwm)
+		return -ENODEV;
+	err = pwmchip_remove(&pwm->chip);
+	if(err < 0)
+		return err;
+
+	return 0;
+}
+#ifdef CONFIG_OF
+static struct of_device_id ls2k_pwm_id_table[] = {
+	{.compatible = "loongson,ls2k-pwm"},
+	{},
+};
+#endif
+static struct platform_driver ls2k_pwm_driver = {
+	.driver = {
+		.name = "ls2k-pwm",
+		.owner = THIS_MODULE,
+		.bus = &platform_bus_type,
+#ifdef CONFIG_OF
+		.of_match_table = of_match_ptr(ls2k_pwm_id_table),
+#endif
+	},
+	.probe = ls2k_pwm_probe,
+	.remove = ls2k_pwm_remove,
+};
+module_platform_driver(ls2k_pwm_driver);
+
+MODULE_AUTHOR("Juxin Gao <gaojuxin@loongson.com>");
+MODULE_DESCRIPTION("Loongson 2k1000 Pwm Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ls2k-pwm");
-- 
2.17.1

