From 9baab5a954fbe718a80d116c2443da7ce1d2214b Mon Sep 17 00:00:00 2001
From: "Xu, Liang" <liang.xu@intel.com>
Date: Thu, 15 Mar 2018 07:34:38 +0100
Subject: [PATCH] Merge pull request #269 in SW_PON/linux from
 DRVLIB_SW-528-separate-sso-led-driver-into to xrx500

* commit '982cbab0f74614f984e996fb88c15e1c7d218871':
  Add new SSO driver
---
 drivers/gpio/Kconfig          |   8 +
 drivers/gpio/Makefile         |   1 +
 drivers/gpio/gpio-intel-sso.c | 255 +++++++++
 drivers/leds/Kconfig          |   8 +
 drivers/leds/Makefile         |   1 +
 drivers/leds/leds-intel-sso.c | 969 ++++++++++++++++++++++++++++++++++
 6 files changed, 1242 insertions(+)
 create mode 100644 drivers/gpio/gpio-intel-sso.c
 create mode 100644 drivers/leds/leds-intel-sso.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 5cc6fbf86..871a8f4ec 100755
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1069,6 +1069,14 @@ config GPIO_WM8994
 	  Say yes here to access the GPIO signals of WM8994 audio hub
 	  CODECs from Wolfson Microelectronics.
 
+config GPIO_INTEL_SSO
+	bool "Intel SSO GPIO support"
+	depends on GPIOLIB
+	depends on MFD_SYSCON
+	help
+	  This driver enables GPIO function of SSO.
+	  SSO support upto 32 pins in output mode only.
+
 endmenu
 
 menu "PCI GPIO expanders"
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 59f36367f..3def9b601 100755
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -142,3 +142,4 @@ obj-$(CONFIG_GPIO_ZEVIO)	+= gpio-zevio.o
 obj-$(CONFIG_GPIO_ZYNQ)		+= gpio-zynq.o
 obj-$(CONFIG_GPIO_ZX)		+= gpio-zx.o
 obj-$(CONFIG_GPIO_LOONGSON1)	+= gpio-loongson1.o
