diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/leds/blink | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | drivers/leds/blink/Kconfig | 31 | ||||
-rw-r--r-- | drivers/leds/blink/Makefile | 3 | ||||
-rw-r--r-- | drivers/leds/blink/leds-bcm63138.c | 307 | ||||
-rw-r--r-- | drivers/leds/blink/leds-lgm-sso.c | 877 |
4 files changed, 1218 insertions, 0 deletions
diff --git a/drivers/leds/blink/Kconfig b/drivers/leds/blink/Kconfig new file mode 100644 index 000000000..945c84286 --- /dev/null +++ b/drivers/leds/blink/Kconfig @@ -0,0 +1,31 @@ +config LEDS_BCM63138 + tristate "LED Support for Broadcom BCM63138 SoC" + depends on LEDS_CLASS + depends on ARCH_BCM4908 || ARCH_BCM_5301X || BCM63XX || COMPILE_TEST + depends on HAS_IOMEM + depends on OF + default ARCH_BCM4908 + help + This option enables support for LED controller that is part of + BCM63138 SoC. The same hardware block is known to be also used + in BCM4908, BCM6848, BCM6858, BCM63148, BCM63381 and BCM68360. + + If compiled as module it will be called leds-bcm63138. + +config LEDS_LGM + tristate "LED support for LGM SoC series" + depends on X86 || COMPILE_TEST + depends on GPIOLIB && LEDS_CLASS && MFD_SYSCON && OF + help + This option enables support for LEDs connected to GPIO lines on + Lightning Mountain (LGM) SoC. Lightning Mountain is a AnyWAN + gateway-on-a-chip SoC to be shipped on mid and high end home + gateways and routers. + + These LEDs are driven by a Serial Shift Output (SSO) controller. + The driver supports hardware blinking and the LEDs can be configured + to be triggered by software/CPU or by hardware. + + Say 'Y' here if you are working on LGM SoC based platform. Otherwise, + say 'N'. To compile this driver as a module, choose M here: the module + will be called leds-lgm-sso. diff --git a/drivers/leds/blink/Makefile b/drivers/leds/blink/Makefile new file mode 100644 index 000000000..447029f41 --- /dev/null +++ b/drivers/leds/blink/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_LEDS_BCM63138) += leds-bcm63138.o +obj-$(CONFIG_LEDS_LGM) += leds-lgm-sso.o diff --git a/drivers/leds/blink/leds-bcm63138.c b/drivers/leds/blink/leds-bcm63138.c new file mode 100644 index 000000000..2cf2761e4 --- /dev/null +++ b/drivers/leds/blink/leds-bcm63138.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl> + */ +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#define BCM63138_MAX_LEDS 32 +#define BCM63138_MAX_BRIGHTNESS 9 + +#define BCM63138_LED_BITS 4 /* how many bits control a single LED */ +#define BCM63138_LED_MASK ((1 << BCM63138_LED_BITS) - 1) /* 0xf */ +#define BCM63138_LEDS_PER_REG (32 / BCM63138_LED_BITS) /* 8 */ + +#define BCM63138_GLB_CTRL 0x00 +#define BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL 0x00000002 +#define BCM63138_GLB_CTRL_SERIAL_LED_EN_POL 0x00000008 +#define BCM63138_MASK 0x04 +#define BCM63138_HW_LED_EN 0x08 +#define BCM63138_SERIAL_LED_SHIFT_SEL 0x0c +#define BCM63138_FLASH_RATE_CTRL1 0x10 +#define BCM63138_FLASH_RATE_CTRL2 0x14 +#define BCM63138_FLASH_RATE_CTRL3 0x18 +#define BCM63138_FLASH_RATE_CTRL4 0x1c +#define BCM63138_BRIGHT_CTRL1 0x20 +#define BCM63138_BRIGHT_CTRL2 0x24 +#define BCM63138_BRIGHT_CTRL3 0x28 +#define BCM63138_BRIGHT_CTRL4 0x2c +#define BCM63138_POWER_LED_CFG 0x30 +#define BCM63138_HW_POLARITY 0xb4 +#define BCM63138_SW_DATA 0xb8 +#define BCM63138_SW_POLARITY 0xbc +#define BCM63138_PARALLEL_LED_POLARITY 0xc0 +#define BCM63138_SERIAL_LED_POLARITY 0xc4 +#define BCM63138_HW_LED_STATUS 0xc8 +#define BCM63138_FLASH_CTRL_STATUS 0xcc +#define BCM63138_FLASH_BRT_CTRL 0xd0 +#define BCM63138_FLASH_P_LED_OUT_STATUS 0xd4 +#define BCM63138_FLASH_S_LED_OUT_STATUS 0xd8 + +struct bcm63138_leds { + struct device *dev; + void __iomem *base; + spinlock_t lock; +}; + +struct bcm63138_led { + struct bcm63138_leds *leds; + struct led_classdev cdev; + u32 pin; + bool active_low; +}; + +/* + * I/O access + */ + +static void bcm63138_leds_write(struct bcm63138_leds *leds, unsigned int reg, + u32 data) +{ + writel(data, leds->base + reg); +} + +static unsigned long bcm63138_leds_read(struct bcm63138_leds *leds, + unsigned int reg) +{ + return readl(leds->base + reg); +} + +static void bcm63138_leds_update_bits(struct bcm63138_leds *leds, + unsigned int reg, u32 mask, u32 val) +{ + WARN_ON(val & ~mask); + + bcm63138_leds_write(leds, reg, (bcm63138_leds_read(leds, reg) & ~mask) | (val & mask)); +} + +/* + * Helpers + */ + +static void bcm63138_leds_set_flash_rate(struct bcm63138_leds *leds, + struct bcm63138_led *led, + u8 value) +{ + int reg_offset = (led->pin >> fls((BCM63138_LEDS_PER_REG - 1))) * 4; + int shift = (led->pin & (BCM63138_LEDS_PER_REG - 1)) * BCM63138_LED_BITS; + + bcm63138_leds_update_bits(leds, BCM63138_FLASH_RATE_CTRL1 + reg_offset, + BCM63138_LED_MASK << shift, value << shift); +} + +static void bcm63138_leds_set_bright(struct bcm63138_leds *leds, + struct bcm63138_led *led, + u8 value) +{ + int reg_offset = (led->pin >> fls((BCM63138_LEDS_PER_REG - 1))) * 4; + int shift = (led->pin & (BCM63138_LEDS_PER_REG - 1)) * BCM63138_LED_BITS; + + bcm63138_leds_update_bits(leds, BCM63138_BRIGHT_CTRL1 + reg_offset, + BCM63138_LED_MASK << shift, value << shift); +} + +static void bcm63138_leds_enable_led(struct bcm63138_leds *leds, + struct bcm63138_led *led, + enum led_brightness value) +{ + u32 bit = BIT(led->pin); + + bcm63138_leds_update_bits(leds, BCM63138_SW_DATA, bit, value ? bit : 0); +} + +/* + * API callbacks + */ + +static void bcm63138_leds_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev); + struct bcm63138_leds *leds = led->leds; + unsigned long flags; + + spin_lock_irqsave(&leds->lock, flags); + + bcm63138_leds_enable_led(leds, led, value); + if (!value) + bcm63138_leds_set_flash_rate(leds, led, 0); + else + bcm63138_leds_set_bright(leds, led, value); + + spin_unlock_irqrestore(&leds->lock, flags); +} + +static int bcm63138_leds_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev); + struct bcm63138_leds *leds = led->leds; + unsigned long flags; + u8 value; + + if (!*delay_on && !*delay_off) { + *delay_on = 640; + *delay_off = 640; + } + + if (*delay_on != *delay_off) { + dev_dbg(led_cdev->dev, "Blinking at unequal delays is not supported\n"); + return -EINVAL; + } + + switch (*delay_on) { + case 1152 ... 1408: /* 1280 ms ± 10% */ + value = 0x7; + break; + case 576 ... 704: /* 640 ms ± 10% */ + value = 0x6; + break; + case 288 ... 352: /* 320 ms ± 10% */ + value = 0x5; + break; + case 126 ... 154: /* 140 ms ± 10% */ + value = 0x4; + break; + case 59 ... 72: /* 65 ms ± 10% */ + value = 0x3; + break; + default: + dev_dbg(led_cdev->dev, "Blinking delay value %lu is unsupported\n", + *delay_on); + return -EINVAL; + } + + spin_lock_irqsave(&leds->lock, flags); + + bcm63138_leds_enable_led(leds, led, BCM63138_MAX_BRIGHTNESS); + bcm63138_leds_set_flash_rate(leds, led, value); + + spin_unlock_irqrestore(&leds->lock, flags); + + return 0; +} + +/* + * LED driver + */ + +static void bcm63138_leds_create_led(struct bcm63138_leds *leds, + struct device_node *np) +{ + struct led_init_data init_data = { + .fwnode = of_fwnode_handle(np), + }; + struct device *dev = leds->dev; + struct bcm63138_led *led; + struct pinctrl *pinctrl; + u32 bit; + int err; + + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); + if (!led) { + dev_err(dev, "Failed to alloc LED\n"); + return; + } + + led->leds = leds; + + if (of_property_read_u32(np, "reg", &led->pin)) { + dev_err(dev, "Missing \"reg\" property in %pOF\n", np); + goto err_free; + } + + if (led->pin >= BCM63138_MAX_LEDS) { + dev_err(dev, "Invalid \"reg\" value %d\n", led->pin); + goto err_free; + } + + led->active_low = of_property_read_bool(np, "active-low"); + + led->cdev.max_brightness = BCM63138_MAX_BRIGHTNESS; + led->cdev.brightness_set = bcm63138_leds_brightness_set; + led->cdev.blink_set = bcm63138_leds_blink_set; + + err = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); + if (err) { + dev_err(dev, "Failed to register LED %pOF: %d\n", np, err); + goto err_free; + } + + pinctrl = devm_pinctrl_get_select_default(led->cdev.dev); + if (IS_ERR(pinctrl) && PTR_ERR(pinctrl) != -ENODEV) { + dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %ld\n", + np, PTR_ERR(pinctrl)); + } + + bit = BIT(led->pin); + bcm63138_leds_update_bits(leds, BCM63138_PARALLEL_LED_POLARITY, bit, + led->active_low ? 0 : bit); + bcm63138_leds_update_bits(leds, BCM63138_HW_LED_EN, bit, 0); + bcm63138_leds_set_flash_rate(leds, led, 0); + bcm63138_leds_enable_led(leds, led, led->cdev.brightness); + + return; + +err_free: + devm_kfree(dev, led); +} + +static int bcm63138_leds_probe(struct platform_device *pdev) +{ + struct device_node *np = dev_of_node(&pdev->dev); + struct device *dev = &pdev->dev; + struct bcm63138_leds *leds; + struct device_node *child; + + leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL); + if (!leds) + return -ENOMEM; + + leds->dev = dev; + + leds->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(leds->base)) + return PTR_ERR(leds->base); + + spin_lock_init(&leds->lock); + + bcm63138_leds_write(leds, BCM63138_GLB_CTRL, + BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL | + BCM63138_GLB_CTRL_SERIAL_LED_EN_POL); + bcm63138_leds_write(leds, BCM63138_HW_LED_EN, 0); + bcm63138_leds_write(leds, BCM63138_SERIAL_LED_POLARITY, 0); + bcm63138_leds_write(leds, BCM63138_PARALLEL_LED_POLARITY, 0); + + for_each_available_child_of_node(np, child) { + bcm63138_leds_create_led(leds, child); + } + + return 0; +} + +static const struct of_device_id bcm63138_leds_of_match_table[] = { + { .compatible = "brcm,bcm63138-leds", }, + { }, +}; + +static struct platform_driver bcm63138_leds_driver = { + .probe = bcm63138_leds_probe, + .driver = { + .name = "leds-bcm63xxx", + .of_match_table = bcm63138_leds_of_match_table, + }, +}; + +module_platform_driver(bcm63138_leds_driver); + +MODULE_AUTHOR("Rafał Miłecki"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, bcm63138_leds_of_match_table); diff --git a/drivers/leds/blink/leds-lgm-sso.c b/drivers/leds/blink/leds-lgm-sso.c new file mode 100644 index 000000000..6f270c027 --- /dev/null +++ b/drivers/leds/blink/leds-lgm-sso.c @@ -0,0 +1,877 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Lightning Mountain SoC LED Serial Shift Output Controller driver + * + * Copyright (c) 2020 Intel Corporation. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/sizes.h> +#include <linux/uaccess.h> + +#define SSO_DEV_NAME "lgm-sso" + +#define LED_BLINK_H8_0 0x0 +#define LED_BLINK_H8_1 0x4 +#define GET_FREQ_OFFSET(pin, src) (((pin) * 6) + ((src) * 2)) +#define GET_SRC_OFFSET(pinc) (((pin) * 6) + 4) + +#define DUTY_CYCLE(x) (0x8 + ((x) * 4)) +#define SSO_CON0 0x2B0 +#define SSO_CON0_RZFL BIT(26) +#define SSO_CON0_BLINK_R BIT(30) +#define SSO_CON0_SWU BIT(31) + +#define SSO_CON1 0x2B4 +#define SSO_CON1_FCDSC GENMASK(21, 20) /* Fixed Divider Shift Clock */ +#define SSO_CON1_FPID GENMASK(24, 23) +#define SSO_CON1_GPTD GENMASK(26, 25) +#define SSO_CON1_US GENMASK(31, 30) + +#define SSO_CPU 0x2B8 +#define SSO_CON2 0x2C4 +#define SSO_CON3 0x2C8 + +/* Driver MACRO */ +#define MAX_PIN_NUM_PER_BANK SZ_32 +#define MAX_GROUP_NUM SZ_4 +#define PINS_PER_GROUP SZ_8 +#define FPID_FREQ_RANK_MAX SZ_4 +#define SSO_LED_MAX_NUM SZ_32 +#define MAX_FREQ_RANK 10 +#define DEF_GPTC_CLK_RATE 200000000 +#define SSO_DEF_BRIGHTNESS LED_HALF +#define DATA_CLK_EDGE 0 /* 0-rising, 1-falling */ + +static const u32 freq_div_tbl[] = {4000, 2000, 1000, 800}; +static const int freq_tbl[] = {2, 4, 8, 10, 50000, 100000, 200000, 250000}; +static const int shift_clk_freq_tbl[] = {25000000, 12500000, 6250000, 3125000}; + +/* + * Update Source to update the SOUTs + * SW - Software has to update the SWU bit + * GPTC - General Purpose timer is used as clock source + * FPID - Divided FSC clock (FPID) is used as clock source + */ +enum { + US_SW = 0, + US_GPTC = 1, + US_FPID = 2 +}; + +enum { + MAX_FPID_FREQ_RANK = 5, /* 1 to 4 */ + MAX_GPTC_FREQ_RANK = 9, /* 5 to 8 */ + MAX_GPTC_HS_FREQ_RANK = 10, /* 9 to 10 */ +}; + +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, +}; + +struct sso_led_priv; + +struct sso_led_desc { + const char *name; + const char *default_trigger; + unsigned int brightness; + unsigned int blink_rate; + 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; + int freq_idx; + u32 pin; +}; + +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; +}; + +struct sso_gpio { + struct gpio_chip chip; + int shift_clk_freq; + int edge; + int freq; + u32 pins; + u32 alloc_bitmap; +}; + +struct sso_led_priv { + struct regmap *mmap; + struct device *dev; + struct platform_device *pdev; + struct clk_bulk_data clocks[2]; + u32 fpid_clkrate; + u32 gptc_clkrate; + u32 freq[MAX_FREQ_RANK]; + struct list_head led_list; + struct sso_gpio gpio; +}; + +static int sso_get_blink_rate_idx(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 -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_freq_set(struct sso_led_priv *priv, u32 pin, int freq_idx) +{ + u32 reg, off, freq_src, val_freq; + u32 low, high, val; + unsigned int group; + + if (!freq_idx) + return; + + group = sso_led_pin_to_group(pin); + freq_src = sso_led_get_freq_src(freq_idx); + off = sso_led_pin_blink_off(pin, group); + + if (group == LED_GRP0_0_23) + return; + else if (group == LED_GRP1_24_28) + reg = LED_BLINK_H8_0; + else + reg = LED_BLINK_H8_1; + + if (freq_src == CLK_SRC_FPID) + val_freq = freq_idx - 1; + else if (freq_src == CLK_SRC_GPTC) + val_freq = freq_idx - MAX_FPID_FREQ_RANK; + + /* set blink rate idx */ + if (freq_src != CLK_SRC_GPTC_HS) { + low = GET_FREQ_OFFSET(off, freq_src); + high = low + 2; + val = val_freq << high; + regmap_update_bits(priv->mmap, reg, GENMASK(high, low), val); + } + + /* select clock source */ + low = GET_SRC_OFFSET(off); + high = low + 2; + val = freq_src << high; + regmap_update_bits(priv->mmap, reg, GENMASK(high, low), val); +} + +static void sso_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct sso_led_priv *priv; + struct sso_led_desc *desc; + struct sso_led *led; + int val; + + led = cdev_to_sso_led_data(led_cdev); + priv = led->priv; + desc = &led->desc; + + desc->brightness = brightness; + regmap_write(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; + regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin), 0); + } else if (desc->hw_blink && val && !desc->blinking) { + desc->blinking = 1; + regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin), + 1 << desc->pin); + } + + if (!desc->hw_trig) + gpiod_set_value(led->gpiod, val); +} + +static enum led_brightness sso_led_brightness_get(struct led_classdev *led_cdev) +{ + struct sso_led *led = cdev_to_sso_led_data(led_cdev); + + return (enum led_brightness)led->desc.brightness; +} + +static int +delay_to_freq_idx(struct sso_led *led, unsigned long *delay_on, + unsigned long *delay_off) +{ + struct sso_led_priv *priv = led->priv; + unsigned long delay; + int freq_idx; + u32 freq; + + 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_get_blink_rate_idx(priv, freq); + if (freq_idx == -1) + freq_idx = MAX_FREQ_RANK - 1; + + 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_priv *priv; + struct sso_led *led; + int 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); + regmap_update_bits(priv->mmap, SSO_CON2, BIT(led->desc.pin), + 1 << led->desc.pin); + 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); + regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin), + 1 << desc->pin); + } + + if (desc->hw_trig) + regmap_update_bits(priv->mmap, SSO_CON3, BIT(desc->pin), + 1 << desc->pin); + + /* set brightness */ + regmap_write(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 fwnode_handle *child) +{ + struct sso_led_desc *desc = &led->desc; + struct led_init_data init_data; + int err; + + init_data.fwnode = child; + init_data.devicename = SSO_DEV_NAME; + init_data.default_label = ":"; + + 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 = LED_FULL; + + if (desc->retain_state_shutdown) + led->cdev.flags |= LED_RETAIN_AT_SHUTDOWN; + 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); + + err = devm_led_classdev_register_ext(priv->dev, &led->cdev, &init_data); + if (err) + return err; + + list_add(&led->list, &priv->led_list); + + return 0; +} + +static void sso_init_freq(struct sso_led_priv *priv) +{ + int i; + + priv->freq[0] = 0; + for (i = 1; i < MAX_FREQ_RANK; i++) { + if (i < MAX_FPID_FREQ_RANK) { + priv->freq[i] = priv->fpid_clkrate / freq_div_tbl[i - 1]; + } else if (i < MAX_GPTC_FREQ_RANK) { + priv->freq[i] = priv->gptc_clkrate / + freq_div_tbl[i - MAX_FPID_FREQ_RANK]; + } else if (i < MAX_GPTC_HS_FREQ_RANK) { + priv->freq[i] = priv->gptc_clkrate; + } + } +} + +static int sso_gpio_request(struct gpio_chip *chip, unsigned int offset) +{ + struct sso_led_priv *priv = gpiochip_get_data(chip); + + if (priv->gpio.alloc_bitmap & BIT(offset)) + return -EINVAL; + + priv->gpio.alloc_bitmap |= BIT(offset); + regmap_write(priv->mmap, DUTY_CYCLE(offset), 0xFF); + + return 0; +} + +static void sso_gpio_free(struct gpio_chip *chip, unsigned int offset) +{ + struct sso_led_priv *priv = gpiochip_get_data(chip); + + priv->gpio.alloc_bitmap &= ~BIT(offset); + regmap_write(priv->mmap, DUTY_CYCLE(offset), 0x0); +} + +static int sso_gpio_get_dir(struct gpio_chip *chip, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int +sso_gpio_dir_out(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct sso_led_priv *priv = gpiochip_get_data(chip); + bool bit = !!value; + + regmap_update_bits(priv->mmap, SSO_CPU, BIT(offset), bit << offset); + if (!priv->gpio.freq) + regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_SWU, + SSO_CON0_SWU); + + return 0; +} + +static int sso_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct sso_led_priv *priv = gpiochip_get_data(chip); + u32 reg_val; + + regmap_read(priv->mmap, SSO_CPU, ®_val); + + return !!(reg_val & BIT(offset)); +} + +static void sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct sso_led_priv *priv = gpiochip_get_data(chip); + + regmap_update_bits(priv->mmap, SSO_CPU, BIT(offset), value << offset); + if (!priv->gpio.freq) + regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_SWU, + SSO_CON0_SWU); +} + +static int sso_gpio_gc_init(struct device *dev, struct sso_led_priv *priv) +{ + struct gpio_chip *gc = &priv->gpio.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->get = sso_gpio_get; + gc->set = sso_gpio_set; + + gc->label = "lgm-sso"; + gc->base = -1; + /* To exclude pins from control, use "gpio-reserved-ranges" */ + gc->ngpio = priv->gpio.pins; + gc->parent = dev; + gc->owner = THIS_MODULE; + + return devm_gpiochip_add_data(dev, gc, priv); +} + +static int sso_gpio_get_freq_idx(int freq) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(freq_tbl); idx++) { + if (freq <= freq_tbl[idx]) + return idx; + } + + return -1; +} + +static void sso_register_shift_clk(struct sso_led_priv *priv) +{ + int idx, size = ARRAY_SIZE(shift_clk_freq_tbl); + u32 val = 0; + + for (idx = 0; idx < size; idx++) { + if (shift_clk_freq_tbl[idx] <= priv->gpio.shift_clk_freq) { + val = idx; + break; + } + } + + if (idx == size) + dev_warn(priv->dev, "%s: Invalid freq %d\n", + __func__, priv->gpio.shift_clk_freq); + + regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_FCDSC, + FIELD_PREP(SSO_CON1_FCDSC, val)); +} + +static int sso_gpio_freq_set(struct sso_led_priv *priv) +{ + int freq_idx; + u32 val; + + freq_idx = sso_gpio_get_freq_idx(priv->gpio.freq); + if (freq_idx == -1) + freq_idx = ARRAY_SIZE(freq_tbl) - 1; + + val = freq_idx % FPID_FREQ_RANK_MAX; + + if (!priv->gpio.freq) { + regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R, 0); + regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US, + FIELD_PREP(SSO_CON1_US, US_SW)); + } else if (freq_idx < FPID_FREQ_RANK_MAX) { + regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R, + SSO_CON0_BLINK_R); + regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US, + FIELD_PREP(SSO_CON1_US, US_FPID)); + regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_FPID, + FIELD_PREP(SSO_CON1_FPID, val)); + } else { + regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R, + SSO_CON0_BLINK_R); + regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US, + FIELD_PREP(SSO_CON1_US, US_GPTC)); + regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_GPTD, + FIELD_PREP(SSO_CON1_GPTD, val)); + } + + return 0; +} + +static int sso_gpio_hw_init(struct sso_led_priv *priv) +{ + u32 activate; + int i, err; + + /* Clear all duty cycles */ + for (i = 0; i < priv->gpio.pins; i++) { + err = regmap_write(priv->mmap, DUTY_CYCLE(i), 0); + if (err) + return err; + } + + /* 4 groups for total 32 pins */ + for (i = 1; i <= MAX_GROUP_NUM; i++) { + activate = !!(i * PINS_PER_GROUP <= priv->gpio.pins || + priv->gpio.pins > (i - 1) * PINS_PER_GROUP); + err = regmap_update_bits(priv->mmap, SSO_CON1, BIT(i - 1), + activate << (i - 1)); + if (err) + return err; + } + + /* NO HW directly controlled pin by default */ + err = regmap_write(priv->mmap, SSO_CON3, 0); + if (err) + return err; + + /* NO BLINK for all pins */ + err = regmap_write(priv->mmap, SSO_CON2, 0); + if (err) + return err; + + /* OUTPUT 0 by default */ + err = regmap_write(priv->mmap, SSO_CPU, 0); + if (err) + return err; + + /* update edge */ + err = regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_RZFL, + FIELD_PREP(SSO_CON0_RZFL, priv->gpio.edge)); + if (err) + return err; + + /* Set GPIO update rate */ + sso_gpio_freq_set(priv); + + /* Register shift clock */ + sso_register_shift_clk(priv); + + return 0; +} + +static void sso_led_shutdown(struct sso_led *led) +{ + struct sso_led_priv *priv = led->priv; + + /* unregister led */ + devm_led_classdev_unregister(priv->dev, &led->cdev); + + /* clear HW control bit */ + if (led->desc.hw_trig) + regmap_update_bits(priv->mmap, SSO_CON3, BIT(led->desc.pin), 0); + + led->priv = NULL; +} + +static int +__sso_led_dt_parse(struct sso_led_priv *priv, struct fwnode_handle *fw_ssoled) +{ + struct fwnode_handle *fwnode_child; + struct device *dev = priv->dev; + struct sso_led_desc *desc; + struct sso_led *led; + const char *tmp; + u32 prop; + int ret; + + fwnode_for_each_child_node(fw_ssoled, fwnode_child) { + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); + if (!led) { + ret = -ENOMEM; + goto __dt_err; + } + + INIT_LIST_HEAD(&led->list); + led->priv = priv; + desc = &led->desc; + + led->gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, + fwnode_child, + GPIOD_ASIS, NULL); + if (IS_ERR(led->gpiod)) { + ret = dev_err_probe(dev, PTR_ERR(led->gpiod), "led: get gpio fail!\n"); + goto __dt_err; + } + + fwnode_property_read_string(fwnode_child, + "linux,default-trigger", + &desc->default_trigger); + + if (fwnode_property_present(fwnode_child, + "retain-state-suspended")) + desc->retain_state_suspended = 1; + + if (fwnode_property_present(fwnode_child, + "retain-state-shutdown")) + desc->retain_state_shutdown = 1; + + if (fwnode_property_present(fwnode_child, "panic-indicator")) + desc->panic_indicator = 1; + + ret = fwnode_property_read_u32(fwnode_child, "reg", &prop); + if (ret) + goto __dt_err; + if (prop >= SSO_LED_MAX_NUM) { + dev_err(dev, "invalid LED pin:%u\n", prop); + ret = -EINVAL; + goto __dt_err; + } + desc->pin = prop; + + if (fwnode_property_present(fwnode_child, "intel,sso-hw-blink")) + desc->hw_blink = 1; + + desc->hw_trig = fwnode_property_read_bool(fwnode_child, + "intel,sso-hw-trigger"); + 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(fwnode_child, + "intel,sso-blink-rate-hz", &prop)) { + /* default first freq rate */ + desc->freq_idx = 0; + desc->blink_rate = priv->freq[desc->freq_idx]; + } else { + desc->freq_idx = sso_get_blink_rate_idx(priv, prop); + if (desc->freq_idx == -1) + desc->freq_idx = MAX_FREQ_RANK - 1; + + desc->blink_rate = priv->freq[desc->freq_idx]; + } + + if (!fwnode_property_read_string(fwnode_child, "default-state", &tmp)) { + if (!strcmp(tmp, "on")) + desc->brightness = LED_FULL; + } + + ret = sso_create_led(priv, led, fwnode_child); + if (ret) + goto __dt_err; + } + + return 0; + +__dt_err: + fwnode_handle_put(fwnode_child); + /* unregister leds */ + list_for_each_entry(led, &priv->led_list, list) + sso_led_shutdown(led); + + return ret; +} + +static int sso_led_dt_parse(struct sso_led_priv *priv) +{ + struct fwnode_handle *fwnode = dev_fwnode(priv->dev); + struct fwnode_handle *fw_ssoled; + struct device *dev = priv->dev; + int count; + int ret; + + count = device_get_child_node_count(dev); + if (!count) + return 0; + + fw_ssoled = fwnode_get_named_child_node(fwnode, "ssoled"); + if (fw_ssoled) { + ret = __sso_led_dt_parse(priv, fw_ssoled); + fwnode_handle_put(fw_ssoled); + if (ret) + return ret; + } + + return 0; +} + +static int sso_probe_gpios(struct sso_led_priv *priv) +{ + struct device *dev = priv->dev; + int ret; + + if (device_property_read_u32(dev, "ngpios", &priv->gpio.pins)) + priv->gpio.pins = MAX_PIN_NUM_PER_BANK; + + if (priv->gpio.pins > MAX_PIN_NUM_PER_BANK) + return -EINVAL; + + if (device_property_read_u32(dev, "intel,sso-update-rate-hz", + &priv->gpio.freq)) + priv->gpio.freq = 0; + + priv->gpio.edge = DATA_CLK_EDGE; + priv->gpio.shift_clk_freq = -1; + + ret = sso_gpio_hw_init(priv); + if (ret) + return ret; + + return sso_gpio_gc_init(dev, priv); +} + +static void sso_clock_disable_unprepare(void *data) +{ + struct sso_led_priv *priv = data; + + clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clocks), priv->clocks); +} + +static int intel_sso_led_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sso_led_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdev = pdev; + priv->dev = dev; + + /* gate clock */ + priv->clocks[0].id = "sso"; + + /* fpid clock */ + priv->clocks[1].id = "fpid"; + + ret = devm_clk_bulk_get(dev, ARRAY_SIZE(priv->clocks), priv->clocks); + if (ret) { + dev_err(dev, "Getting clocks failed!\n"); + return ret; + } + + ret = clk_bulk_prepare_enable(ARRAY_SIZE(priv->clocks), priv->clocks); + if (ret) { + dev_err(dev, "Failed to prepare and enable clocks!\n"); + return ret; + } + + ret = devm_add_action_or_reset(dev, sso_clock_disable_unprepare, priv); + if (ret) + return ret; + + priv->fpid_clkrate = clk_get_rate(priv->clocks[1].clk); + + priv->mmap = syscon_node_to_regmap(dev->of_node); + + 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_probe_gpios(priv); + if (ret) { + regmap_exit(priv->mmap); + return ret; + } + + INIT_LIST_HEAD(&priv->led_list); + + platform_set_drvdata(pdev, priv); + sso_init_freq(priv); + + priv->gptc_clkrate = DEF_GPTC_CLK_RATE; + + ret = sso_led_dt_parse(priv); + if (ret) { + regmap_exit(priv->mmap); + return ret; + } + dev_info(priv->dev, "sso LED init success!\n"); + + return 0; +} + +static int intel_sso_led_remove(struct platform_device *pdev) +{ + struct sso_led_priv *priv; + struct sso_led *led, *n; + + priv = platform_get_drvdata(pdev); + + list_for_each_entry_safe(led, n, &priv->led_list, list) { + list_del(&led->list); + sso_led_shutdown(led); + } + + regmap_exit(priv->mmap); + + return 0; +} + +static const struct of_device_id of_sso_led_match[] = { + { .compatible = "intel,lgm-ssoled" }, + {} +}; + +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 = "lgm-ssoled", + .of_match_table = of_sso_led_match, + }, +}; + +module_platform_driver(intel_sso_led_driver); + +MODULE_DESCRIPTION("Intel SSO LED/GPIO driver"); +MODULE_LICENSE("GPL v2"); |