+obj-$(CONFIG_GPIO_INTEL_SSO)	+= gpio-intel-sso.o
diff --git a/drivers/gpio/gpio-intel-sso.c b/drivers/gpio/gpio-intel-sso.c
new file mode 100644
index 000000000..3b53938ef
--- /dev/null
+++ b/drivers/gpio/gpio-intel-sso.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel GPIO SSO driver
+ *
+ * Copyright (C) 2018 Intel Corporation.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+
+/* reg definition */
+#define DUTY_CYCLE(x)	(0x8 + ((x) * 4))
+#define SSO_CON0	0x2B0
+#define SSO_CON1	0x2B4
+#define SSO_CPU		0x2B8
+#define SSO_CON2	0x2C4
+#define SSO_CON3	0x2C8
+
+#define MAX_PIN_NUM_PER_BANK	32
+#define MAX_GROUP_NUM		4
+#define PINS_PER_GROUP		8
+
+static const char * const sso_gpio_drv_name = "intel-sso-gpio";
+
+/**
+ * struct sso_gpio_priv
+ *
+ * @mmap: SSO mapped memory resource
+ * @dev: sso basic device
+ * @pdev: sso platform device
+ * @gclk: sso gate clock
+ * @chip: gpio controller chip
+ * @pins: pin number of the gpio chip
+ * @alloc_bitmap: bitmap for allocated pins
+ * @lock: lock to protect register write
+ */
+struct sso_gpio_priv {
+	struct regmap *mmap;
+	struct device *dev;
+	struct platform_device *pdev;
+	struct clk *gclk;
+	struct gpio_chip chip;
+	u32 pins;
+	int gpio_base;
+	u32 alloc_bitmap;
+};
+
+static int sso_gpio_writel(struct regmap *map, u32 reg, u32 val)
+{
+	return regmap_write(map, reg, val);
+}
+
+static int
+sso_gpio_update_bit(struct regmap *map, u32 reg, u32 off, u32 val)
+{
+	bool bit;
+
+	bit = !!val;
+	return regmap_update_bits(map, reg, BIT(off), (bit << off));
+}
+
+static int sso_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+	struct sso_gpio_priv *priv = gpiochip_get_data(chip);
+
+	if (priv->alloc_bitmap & BIT(offset))
+		return -EINVAL;
+
+	priv->alloc_bitmap |= BIT(offset);
+
+	return 0;
+}
+
+static void sso_gpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+	struct sso_gpio_priv *priv = gpiochip_get_data(chip);
+
+	priv->alloc_bitmap &= ~BIT(offset);
+}
+
+static int sso_gpio_get_dir(struct gpio_chip *chip, unsigned int offset)
+{
+	return GPIOF_DIR_OUT;
+}
+
+static int
+sso_gpio_dir_out(struct gpio_chip *chip, unsigned int offset, int value)
+{
+	struct sso_gpio_priv *priv = gpiochip_get_data(chip);
+
+	return sso_gpio_update_bit(priv->mmap, SSO_CPU, offset, value);
+}
+
+static void sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+	struct sso_gpio_priv *priv = gpiochip_get_data(chip);
+
+	sso_gpio_update_bit(priv->mmap, SSO_CPU, offset, value);
+}
+
+static int sso_gpio_gc_init(struct sso_gpio_priv *priv,
+			    struct device *dev, const char *name)
+{
+	struct gpio_chip *gc = &priv->chip;
+
+	gc->request		= sso_gpio_request;
+	gc->free		= sso_gpio_free;
+	gc->get_direction	= sso_gpio_get_dir;
+	gc->direction_output	= sso_gpio_dir_out;
+	gc->set			= sso_gpio_set;
+
+	gc->label		= name;
+	gc->base		= priv->gpio_base;
+	gc->ngpio		= priv->pins;
+	gc->parent		= dev;
+	gc->owner		= THIS_MODULE;
+	gc->of_node		= dev->of_node;
+
+	return devm_gpiochip_add_data(dev, gc, priv);
+}
+
+static int sso_gpio_hw_init(struct sso_gpio_priv *priv)
+{
+	int i;
+
+	/* Clear all duty cycles */
+	for (i = 0; i < priv->pins; i++) {
+		if (sso_gpio_writel(priv->mmap, DUTY_CYCLE(i), 0))
+			return -ENOTSUPP;
+	}
+
+	/* 4 groups for total 32 pins */
+	for (i = 1; i <= MAX_GROUP_NUM; i++) {
+		if (i * PINS_PER_GROUP <= priv->pins ||
+		    priv->pins > (i - 1) * PINS_PER_GROUP)
+			if (sso_gpio_update_bit(priv->mmap, SSO_CON1, i - 1, 1))
+				return -ENOTSUPP;
+	}
+
+	/* NO HW directly controlled pin by default */
+	if (sso_gpio_writel(priv->mmap, SSO_CON3, 0))
+		return -ENOTSUPP;
+
+	/* NO BLINK for all pins */
+	if (sso_gpio_writel(priv->mmap, SSO_CON2, 0))
+		return -ENOTSUPP;
+
+	/* OUTPUT 0 by default */
+	if (sso_gpio_writel(priv->mmap, SSO_CPU, 0))
+		return -ENOTSUPP;
+
+	return 0;
+}
+
+static int intel_sso_gpio_probe(struct platform_device *pdev)
+{
+	struct sso_gpio_priv *priv;
+	struct device *dev = &pdev->dev;
+	const char *drv_name;
+	u32 prop;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	/* pin base */
+	if (device_property_read_u32(dev, "intel,sso-gpio-base", &prop))
+		priv->gpio_base = -1;
+	else
+		priv->gpio_base = prop;
+
+	/* pin number */
+	if (!device_property_read_u32(dev, "ngpios", &prop)) {
+		priv->pins = prop;
+		if (priv->pins > MAX_PIN_NUM_PER_BANK)
+			return -EINVAL;
+	} else {
+		priv->pins = MAX_PIN_NUM_PER_BANK;
+	}
+
+	if (!device_property_read_string(dev, "intel,sso-gpio-name", &drv_name))
+		drv_name = dev->of_node->full_name;
+	else
+		drv_name = sso_gpio_drv_name;
+
+	/* gate clock */
+	priv->gclk = devm_clk_get(dev, "sso");
+	if (IS_ERR(priv->gclk)) {
+		dev_err(dev, "get sso gate clock failed!\n");
+		return -EINVAL;
+	}
+
+	if (clk_prepare_enable(priv->gclk)) {
+		dev_err(dev, "Failed to enable sso gate clock!\n");
+		return -EINVAL;
+	}
+
+	/* gpio mem */
+	priv->mmap = syscon_node_to_regmap(dev->of_node);
+	if (IS_ERR(priv->mmap)) {
+		dev_err(dev, "Failed to map iomem!\n");
+		return PTR_ERR(priv->mmap);
+	}
+
+	ret = sso_gpio_hw_init(priv);
+	if (ret)
+		goto hw_err;
+
+	ret = sso_gpio_gc_init(priv, dev, drv_name);
+	if (ret)
+		goto gc_err;
+
+	priv->dev = dev;
+	priv->pdev = pdev;
+	platform_set_drvdata(pdev, priv);
+
+	return 0;
+
+hw_err:
+gc_err:
+	regmap_exit(priv->mmap);
+	return ret;
+}
+
+static const struct of_device_id of_sso_gpio_match[] = {
+	{ .compatible = "intel,sso-gpio" },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, of_sso_gpio_match);
+
+static struct platform_driver intel_sso_gpio_driver = {
+	.probe		= intel_sso_gpio_probe,
+	.driver		= {
+			.name = "intel-sso-gpio",
+			.of_match_table = of_match_ptr(of_sso_gpio_match),
+	},
+};
+
+static int __init intel_sso_gpio_init(void)
+{
+	return platform_driver_register(&intel_sso_gpio_driver);
+}
+subsys_initcall(intel_sso_gpio_init);
+
+MODULE_AUTHOR("Zhu Yixin <Yixin.zhu@intel.com>");
+MODULE_DESCRIPTION("Intel SSO GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 066971115..9ea5b703e 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -670,6 +670,14 @@ config LEDS_XRX500
       This also acts as a GPIO controller for drivers to drive the shift register
       outputs.
 
+config LEDS_INTEL_SSO
+	tristate "LED support for INTEL SSO"
+	depends on LEDS_CLASS
+	depends on MFD_SYSCON
+	depends on GPIO_INTEL_SSO
+	help
+	  This enables LED support for Serial Shift Output Controller(SSO).
+
 comment "LED Triggers"
 source "drivers/leds/trigger/Kconfig"
 
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 75e992079..bddc91073 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
 obj-$(CONFIG_LEDS_PM8058)		+= leds-pm8058.o
 obj-$(CONFIG_LEDS_MLXCPLD)		+= leds-mlxcpld.o
 obj-$(CONFIG_LEDS_XRX500)       += leds-xrx500.o leds-xrx500-proc.o
+obj-$(CONFIG_LEDS_INTEL_SSO)	+= leds-intel-sso.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
diff --git a/drivers/leds/leds-intel-sso.c b/drivers/leds/leds-intel-sso.c
new file mode 100644
index 000000000..bc45767c6
--- /dev/null
+++ b/drivers/leds/leds-intel-sso.c
@@ -0,0 +1,969 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel LED SSO driver
+ *
+ * Copyright (C) 2018 Intel Corporation.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/leds.h>
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/debugfs.h>
+#include <linux/proc_fs.h>
+#include <linux/uaccess.h>
+
+/* reg definition */
+#define DUTY_CYCLE(x)	(0x8 + ((x) * 4))
+#define LED_BLINK_H8_0	0x0
+#define LED_BLINK_H8_1	0x4
+#define SSO_CON0	0x2B0
+#define SSO_CON1	0x2B4
+#define SSO_CON2	0x2C4
+#define SSO_CON3	0x2C8
+
+/* CON0 */
+#define RZFL		26
+#define BLINK_R		30
+/* BLINK H8 */
+#define BLINK_FREQ_OFF(pin, src)	(((pin) * 6) + ((src) * 2))
+#define BLINK_FREQ_MASK	0x3
+#define BLINK_SRC_OFF(pin, src)		(((pin) * 6) + 4)
+#define BLINK_SRC_MASK	0x3
+
+/* Driver MACRO */
+#define MAX_FREQ_RANK		9
+#define GPTC_PRE_DIV		200
+#define SSO_LED_MAX_NUM		32
+#define BRIGHTNESS_MAX		255
+
+enum {
+	MAX_FPID_FREQ_RANK = 4, /* 0 - 3 */
+	MAX_GPTC_FREQ_RANK = 8, /* 4 - 7 */
+	MAX_GPTC_HS_FREQ_RANK = 9, /* 8 */
+};
+
+enum {
+	LED_GRP0_PIN_MAX = 24,
+	LED_GRP1_PIN_MAX = 29,
+	LED_GRP2_PIN_MAX = 32,
+};
+
+enum {
+	LED_GRP0_0_23,
+	LED_GRP1_24_28,
+	LED_GRP2_29_31,
+	LED_GROUP_MAX,
+};
+
+enum {
+	CLK_SRC_FPID = 0,
+	CLK_SRC_GPTC = 1,
+	CLK_SRC_GPTC_HS = 2,
+};
+
+enum {
+	SSO_BRIGHTNESS_OFF = 0,
+	SSO_DEF_BRIGHTNESS = 0x80,
+	SSO_MAX_BRIGHTNESS = 0xFF
+};
+
+static const u32 freq_div_tbl[] = {4000, 2000, 1000, 800};
+
+struct sso_led_priv;
+/**
+ * struct sso_led_desc
+ *
+ * @np: device tree node for this LED
+ * @name: LED name
+ * @default_trigger: LED trigger
+ * @pin: gpio pin id/offset
+ * @brighness: LED brightness/color
+ * @blink_rate: LED HW blink frequency
+ * @freq_idx: LED freqency idx of freq array of sso_led_priv
+ * @retain_state_suspended: flag for LED class
+ * @panic_indicator: flag for LED class
+ * @hw_blink: HW blink
+ * @hw_trig: HW control LED
+ * @blinking: LED blink status
+ */
+struct sso_led_desc {
+	struct device_node *np;
+	const char *name;
+	const char *default_trigger;
+	u32 pin;
+	unsigned int brightness;
+	unsigned int blink_rate;
+	u32 freq_idx;
+
+	unsigned int retain_state_suspended:1;
+	unsigned int retain_state_shutdown:1;
+	unsigned int panic_indicator:1;
+	unsigned int hw_blink:1;
+	unsigned int hw_trig:1;
+	unsigned int blinking:1;
+};
+
+/**
+ * struct sso_led
+ *
+ * @list: LED struct list
+ * @cdev: LED class device for this LED
+ * @gpiod: gpio descriptor
+ * @led: led settings for this LED
+ * @priv: pointer to sso driver private data
+ */
+struct sso_led {
+	struct list_head list;
+	struct led_classdev cdev;
+	struct gpio_desc *gpiod;
+	struct sso_led_desc desc;
+	struct sso_led_priv *priv;
+};
+
+/**
+ *
+ * @fpid_clk: sso slow clock
+ * @fpid_clkrate: sso slow clock rate
+ * @gptc_clkrate: sso fast clock rate
+ * @falling_trig: led trigger on rise edge or falling edge.
+ * @brightness: default brightness for all leds
+ * @blink_rate_idx: default brink rate idx of freq
+ */
+struct sso_cfg {
+	struct clk *fpid_clk;
+	u32 fpid_clkrate;
+	u32 gptc_clkrate;
+	bool falling_trig;
+	u32 brightness;
+	int blink_rate_idx;
+};
+
+/**
+ * struct sso_led_priv
+ *
+ * @membase: SSO mapped memory resource
+ * @dev: sso basic device
+ * @pdev: sso platform device
+ * @freq: sso frequency array
+ * @cfg: sso DT configuration
+ * @led_list: link list of leds of SSO
+ * @debugfs: debugfs proc
+ */
+struct sso_led_priv {
+	struct regmap *mmap;
+	struct device *dev;
+	struct platform_device *pdev;
+	u32 freq[MAX_FREQ_RANK];
+	struct sso_cfg cfg;
+	struct list_head led_list;
+	struct dentry *debugfs;
+};
+
+static int sso_led_writel(struct regmap *map, u32 reg, u32 val)
+{
+	return regmap_write(map, reg, val);
+}
+
+static int
+sso_led_update_bit(struct regmap *map, u32 reg, u32 off, u32 val)
+{
+	val = !!val;
+
+	return regmap_update_bits(map, reg, BIT(off), (val << off));
+}
+
+static int
+sso_led_write_mask(struct regmap *map, u32 reg, u32 off, u32 mask, u32 val)
+{
+	u32 reg_val;
+
+	if (regmap_read(map, reg, &reg_val))
+		return -EINVAL;
+
+	reg_val = (reg_val & ~(mask << off)) | ((val & mask) << off);
+
+	return sso_led_writel(map, reg, reg_val);
+}
+
+static int sso_led_hw_init(struct sso_led_priv *priv)
+{
+	struct sso_cfg *cfg = &priv->cfg;
+
+	if (sso_led_update_bit(priv->mmap, SSO_CON0, RZFL, cfg->falling_trig))
+		return -EINVAL;
+	if (sso_led_update_bit(priv->mmap, SSO_CON0, BLINK_R, 1))
+		return -EINVAL;
+
+	return 0;
+}
+
+static u32 sso_rectify_brightness(u32 brightness)
+{
+	if (brightness > BRIGHTNESS_MAX)
+		return BRIGHTNESS_MAX;
+	else
+		return brightness;
+}
+
+static int sso_rectify_blink_rate(struct sso_led_priv *priv, u32 rate)
+{
+	int i;
+
+	for (i = 0; i < MAX_FREQ_RANK; i++) {
+		if (rate <= priv->freq[i])
+			return i;
+	}
+
+	return i - 1;
+}
+
+static unsigned int sso_led_pin_to_group(u32 pin)
+{
+	if (pin < LED_GRP0_PIN_MAX)
+		return LED_GRP0_0_23;
+	else if (pin < LED_GRP1_PIN_MAX)
+		return LED_GRP1_24_28;
+	else
+		return LED_GRP2_29_31;
+}
+
+static u32 sso_led_get_freq_src(int freq_idx)
+{
+	if (freq_idx < MAX_FPID_FREQ_RANK)
+		return CLK_SRC_FPID;
+	else if (freq_idx < MAX_GPTC_FREQ_RANK)
+		return CLK_SRC_GPTC;
+	else
+		return CLK_SRC_GPTC_HS;
+}
+
+static u32 sso_led_pin_blink_off(u32 pin, unsigned int group)
+{
+	if (group == LED_GRP2_29_31)
+		return pin - LED_GRP1_PIN_MAX;
+	else if (group == LED_GRP1_24_28)
+		return pin - LED_GRP0_PIN_MAX;
+	else	/* led 0 - 23 in led 32 location */
+		return SSO_LED_MAX_NUM - LED_GRP1_PIN_MAX;
+}
+
+static struct sso_led
+*cdev_to_sso_led_data(struct led_classdev *led_cdev)
+{
+	return container_of(led_cdev, struct sso_led, cdev);
+}
+
+static void
+sso_led_set_blink(struct sso_led_priv *priv, u32 pin, bool set)
+{
+	sso_led_update_bit(priv->mmap, SSO_CON2, pin, set);
+}
+
+static void
+sso_led_set_hw_trigger(struct sso_led_priv *priv, u32 pin, bool set)
+{
+	sso_led_update_bit(priv->mmap, SSO_CON3, pin, set);
+}
+
+static void sso_led_freq_set(struct sso_led_priv *priv, u32 pin, int freq_idx)
+{
+	unsigned int group;
+	u32 reg, off, freq_src, val;
+
+	group = sso_led_pin_to_group(pin);
+	freq_src = sso_led_get_freq_src(freq_idx);
+	off = sso_led_pin_blink_off(pin, group);
+	reg = (group == LED_GRP1_24_28) ? LED_BLINK_H8_0 : LED_BLINK_H8_1;
+
+	/* set blink rate idx */
+	val = freq_idx;
+	if (freq_src != CLK_SRC_GPTC_HS)
+		sso_led_write_mask(priv->mmap, reg,
+				   BLINK_FREQ_OFF(off, freq_src),
+				   BLINK_FREQ_MASK, val);
+
+	/* select clock source */
+	sso_led_write_mask(priv->mmap, reg,
+			   BLINK_SRC_OFF(off, freq_src),
+			   BLINK_SRC_MASK, freq_src);
+}
+
+static void sso_led_brightness_set(struct led_classdev *led_cdev,
+				   enum led_brightness brightness)
+{
+	struct sso_led *led;
+	struct sso_led_priv *priv;
+	struct sso_led_desc *desc;
+	int val;
+
+	led = cdev_to_sso_led_data(led_cdev);
+	priv = led->priv;
+	desc = &led->desc;
+
+	desc->brightness = brightness;
+	sso_led_writel(priv->mmap, DUTY_CYCLE(desc->pin), brightness);
+
+	if (brightness == LED_OFF)
+		val = 0;
+	else
+		val = 1;
+
+	/* HW blink off */
+	if (desc->hw_blink && !val && desc->blinking) {
+		desc->blinking = 0;
+		sso_led_set_blink(priv, desc->pin, 0);
+	} else if (desc->hw_blink && val && !desc->blinking) {
+		desc->blinking = 1;
+		sso_led_set_blink(priv, desc->pin, 1);
+	}
+
+	if (!desc->hw_trig && led->gpiod)
+		gpiod_set_value(led->gpiod, val);
+}
+
+static enum led_brightness sso_led_brightness_get(struct led_classdev *led_cdev)
+{
+	struct sso_led *led;
+
+	led = cdev_to_sso_led_data(led_cdev);
+
+	return (enum led_brightness)led->desc.brightness;
+}
+
+static u32
+delay_to_freq_idx(struct sso_led *led,
+		  unsigned long *delay_on, unsigned long *delay_off)
+{
+	u32 freq_idx, freq;
+	struct sso_led_priv *priv = led->priv;
+	unsigned long delay;
+
+	if (!*delay_on && !*delay_off) {
+		*delay_on = *delay_off = (1000 / priv->freq[0]) / 2;
+		return 0;
+	}
+
+	delay = *delay_on + *delay_off;
+	freq = 1000 / delay;
+	freq_idx = sso_rectify_blink_rate(priv, freq);
+	delay = 1000 / priv->freq[freq_idx];
+	*delay_on = *delay_off = delay / 2;
+
+	if (!*delay_on)
+		*delay_on = *delay_off = 1;
+
+	return freq_idx;
+}
+
+static int sso_led_blink_set(struct led_classdev *led_cdev,
+			     unsigned long *delay_on,
+			     unsigned long *delay_off)
+{
+	struct sso_led *led;
+	struct sso_led_priv *priv;
+	u32 freq_idx;
+
+	led = cdev_to_sso_led_data(led_cdev);
+	priv = led->priv;
+	freq_idx = delay_to_freq_idx(led, delay_on, delay_off);
+
+	sso_led_freq_set(priv, led->desc.pin, freq_idx);
+	/* Enable HW blink */
+	sso_led_set_blink(priv, led->desc.pin, 1);
+	led->desc.freq_idx = freq_idx;
+	led->desc.blink_rate = priv->freq[freq_idx];
+	led->desc.blinking = 1;
+
+	return 1;
+}
+
+static void sso_led_hw_cfg(struct sso_led_priv *priv, struct sso_led *led)
+{
+	struct sso_led_desc *desc = &led->desc;
+
+	/* set freq */
+	if (desc->hw_blink) {
+		sso_led_freq_set(priv, desc->pin, desc->freq_idx);
+		sso_led_set_blink(priv, desc->pin, 1);
+	}
+
+	if (desc->hw_trig)
+		sso_led_set_hw_trigger(priv, desc->pin, 1);
+
+	/* set brightness */
+	sso_led_writel(priv->mmap, DUTY_CYCLE(desc->pin), desc->brightness);
+
+	/* enable output */
+	if (!desc->hw_trig && desc->brightness)
+		gpiod_set_value(led->gpiod, 1);
+}
+
+static int sso_create_led(struct sso_led_priv *priv, struct sso_led *led)
+{
+	struct sso_led_desc *desc = &led->desc;
+
+	led->cdev.name = desc->name;
+	led->cdev.default_trigger = desc->default_trigger;
+	led->cdev.brightness_set = sso_led_brightness_set;
+	led->cdev.brightness_get = sso_led_brightness_get;
+	led->cdev.brightness = desc->brightness;
+	led->cdev.max_brightness = BRIGHTNESS_MAX;
+
+	if (desc->retain_state_suspended)
+		led->cdev.flags |= LED_CORE_SUSPENDRESUME;
+	if (desc->panic_indicator)
+		led->cdev.flags |= LED_PANIC_INDICATOR;
+
+	if (desc->hw_blink)
+		led->cdev.blink_set = sso_led_blink_set;
+
+	sso_led_hw_cfg(priv, led);
+
+	if (devm_led_classdev_register(priv->dev, &led->cdev)) {
+		dev_err(priv->dev, "register led class %s failed!\n",
+			desc->name);
+		return -EINVAL;
+	}
+
+	list_add(&led->list, &priv->led_list);
+
+	return 0;
+}
+
+static int sso_led_dt_parse(struct sso_led_priv *priv)
+{
+	struct sso_led_desc *desc;
+	struct sso_led *led;
+	struct fwnode_handle *child;
+	struct device *dev = priv->dev;
+	int count;
+	u32 prop;
+
+	count = device_get_child_node_count(dev);
+	if (!count)
+		return 0;
+
+	device_for_each_child_node(dev, child) {
+		led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+		if (!led) {
+			dev_err(dev, "sso led!\n");
+			return -ENOMEM;
+		}
+
+		INIT_LIST_HEAD(&led->list);
+		led->priv = priv;
+		desc = &led->desc;
+
+		desc->np = to_of_node(child);
+		if (fwnode_property_read_string(child, "label", &desc->name)) {
+			dev_err(dev, "LED no label name!\n");
+			goto __dt_err;
+		}
+
+		/**
+		 * v4.11
+		 * update to devm_fwnode_get_gpiod_from_child
+		 */
+		led->gpiod = devm_get_gpiod_from_child(dev, "led", child);
+		if (IS_ERR(led->gpiod)) {
+			dev_err(dev, "led: %s get gpio fail!\n", desc->name);
+			goto __dt_err;
+		}
+
+		fwnode_property_read_string(child, "linux,default-trigger",
+					    &desc->default_trigger);
+
+		if (fwnode_property_present(child, "retain_state_suspended"))
+			desc->retain_state_suspended = 1;
+
+		if (fwnode_property_present(child, "retain-state-shutdown"))
+			desc->retain_state_shutdown = 1;
+
+		if (fwnode_property_present(child, "panic-indicator"))
+			desc->panic_indicator = 1;
+
+		if (fwnode_property_read_u32(child, "led-pin", &prop)) {
+			dev_err(dev, "Failed to find led pin id!\n");
+			goto __dt_err;
+		} else {
+			desc->pin = prop;
+			if (desc->pin >= SSO_LED_MAX_NUM) {
+				dev_err(dev, "invalid LED pin: %u\n",
+					desc->pin);
+				goto __dt_err;
+			}
+		}
+
+		if (fwnode_property_present(child, "sso,hw-blink"))
+			desc->hw_blink = 1;
+
+		if (fwnode_property_present(child, "sso,hw-trigger"))
+			desc->hw_trig = 1;
+
+		if (desc->hw_trig) {
+			desc->default_trigger = NULL;
+			desc->retain_state_shutdown = 0;
+			desc->retain_state_suspended = 0;
+			desc->panic_indicator = 0;
+			desc->hw_blink = 0;
+		}
+
+		if (fwnode_property_read_u32(child, "blink-rate", &prop)) {
+			/* default first freq rate */
+			desc->freq_idx = priv->cfg.blink_rate_idx;
+			desc->blink_rate = priv->freq[desc->freq_idx];
+		} else {
+			desc->freq_idx = sso_rectify_blink_rate(priv, prop);
+			desc->blink_rate = priv->freq[desc->freq_idx];
+		}
+
+		if (fwnode_property_read_u32(child, "brightness", &prop))
+			desc->brightness = priv->cfg.brightness;
+		else
+			desc->brightness = sso_rectify_brightness(prop);
+
+		if (sso_create_led(priv, led))
+			goto __dt_err;
+	}
+
+	return 0;
+
+__dt_err:
+	fwnode_handle_put(child);
+	return -EINVAL;
+}
+
+static int sso_dt_parse(struct sso_led_priv *priv)
+{
+	struct sso_cfg *cfg;
+	struct device *dev = priv->dev;
+	u32 prop;
+
+	cfg = &priv->cfg;
+
+	if (device_property_present(dev, "sso,falling"))
+		cfg->falling_trig = true;
+	else
+		cfg->falling_trig = false;
+
+	if (device_property_read_u32(dev, "sso,default-brightness", &prop)) {
+		cfg->brightness = SSO_DEF_BRIGHTNESS;
+	} else {
+		cfg->brightness = prop;
+		if (cfg->brightness > SSO_MAX_BRIGHTNESS)
+			cfg->brightness = SSO_MAX_BRIGHTNESS;
+	}
+
+	if (device_property_read_u32(dev, "sso,default-blinkrate", &prop))
+		cfg->blink_rate_idx = 0;
+	else
+		cfg->blink_rate_idx = sso_rectify_blink_rate(priv, prop);
+
+	if (sso_led_hw_init(priv)) {
+		dev_err(dev, "led HW init fail!\n");
+		return -EINVAL;
+	}
+
+	if (sso_led_dt_parse(priv))
+		return -EINVAL;
+
+	return 0;
+}
+
+static void sso_init_freq(struct sso_led_priv *priv)
+{
+	int i;
+	struct sso_cfg *cfg = &priv->cfg;
+
+	for (i = 0; i < MAX_FREQ_RANK; i++) {
+		if (i < MAX_FPID_FREQ_RANK) {
+			priv->freq[i] = cfg->fpid_clkrate / freq_div_tbl[i];
+		} else if (i < MAX_GPTC_FREQ_RANK) {
+			priv->freq[i] = cfg->gptc_clkrate /
+				freq_div_tbl[i - MAX_FPID_FREQ_RANK];
+		} else if (i < MAX_GPTC_HS_FREQ_RANK) {
+			priv->freq[i] = cfg->gptc_clkrate;
+		}
+	}
+
+	/* debug dump */
+	for (i = 0; i < MAX_FREQ_RANK; i++)
+		dev_dbg(priv->dev, "SSO freq[%d]: %u\n", i, priv->freq[i]);
+}
+
+static void sso_led_shutdown(struct sso_led *led)
+{
+	struct sso_led_priv *priv;
+
+	priv = led->priv;
+
+	/* unregister led */
+	devm_led_classdev_unregister(priv->dev, &led->cdev);
+
+	/* turn off led */
+	if (!led->desc.retain_state_shutdown)
+		sso_led_brightness_set(&led->cdev, LED_OFF);
+
+	/* clear HW control bit */
+	if (led->desc.hw_trig)
+		sso_led_set_hw_trigger(priv, led->desc.pin, 0);
+
+	if (led->gpiod)
+		devm_gpiod_put(priv->dev, led->gpiod);
+
+	led->priv = NULL;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+
+static ssize_t
+sso_led_create_write(struct file *s, const char __user *buffer,
+		     size_t count, loff_t *pos)
+{
+	char buf[128] = {0};
+	char *start, *led_name;
+	u32 pin, blink_rate;
+	struct sso_led_priv *priv;
+	struct sso_led *led;
+	struct sso_led_desc *desc;
+	struct device *dev;
+	int i;
+
+	priv = file_inode(s)->i_private;
+	dev = priv->dev;
+	copy_from_user(buf, buffer, sizeof(buf) - 1);
+
+	if (strcmp(buf, "help") == 0)
+		goto __create_help;
+
+	for (i = 0; buf[i] != ' ' && buf[i] != 0; i++)
+		;
+	if (buf[i] == 0)
+		goto __create_help;
+	buf[i++] = 0;
+	start = &buf[i];
+	for (; buf[i] >= '0' && buf[i] <= '9'; i++)
+		;
+	buf[i] = 0;
+
+	if (kstrtou32(buf, 0, &pin)) {
+		dev_err(dev, "Failed to parse led pin!\n");
+		goto __create_help;
+	}
+
+	if (kstrtou32(start, 0, &blink_rate)) {
+		dev_err(dev, "Failed to parse led blink rate!\n");
+		goto __create_help;
+	}
+
+	if (pin >= SSO_LED_MAX_NUM)
+		return -EINVAL;
+
+	led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&led->list);
+	led->priv = priv;
+	desc = &led->desc;
+
+	led_name = devm_kmalloc(dev, 32, GFP_KERNEL);
+	if (!led_name)
+		return -ENOMEM;
+
+	sprintf(led_name, "sso-led-%u", pin);
+	desc->name = (const char *)led_name;
+	desc->hw_blink = 1;
+	desc->freq_idx = sso_rectify_blink_rate(priv, blink_rate);
+	desc->blink_rate = priv->freq[desc->freq_idx];
+	desc->brightness = 0;
+	desc->pin = pin;
+	desc->hw_trig = 0;
+
+	if (sso_create_led(priv, led))
+		return -EINVAL;
+
+	return count;
+
+__create_help:
+	dev_info(priv->dev, "echo <pin> <blink rate> > create\n");
+	return count;
+}
+
+static const struct file_operations sso_led_create_fops = {
+	.write = sso_led_create_write,
+	.llseek = noop_llseek,
+};
+
+static ssize_t
+sso_led_delete_write(struct file *s, const char __user *buffer,
+		     size_t count, loff_t *pos)
+{
+	char buf[32] = {0};
+	u32 pin;
+	struct sso_led_priv *priv;
+	struct list_head *p, *n;
+	struct sso_led *led;
+	int i;
+
+	priv = file_inode(s)->i_private;
+	copy_from_user(buf, buffer, sizeof(buf) - 1);
+
+	if (strcmp(buf, "help") == 0)
+		goto __delete_err;
+
+	for (i = 0; buf[i] >= '0' && buf[i] <= '9'; i++)
+		;
+	buf[i] = 0;
+
+	if (kstrtou32(buf, 0, &pin))
+		goto __delete_err;
+
+	list_for_each_safe(p, n, &priv->led_list) {
+		led = list_entry(p, struct sso_led, list);
+		if (led->desc.pin == pin && !led->gpiod) {
+			list_del(p);
+			sso_led_shutdown(led);
+			devm_kfree(priv->dev, (void *)led->desc.name);
+			devm_kfree(priv->dev, led);
+			break;
+		}
+	}
+
+	return count;
+
+__delete_err:
+	dev_info(priv->dev, "echo <pin> > delete\n");
+	return count;
+}
+
+static const struct file_operations sso_led_delete_fops = {
+	.write = sso_led_delete_write,
+	.llseek = noop_llseek,
+};
+
+static void *sso_led_show_seq_start(struct seq_file *s, loff_t *pos)
+{
+	struct sso_led_priv *priv = s->private;
+
+	return seq_list_start(&priv->led_list, *pos);
+}
+
+static void *sso_led_show_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+	struct sso_led_priv *priv = s->private;
+
+	return seq_list_next(v, &priv->led_list, pos);
+}
+
+static void sso_led_show_seq_stop(struct seq_file *s, void *v)
+{
+}
+
+static int sso_led_show_seq_show(struct seq_file *s, void *v)
+{
+	struct list_head *p = v;
+	struct sso_led *led;
+	struct sso_led_desc *desc;
+
+	led = list_entry(p, struct sso_led, list);
+	desc = &led->desc;
+
+	seq_printf(s, "-------------%s-----------------------\n", desc->name);
+	seq_printf(s, "pin: %u\n", desc->pin);
+	seq_printf(s, "brightness : %u\n", desc->brightness);
+	seq_printf(s, "blink rate: %u\n", desc->blink_rate);
+	if (desc->default_trigger)
+		seq_printf(s, "default trigger: %s\n", desc->default_trigger);
+	seq_printf(s, "%s driven LED\n", (!desc->hw_trig) ? "SW" : "HW");
+	seq_printf(s, "%s blinking\n", (!desc->hw_blink) ? "SW" : "HW");
+	seq_printf(s, "blinking status: %s\n", (desc->blinking) ? "Yes" : "No");
+
+	return 0;
+}
+
+static const struct seq_operations sso_led_show_seq_ops = {
+	.start = sso_led_show_seq_start,
+	.next = sso_led_show_seq_next,
+	.stop = sso_led_show_seq_stop,
+	.show = sso_led_show_seq_show,
+};
+
+static int sso_led_show_seq_open(struct inode *inode, struct file *file)
+{
+	struct seq_file *s;
+	int ret;
+
+	ret = seq_open(file, &sso_led_show_seq_ops);
+	if (!ret) {
+		s = file->private_data;
+		s->private = inode->i_private;
+	}
+
+	return ret;
+}
+
+static const struct file_operations sso_led_show_fops = {
+	.open = sso_led_show_seq_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = seq_release,
+};
+
+static int sso_led_proc_init(struct sso_led_priv *priv)
+{
+	char sso_led_dir[64] = {0};
+	struct dentry *file;
+
+	strlcpy(sso_led_dir, priv->dev->of_node->name, sizeof(sso_led_dir));
+	priv->debugfs = debugfs_create_dir(sso_led_dir, NULL);
+
+	if (!priv->debugfs)
+		return -ENOMEM;
+
+	file = debugfs_create_file("create", 0644, priv->debugfs,
+				   priv, &sso_led_create_fops);
+	if (!file)
+		goto __proc_err;
+
+	file = debugfs_create_file("delete", 0644, priv->debugfs,
+				   priv, &sso_led_delete_fops);
+	if (!file)
+		goto __proc_err;
+
+	file = debugfs_create_file("show", 0644, priv->debugfs,
+				   priv, &sso_led_show_fops);
+	if (!file)
+		goto __proc_err;
+
+	return 0;
+
+__proc_err:
+	debugfs_remove_recursive(priv->debugfs);
+	return -ENOMEM;
+}
+
+static void sso_led_proc_exit(struct sso_led_priv *priv)
+{
+	debugfs_remove_recursive(priv->debugfs);
+	priv->debugfs = NULL;
+}
+#else
+static int sso_led_proc_init(struct sso_led_priv *priv)
+{
+	return 0;
+}
+
+static void sso_led_proc_exit(struct sso_led_priv *priv)
+{
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+static int intel_sso_led_probe(struct platform_device *pdev)
+{
+	struct sso_led_priv *priv;
+	struct device *dev = &pdev->dev;
+	struct sso_cfg *cfg;
+	u32 prop;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&priv->led_list);
+
+	/* led mem */
+	priv->mmap = syscon_node_to_regmap(dev->parent->of_node);
+	if (IS_ERR_OR_NULL(priv->mmap)) {
+		dev_err(dev, "Failed to get sso led iomem");
+		return PTR_ERR(priv->mmap);
+	}
+
+	cfg = &priv->cfg;
+	cfg->fpid_clk = devm_clk_get(dev, "fpid");
+	if (IS_ERR(cfg->fpid_clk)) {
+		dev_err(dev, "Failed to get fpid clock!\n");
+		goto __hw_err;
+	}
+	if (clk_prepare_enable(cfg->fpid_clk)) {
+		dev_err(dev, "Failed to enable fpid clock!\n");
+		goto __hw_err;
+	}
+	cfg->fpid_clkrate = clk_get_rate(cfg->fpid_clk);
+
+	if (device_property_read_u32(dev, "intel,gptc-clkrate", &prop)) {
+		dev_err(dev, "Failed to get gptc clock rate");
+		goto __hw_err;
+	} else {
+		cfg->gptc_clkrate = prop;
+	}
+
+	priv->dev = dev;
+	priv->pdev = pdev;
+	platform_set_drvdata(pdev, priv);
+
+	sso_init_freq(priv);
+	if (sso_dt_parse(priv))
+		goto __hw_err;
+
+	if (sso_led_proc_init(priv))
+		goto __hw_err;
+
+	return 0;
+
+__hw_err:
+	regmap_exit(priv->mmap);
+	return -EINVAL;
+}
+
+static int intel_sso_led_remove(struct platform_device *pdev)
+{
+	struct sso_led_priv *priv;
+	struct sso_led *led;
+	struct list_head *pos, *n;
+
+	priv = platform_get_drvdata(pdev);
+
+	list_for_each_safe(pos, n, &priv->led_list) {
+		list_del(pos);
+		led = list_entry(pos, struct sso_led, list);
+		sso_led_shutdown(led);
+	}
+
+	clk_disable_unprepare(priv->cfg.fpid_clk);
+	regmap_exit(priv->mmap);
+
+	sso_led_proc_exit(priv);
+
+	return 0;
+}
+
+static const struct of_device_id of_sso_led_match[] = {
+	{ .compatible = "intel,sso-led" },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, of_sso_led_match);
+
+static struct platform_driver intel_sso_led_driver = {
+	.probe		= intel_sso_led_probe,
+	.remove		= intel_sso_led_remove,
+	.driver		= {
+			.name = "intel-sso-led",
+			.of_match_table = of_match_ptr(of_sso_led_match),
+	},
+};
+
+module_platform_driver(intel_sso_led_driver);
+
+MODULE_AUTHOR("Zhu Yixin <Yixin.zhu@intel.com>");
+MODULE_DESCRIPTION("Intel SSO LED driver");
+MODULE_LICENSE("GPL v2");
-- 
GitLab