From c15d36508af69d0448eee64255e2cf47217953cc Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Thu, 28 May 2020 16:58:43 +0200 Subject: [PATCH 01/19] gpiolib: Introduce gpiochip_irqchip_add_domain() The function connects an IRQ domain to a gpiochip and reuses gpiochip_to_irq() which is provided by gpiolib. gpiochip_irqchip_* and regmap_irq partially provide the same functionality. This function will help to connect just the minimal functionality of the gpiochip_irqchip which is needed to work together with regmap-irq. Signed-off-by: Michael Walle --- drivers/gpio/gpiolib.c | 20 ++++++++++++++++++++ include/linux/gpio/driver.h | 3 +++ 2 files changed, 23 insertions(+) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 182136d98b9775..e7abb5663360fb 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2745,6 +2745,26 @@ int gpiochip_irqchip_add_key(struct gpio_chip *gc, } EXPORT_SYMBOL_GPL(gpiochip_irqchip_add_key); +/** + * gpiochip_irqchip_add_domain() - adds an irqdomain to a gpiochip + * @gc: the gpiochip to add the irqchip to + * @domain: the irqdomain to add to the gpiochip + * + * This function adds an IRQ domain to the gpiochip. + */ +int gpiochip_irqchip_add_domain(struct gpio_chip *gc, + struct irq_domain *domain) +{ + if (!domain) + return -EINVAL; + + gc->to_irq = gpiochip_to_irq; + gc->irq.domain = domain; + + return 0; +} +EXPORT_SYMBOL_GPL(gpiochip_irqchip_add_domain); + #else /* CONFIG_GPIOLIB_IRQCHIP */ static inline int gpiochip_add_irqchip(struct gpio_chip *gc, diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index b8fc92c177eba2..dfb1aa0ad4dc4e 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -599,6 +599,9 @@ int gpiochip_irqchip_add_key(struct gpio_chip *gc, bool gpiochip_irqchip_irq_valid(const struct gpio_chip *gc, unsigned int offset); +int gpiochip_irqchip_add_domain(struct gpio_chip *gc, + struct irq_domain *domain); + #ifdef CONFIG_LOCKDEP /* From 101a721820ef6cbbe03c4ab3865348a7d9c26311 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Thu, 28 May 2020 16:58:44 +0200 Subject: [PATCH 02/19] gpio: add a reusable generic gpio_chip using regmap There are quite a lot simple GPIO controller which are using regmap to access the hardware. This driver tries to be a base to unify existing code into one place. This won't cover everything but it should be a good starting point. It does not implement its own irq_chip because there is already a generic one for regmap based devices. Instead, the irq_chip will be instantiated in the parent driver and its irq domain will be associate to this driver. For now it consists of the usual registers, like set (and an optional clear) data register, an input register and direction registers. Out-of-the-box, it supports consecutive register mappings and mappings where the registers have gaps between them with a linear mapping between GPIO offset and bit position. For weirder mappings the user can register its own .xlate(). Signed-off-by: Michael Walle --- drivers/gpio/Kconfig | 4 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-regmap.c | 349 ++++++++++++++++++++++++++++++++++++ include/linux/gpio/regmap.h | 86 +++++++++ 4 files changed, 440 insertions(+) create mode 100644 drivers/gpio/gpio-regmap.c create mode 100644 include/linux/gpio/regmap.h diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 1b96169d84f746..a8e148f4b2e0dc 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -73,6 +73,10 @@ config GPIO_GENERIC depends on HAS_IOMEM # Only for IOMEM drivers tristate +config GPIO_REGMAP + depends on REGMAP + tristate + # put drivers in the right section, in alphabetical order # This symbol is selected by both I2C and SPI expanders diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index b2cfc21a97f3e5..93e139fdfa5766 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_GPIO_SYSFS) += gpiolib-sysfs.o obj-$(CONFIG_GPIO_ACPI) += gpiolib-acpi.o # Device drivers. Generally keep list sorted alphabetically +obj-$(CONFIG_GPIO_REGMAP) += gpio-regmap.o obj-$(CONFIG_GPIO_GENERIC) += gpio-generic.o # directly supported by gpio-generic diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c new file mode 100644 index 00000000000000..5412cb3b0b2a20 --- /dev/null +++ b/drivers/gpio/gpio-regmap.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * regmap based generic GPIO driver + * + * Copyright 2020 Michael Walle + */ + +#include +#include +#include +#include +#include + +struct gpio_regmap { + struct device *parent; + struct regmap *regmap; + struct gpio_chip gpio_chip; + + int reg_stride; + int ngpio_per_reg; + unsigned int reg_dat_base; + unsigned int reg_set_base; + unsigned int reg_clr_base; + unsigned int reg_dir_in_base; + unsigned int reg_dir_out_base; + + int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base, + unsigned int offset, unsigned int *reg, + unsigned int *mask); + + void *driver_data; +}; + +static unsigned int gpio_regmap_addr(unsigned int addr) +{ + if (addr == GPIO_REGMAP_ADDR_ZERO) + return 0; + + return addr; +} + +static int gpio_regmap_simple_xlate(struct gpio_regmap *gpio, + unsigned int base, unsigned int offset, + unsigned int *reg, unsigned int *mask) +{ + unsigned int line = offset % gpio->ngpio_per_reg; + unsigned int stride = offset / gpio->ngpio_per_reg; + + *reg = base + stride * gpio->reg_stride; + *mask = BIT(line); + + return 0; +} + +static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset) +{ + struct gpio_regmap *gpio = gpiochip_get_data(chip); + unsigned int base, val, reg, mask; + int ret; + + /* we might not have an output register if we are input only */ + if (gpio->reg_dat_base) + base = gpio_regmap_addr(gpio->reg_dat_base); + else + base = gpio_regmap_addr(gpio->reg_set_base); + + ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (ret) + return ret; + + ret = regmap_read(gpio->regmap, reg, &val); + if (ret) + return ret; + + return !!(val & mask); +} + +static void gpio_regmap_set(struct gpio_chip *chip, unsigned int offset, + int val) +{ + struct gpio_regmap *gpio = gpiochip_get_data(chip); + unsigned int base = gpio_regmap_addr(gpio->reg_set_base); + unsigned int reg, mask; + + gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (val) + regmap_update_bits(gpio->regmap, reg, mask, mask); + else + regmap_update_bits(gpio->regmap, reg, mask, 0); +} + +static void gpio_regmap_set_with_clear(struct gpio_chip *chip, + unsigned int offset, int val) +{ + struct gpio_regmap *gpio = gpiochip_get_data(chip); + unsigned int base, reg, mask; + + if (val) + base = gpio_regmap_addr(gpio->reg_set_base); + else + base = gpio_regmap_addr(gpio->reg_clr_base); + + gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + regmap_write(gpio->regmap, reg, mask); +} + +static int gpio_regmap_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + struct gpio_regmap *gpio = gpiochip_get_data(chip); + unsigned int base, val, reg, mask; + int invert, ret; + + if (gpio->reg_dir_out_base) { + base = gpio_regmap_addr(gpio->reg_dir_out_base); + invert = 0; + } else if (gpio->reg_dir_in_base) { + base = gpio_regmap_addr(gpio->reg_dir_in_base); + invert = 1; + } else { + return -EOPNOTSUPP; + } + + ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (ret) + return ret; + + ret = regmap_read(gpio->regmap, reg, &val); + if (ret) + return ret; + + if (!!(val & mask) ^ invert) + return GPIO_LINE_DIRECTION_OUT; + else + return GPIO_LINE_DIRECTION_IN; +} + +static int gpio_regmap_set_direction(struct gpio_chip *chip, + unsigned int offset, bool output) +{ + struct gpio_regmap *gpio = gpiochip_get_data(chip); + unsigned int base, val, reg, mask; + int invert, ret; + + if (gpio->reg_dir_out_base) { + base = gpio_regmap_addr(gpio->reg_dir_out_base); + invert = 0; + } else if (gpio->reg_dir_in_base) { + base = gpio_regmap_addr(gpio->reg_dir_in_base); + invert = 1; + } else { + return -EOPNOTSUPP; + } + + ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (ret) + return ret; + + if (invert) + val = output ? 0 : mask; + else + val = output ? mask : 0; + + return regmap_update_bits(gpio->regmap, reg, mask, val); +} + +static int gpio_regmap_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + return gpio_regmap_set_direction(chip, offset, false); +} + +static int gpio_regmap_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + gpio_regmap_set(chip, offset, value); + + return gpio_regmap_set_direction(chip, offset, true); +} + +void gpio_regmap_set_drvdata(struct gpio_regmap *gpio, void *data) +{ + gpio->driver_data = data; +} +EXPORT_SYMBOL_GPL(gpio_regmap_set_drvdata); + +void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio) +{ + return gpio->driver_data; +} +EXPORT_SYMBOL_GPL(gpio_regmap_get_drvdata); + +/** + * gpio_regmap_register() - Register a generic regmap GPIO controller + * @config: configuration for gpio_regmap + * + * Return: A pointer to the registered gpio_regmap or ERR_PTR error value. + */ +struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config) +{ + struct gpio_regmap *gpio; + struct gpio_chip *chip; + int ret; + + if (!config->parent) + return ERR_PTR(-EINVAL); + + if (!config->ngpio) + return ERR_PTR(-EINVAL); + + /* we need at least one */ + if (!config->reg_dat_base && !config->reg_set_base) + return ERR_PTR(-EINVAL); + + /* if we have a direction register we need both input and output */ + if ((config->reg_dir_out_base || config->reg_dir_in_base) && + (!config->reg_dat_base || !config->reg_set_base)) + return ERR_PTR(-EINVAL); + + /* we don't support having both registers simultaneously for now */ + if (config->reg_dir_out_base && config->reg_dir_in_base) + return ERR_PTR(-EINVAL); + + gpio = kzalloc(sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return ERR_PTR(-ENOMEM); + + gpio->parent = config->parent; + gpio->regmap = config->regmap; + gpio->ngpio_per_reg = config->ngpio_per_reg; + gpio->reg_stride = config->reg_stride; + gpio->reg_mask_xlate = config->reg_mask_xlate; + gpio->reg_dat_base = config->reg_dat_base; + gpio->reg_set_base = config->reg_set_base; + gpio->reg_clr_base = config->reg_clr_base; + gpio->reg_dir_in_base = config->reg_dir_in_base; + gpio->reg_dir_out_base = config->reg_dir_out_base; + + /* if not set, assume there is only one register */ + if (!gpio->ngpio_per_reg) + gpio->ngpio_per_reg = config->ngpio; + + /* if not set, assume they are consecutive */ + if (!gpio->reg_stride) + gpio->reg_stride = 1; + + if (!gpio->reg_mask_xlate) + gpio->reg_mask_xlate = gpio_regmap_simple_xlate; + + chip = &gpio->gpio_chip; + chip->parent = config->parent; + chip->base = -1; + chip->ngpio = config->ngpio; + chip->names = config->names; + chip->label = config->label ?: dev_name(config->parent); + + /* + * If our regmap is fast_io we should probably set can_sleep to false. + * Right now, the regmap doesn't save this property, nor is there any + * access function for it. + * The only regmap type which uses fast_io is regmap-mmio. For now, + * assume a safe default of true here. + */ + chip->can_sleep = true; + + chip->get = gpio_regmap_get; + if (gpio->reg_set_base && gpio->reg_clr_base) + chip->set = gpio_regmap_set_with_clear; + else if (gpio->reg_set_base) + chip->set = gpio_regmap_set; + + if (gpio->reg_dir_in_base || gpio->reg_dir_out_base) { + chip->get_direction = gpio_regmap_get_direction; + chip->direction_input = gpio_regmap_direction_input; + chip->direction_output = gpio_regmap_direction_output; + } + + ret = gpiochip_add_data(chip, gpio); + if (ret < 0) + goto err_free_gpio; + + if (config->irq_domain) { + ret = gpiochip_irqchip_add_domain(chip, config->irq_domain); + if (ret) + goto err_remove_gpiochip; + } + + return gpio; + +err_remove_gpiochip: + gpiochip_remove(chip); +err_free_gpio: + kfree(gpio); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(gpio_regmap_register); + +/** + * gpio_regmap_unregister() - Unregister a generic regmap GPIO controller + * @gpio: gpio_regmap device to unregister + */ +void gpio_regmap_unregister(struct gpio_regmap *gpio) +{ + gpiochip_remove(&gpio->gpio_chip); + kfree(gpio); +} +EXPORT_SYMBOL_GPL(gpio_regmap_unregister); + +static void devm_gpio_regmap_unregister(struct device *dev, void *res) +{ + gpio_regmap_unregister(*(struct gpio_regmap **)res); +} + +/** + * devm_gpio_regmap_register() - resource managed gpio_regmap_register() + * @dev: device that is registering this GPIO device + * @config: configuration for gpio_regmap + * + * Managed gpio_regmap_register(). For generic regmap GPIO device registered by + * this function, gpio_regmap_unregister() is automatically called on driver + * detach. See gpio_regmap_register() for more information. + * + * Return: A pointer to the registered gpio_regmap or ERR_PTR error value. + */ +struct gpio_regmap *devm_gpio_regmap_register(struct device *dev, + const struct gpio_regmap_config *config) +{ + struct gpio_regmap **ptr, *gpio; + + ptr = devres_alloc(devm_gpio_regmap_unregister, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + gpio = gpio_regmap_register(config); + if (!IS_ERR(gpio)) { + *ptr = gpio; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return gpio; +} +EXPORT_SYMBOL_GPL(devm_gpio_regmap_register); + +MODULE_AUTHOR("Michael Walle "); +MODULE_DESCRIPTION("GPIO generic regmap driver core"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h new file mode 100644 index 00000000000000..4c1e6b34e8249b --- /dev/null +++ b/include/linux/gpio/regmap.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _LINUX_GPIO_REGMAP_H +#define _LINUX_GPIO_REGMAP_H + +struct device; +struct gpio_regmap; +struct irq_domain; +struct regmap; + +#define GPIO_REGMAP_ADDR_ZERO ((unsigned long)(-1)) +#define GPIO_REGMAP_ADDR(addr) ((addr) ? : GPIO_REGMAP_ADDR_ZERO) + +/** + * struct gpio_regmap_config - Description of a generic regmap gpio_chip. + * @parent: The parent device + * @regmap: The regmap used to access the registers + * given, the name of the device is used + * @label: (Optional) Descriptive name for GPIO controller. + * If not given, the name of the device is used. + * @ngpio: Number of GPIOs + * @names: (Optional) Array of names for gpios + * @reg_dat_base: (Optional) (in) register base address + * @reg_set_base: (Optional) set register base address + * @reg_clr_base: (Optional) clear register base address + * @reg_dir_in_base: (Optional) in setting register base address + * @reg_dir_out_base: (Optional) out setting register base address + * @reg_stride: (Optional) May be set if the registers (of the + * same type, dat, set, etc) are not consecutive. + * @ngpio_per_reg: Number of GPIOs per register + * @irq_domain: (Optional) IRQ domain if the controller is + * interrupt-capable + * @reg_mask_xlate: (Optional) Translates base address and GPIO + * offset to a register/bitmask pair. If not + * given the default gpio_regmap_simple_xlate() + * is used. + * + * The ->reg_mask_xlate translates a given base address and GPIO offset to + * register and mask pair. The base address is one of the given register + * base addresses in this structure. + * + * Although all register base addresses are marked as optional, there are + * several rules: + * 1. if you only have @reg_dat_base set, then it is input-only + * 2. if you only have @reg_set_base set, then it is output-only + * 3. if you have either @reg_dir_in_base or @reg_dir_out_base set, then + * you have to set both @reg_dat_base and @reg_set_base + * 4. if you have @reg_set_base set, you may also set @reg_clr_base to have + * two different registers for setting and clearing the output. This is + * also valid for the output-only case. + * 5. @reg_dir_in_base and @reg_dir_out_base are exclusive; is there really + * hardware which has redundant registers? + * + * Note: All base addresses may have the special value %GPIO_REGMAP_ADDR_ZERO + * which forces the address to the value 0. + */ +struct gpio_regmap_config { + struct device *parent; + struct regmap *regmap; + + const char *label; + int ngpio; + const char *const *names; + + unsigned int reg_dat_base; + unsigned int reg_set_base; + unsigned int reg_clr_base; + unsigned int reg_dir_in_base; + unsigned int reg_dir_out_base; + int reg_stride; + int ngpio_per_reg; + struct irq_domain *irq_domain; + + int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base, + unsigned int offset, unsigned int *reg, + unsigned int *mask); +}; + +struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config); +void gpio_regmap_unregister(struct gpio_regmap *gpio); +struct gpio_regmap *devm_gpio_regmap_register(struct device *dev, + const struct gpio_regmap_config *config); +void gpio_regmap_set_drvdata(struct gpio_regmap *gpio, void *data); +void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio); + +#endif /* _LINUX_GPIO_REGMAP_H */ From 0e984fa70973ff7f0b5ce1d5beab753e9ebc88df Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Thu, 28 May 2020 16:58:45 +0200 Subject: [PATCH 03/19] MAINTAINERS: Add gpio regmap section Add myself as a reviewer for the gpio regmap. Signed-off-by: Michael Walle --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index ecc0749810b006..50983114d097e1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7242,6 +7242,12 @@ S: Maintained F: drivers/gpio/gpio-mockup.c F: tools/testing/selftests/gpio/ +GPIO REGMAP +R: Michael Walle +S: Maintained +F: drivers/gpio/gpio-regmap.c +F: include/linux/gpio/regmap.h + GPIO SUBSYSTEM M: Linus Walleij M: Bartosz Golaszewski From 83a37651c2721e5ef23b9194714fb4d6b4716a03 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Thu, 20 Dec 2018 15:48:31 -0600 Subject: [PATCH 04/19] ASoC: pcm512x: expose 6 GPIOs The GPIOs are used e.g. on HifiBerry DAC+ HATs to control the LED (GPIO3) and the choice of the 44.1 (GPIO6) or 48 (GPIO3) kHz oscillator (when present). Enable gpio_regmap to get/set values and get/set directions. Tested with GPIO_LIB from sys/class/gpio, the LED turns on/off as desired. Signed-off-by: Pierre-Louis Bossart --- sound/soc/codecs/Kconfig | 1 + sound/soc/codecs/pcm512x.c | 47 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 2d4f1b4bc01172..3d7776f38fda3d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -971,6 +971,7 @@ config SND_SOC_PCM5102A config SND_SOC_PCM512x tristate + select GPIO_REGMAP config SND_SOC_PCM512x_I2C tristate "Texas Instruments PCM512x CODECs - I2C" diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index 4cbef9affffda9..55c94fc99ca74b 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,7 @@ static const char * const pcm512x_supply_names[PCM512x_NUM_SUPPLIES] = { struct pcm512x_priv { struct regmap *regmap; struct clk *sclk; + struct gpio_regmap *gpio; struct regulator_bulk_data supplies[PCM512x_NUM_SUPPLIES]; struct notifier_block supply_nb[PCM512x_NUM_SUPPLIES]; int fmt; @@ -1503,9 +1505,17 @@ const struct regmap_config pcm512x_regmap = { }; EXPORT_SYMBOL_GPL(pcm512x_regmap); +/* list human-readable names, makes GPIOLIB usage straightforward */ +static const char * const pcm512x_gpio_names[] = { + "PCM512x-GPIO1", "PCM512x-GPIO2", "PCM512x-GPIO3", + "PCM512x-GPIO4", "PCM512x-GPIO5", "PCM512x-GPIO6" +}; + int pcm512x_probe(struct device *dev, struct regmap *regmap) { struct pcm512x_priv *pcm512x; + struct gpio_regmap_config *gpio_config; + unsigned int reg; int i, ret; pcm512x = devm_kzalloc(dev, sizeof(struct pcm512x_priv), GFP_KERNEL); @@ -1563,6 +1573,43 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap) goto err; } + /* expose 6 GPIO pins, numbered from 1 to 6 */ + gpio_config = devm_kzalloc(dev, sizeof(*gpio_config), GFP_KERNEL); + if (!gpio_config) + return -ENOMEM; + + gpio_config->parent = dev; + gpio_config->regmap = regmap; + gpio_config->label = "pcm512x-gpio"; + gpio_config->names = pcm512x_gpio_names; + gpio_config->ngpio = ARRAY_SIZE(pcm512x_gpio_names); + gpio_config->reg_dat_base = PCM512x_GPIN; + gpio_config->reg_set_base = PCM512x_GPIO_CONTROL_1; + /* reg_dir_in_base not defined */ + gpio_config->reg_dir_out_base = PCM512x_GPIO_EN; + gpio_config->reg_stride = 1; + gpio_config->ngpio_per_reg = ARRAY_SIZE(pcm512x_gpio_names); + /* .reg_mask_xlate not used */ + + pcm512x->gpio = devm_gpio_regmap_register(dev, gpio_config); + if (IS_ERR(pcm512x->gpio)) { + ret = PTR_ERR(pcm512x->gpio); + dev_err(dev, "Could not add gpio chip: %d\n", ret); + goto err; + } + + /* + * select Register GPIOx output for OUTPUT_x (1..6). The + * actual selection of input/output is done by the gpio_regmap + * helpers. + */ + for (i = 0; i < ARRAY_SIZE(pcm512x_gpio_names); i++) { + reg = PCM512x_GPIO_OUTPUT_1 + i; + ret = regmap_update_bits(regmap, reg, 0x0f, 0x02); + if (ret < 0) + return ret; + } + pcm512x->sclk = devm_clk_get(dev, NULL); if (PTR_ERR(pcm512x->sclk) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; From 25462c966060bac8f0d52f967e68b43bff017f2f Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Sun, 29 Mar 2020 11:23:08 -0500 Subject: [PATCH 05/19] ASoC: pcm512x: use "sclk" string to retrieve clock Using devm_clk_get() with a NULL string fails on ACPI platforms, use the "sclk" string as a fallback. Signed-off-by: Pierre-Louis Bossart --- sound/soc/codecs/pcm512x.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index 55c94fc99ca74b..36ed2fbafae9d6 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -1513,6 +1513,7 @@ static const char * const pcm512x_gpio_names[] = { int pcm512x_probe(struct device *dev, struct regmap *regmap) { + const char * const clk_name[] = {NULL, "sclk"}; struct pcm512x_priv *pcm512x; struct gpio_regmap_config *gpio_config; unsigned int reg; @@ -1610,17 +1611,28 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap) return ret; } - pcm512x->sclk = devm_clk_get(dev, NULL); - if (PTR_ERR(pcm512x->sclk) == -EPROBE_DEFER) { - ret = -EPROBE_DEFER; - goto err; - } - if (!IS_ERR(pcm512x->sclk)) { - ret = clk_prepare_enable(pcm512x->sclk); - if (ret != 0) { - dev_err(dev, "Failed to enable SCLK: %d\n", ret); + for (i = 0; i < ARRAY_SIZE(clk_name); i++) { + pcm512x->sclk = devm_clk_get(dev, clk_name[i]); + if (PTR_ERR(pcm512x->sclk) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; goto err; } + if (!IS_ERR(pcm512x->sclk)) { + dev_dbg(dev, "SCLK detected by devm_clk_get\n"); + ret = clk_prepare_enable(pcm512x->sclk); + if (ret != 0) { + dev_err(dev, "Failed to enable SCLK: %d\n", + ret); + goto err; + } + break; + } + + if (!clk_name[i]) + dev_dbg(dev, "no SCLK detected with NULL string\n"); + else + dev_dbg(dev, "no SCLK detected for %s string\n", + clk_name[i]); } /* Default to standby mode */ From 1b6c5d955d066c63f008fdb24eba6830401a46ab Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 27 Mar 2020 16:03:18 -0500 Subject: [PATCH 06/19] ASoC: Intel: sof-pcm512x: use gpiod for LED Remove direct regmap access, use gpios exposed by PCM512x codec Keep the codec_init function, this will be used in following patches The gpios handling is done with an explicit lookup table. We cannot use ACPI-based mappings since we don't have an ACPI device for the machine driver, and the gpiochip is created during the probe of the PCM512x driver. Signed-off-by: Pierre-Louis Bossart --- sound/soc/intel/boards/sof_pcm512x.c | 45 ++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/sound/soc/intel/boards/sof_pcm512x.c b/sound/soc/intel/boards/sof_pcm512x.c index 9fa8a491127692..fee2279b1cfeb1 100644 --- a/sound/soc/intel/boards/sof_pcm512x.c +++ b/sound/soc/intel/boards/sof_pcm512x.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include #include @@ -43,6 +45,7 @@ struct sof_hdmi_pcm { struct sof_card_private { struct list_head hdmi_pcm_list; bool idisp_codec; + struct gpio_desc *gpio_4; }; static int sof_pcm512x_quirk_cb(const struct dmi_system_id *id) @@ -84,23 +87,16 @@ static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd) static int sof_pcm512x_codec_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component; - - snd_soc_component_update_bits(codec, PCM512x_GPIO_EN, 0x08, 0x08); - snd_soc_component_update_bits(codec, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02); - snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1, - 0x08, 0x08); - return 0; } static int aif1_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component; + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); - snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1, - 0x08, 0x08); + /* Turn LED on */ + gpiod_set_value_cansleep(ctx->gpio_4, 1); return 0; } @@ -108,10 +104,10 @@ static int aif1_startup(struct snd_pcm_substream *substream) static void aif1_shutdown(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component; + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); - snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1, - 0x08, 0x00); + /* Turn LED off */ + gpiod_set_value_cansleep(ctx->gpio_4, 0); } static const struct snd_soc_ops sof_pcm512x_ops = { @@ -347,6 +343,14 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, return NULL; } +static struct gpiod_lookup_table pcm512x_gpios_table = { + /* .dev_id set during probe */ + .table = { + GPIO_LOOKUP("pcm512x-gpio", 3, "PCM512x-GPIO4", GPIO_ACTIVE_HIGH), + { }, + }, +}; + static int sof_audio_probe(struct platform_device *pdev) { struct snd_soc_acpi_mach *mach = pdev->dev.platform_data; @@ -404,6 +408,21 @@ static int sof_audio_probe(struct platform_device *pdev) snd_soc_card_set_drvdata(&sof_audio_card_pcm512x, ctx); + /* + * Enable GPIO4 for LED + */ + pcm512x_gpios_table.dev_id = dev_name(&pdev->dev); + gpiod_add_lookup_table(&pcm512x_gpios_table); + + ctx->gpio_4 = devm_gpiod_get(&pdev->dev, "PCM512x-GPIO4", + GPIOD_OUT_LOW); + + if (IS_ERR(ctx->gpio_4)) { + dev_err(&pdev->dev, "gpio4 not found\n"); + ret = PTR_ERR(ctx->gpio_4); + return ret; + } + return devm_snd_soc_register_card(&pdev->dev, &sof_audio_card_pcm512x); } From f970871d33ede97b3d7fda7ca4335fe79d094fd6 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Sun, 29 Mar 2020 13:24:29 -0500 Subject: [PATCH 07/19] ASoC: Intel: sof-pcm512x: detect Hifiberry DAC+ PRO Try to detect HifiBerry 44.1 and 48kHz oscillators on codec init Signed-off-by: Pierre-Louis Bossart --- sound/soc/intel/boards/sof_pcm512x.c | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/sound/soc/intel/boards/sof_pcm512x.c b/sound/soc/intel/boards/sof_pcm512x.c index fee2279b1cfeb1..9409306ba167a7 100644 --- a/sound/soc/intel/boards/sof_pcm512x.c +++ b/sound/soc/intel/boards/sof_pcm512x.c @@ -46,6 +46,8 @@ struct sof_card_private { struct list_head hdmi_pcm_list; bool idisp_codec; struct gpio_desc *gpio_4; + struct clk *sclk; + bool is_dac_pro; }; static int sof_pcm512x_quirk_cb(const struct dmi_system_id *id) @@ -87,6 +89,60 @@ static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd) static int sof_pcm512x_codec_init(struct snd_soc_pcm_runtime *rtd) { + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct device *dev = rtd->card->dev; + unsigned int sck; + int ret; + + ctx->sclk = devm_clk_get(rtd->card->dev, "sclk"); + if (IS_ERR(ctx->sclk)) { + dev_info(dev, "Could not get SCLK, will operate in SOC master mode\n"); + goto skip_dacpro; + } + + /* + * now we have a clk, see if it's really present or if we are on + * plain vanilla DAC+ + */ + + /* Try 48 kHz */ + clk_set_rate(ctx->sclk, 24576000UL); + ret = clk_prepare_enable(ctx->sclk); + if (ret) { + dev_info(dev, "Failed to enable SCLK for DAC+ PRO 48 kHz: %d\n", ret); + goto skip_dacpro; + } + + snd_soc_component_read(codec_dai->component, + PCM512x_RATE_DET_4, &sck); + clk_disable_unprepare(ctx->sclk); + if (sck & 0x40) { + dev_info(dev, "No SCLK detected for DAC+ PRO 48 kHz\n"); + goto skip_dacpro; + } + + /* Try 44.1 kHz */ + clk_set_rate(ctx->sclk, 22579200UL); + ret = clk_prepare_enable(ctx->sclk); + if (ret) { + dev_info(dev, "Failed to enable SCLK for DAC+ PRO 44.1 kHz: %d\n", ret); + goto skip_dacpro; + } + + snd_soc_component_read(codec_dai->component, + PCM512x_RATE_DET_4, &sck); + clk_disable_unprepare(ctx->sclk); + + if (sck & 0x40) { + dev_info(dev, "No SCLK detected for DAC+ PRO 44.1 kHz\n"); + goto skip_dacpro; + } + + dev_info(dev, "DAC+ PRO detected\n"); + ctx->is_dac_pro = true; + +skip_dacpro: return 0; } From 41df23831c613d7b08579a78609766eb9dac5b8e Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Sun, 29 Mar 2020 14:21:43 -0500 Subject: [PATCH 08/19] ASoC: Intel: sof-pcm512x: reconfigure sclk in hw_params if needed The SCLK is resumed by the codec driver. In case the rate specified in hw_params does not match the current configuration, disable, set the new rate and restart the clock. There is no operation on hw_free, the codec suspend routine will disable/deprepare the clock. Note that we don't change the DAI configuration when the DAC+ PRO is detected. All changes for the codec master mode are handled in the topology file (DAI configuration change and scheduling change) Signed-off-by: Pierre-Louis Bossart --- sound/soc/intel/boards/sof_pcm512x.c | 95 ++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/sound/soc/intel/boards/sof_pcm512x.c b/sound/soc/intel/boards/sof_pcm512x.c index 9409306ba167a7..9db29831b3ff46 100644 --- a/sound/soc/intel/boards/sof_pcm512x.c +++ b/sound/soc/intel/boards/sof_pcm512x.c @@ -146,6 +146,31 @@ static int sof_pcm512x_codec_init(struct snd_soc_pcm_runtime *rtd) return 0; } +static int aif1_update_rate_den(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_ratnum rats_no_pll; + unsigned int num = 0, den = 0; + int err; + + rats_no_pll.num = clk_get_rate(ctx->sclk) / 64; + rats_no_pll.den_min = 1; + rats_no_pll.den_max = 128; + rats_no_pll.den_step = 1; + + err = snd_interval_ratnum(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), + 1, &rats_no_pll, &num, &den); + if (err >= 0 && den) { + params->rate_num = num; + params->rate_den = den; + } + + return 0; +} + static int aif1_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; @@ -157,6 +182,75 @@ static int aif1_startup(struct snd_pcm_substream *substream) return 0; } +static int aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct device *dev = rtd->card->dev; + int current_rate; + int sclk_rate; + int channels; + int width; + int rate; + int ret = 0; + + if (ctx->is_dac_pro) { + rate = params_rate(params); + channels = params_channels(params); + width = snd_pcm_format_physical_width(params_format(params)); + + if (rate % 24000) + sclk_rate = 22579200; + else + sclk_rate = 24576000; + + current_rate = clk_get_rate(ctx->sclk); + if (current_rate != sclk_rate) { + /* + * The sclk clock is started and stopped by the codec + * resume/suspend functions. If the rate isn't correct, + * stop, set the new rate and restart the clock + */ + + dev_dbg(dev, "reconfiguring SCLK to rate %d\n", + sclk_rate); + + clk_disable_unprepare(ctx->sclk); + + ret = clk_set_rate(ctx->sclk, sclk_rate); + if (ret) { + dev_err(dev, "Could not set SCLK rate %d\n", + sclk_rate); + return ret; + } + + ret = clk_prepare_enable(ctx->sclk); + if (ret) { + dev_err(dev, "Failed to enable SCLK: %d\n", + ret); + return ret; + } + } + + ret = aif1_update_rate_den(substream, params); + if (ret) { + dev_err(dev, "Failed to update rate denominator: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_bclk_ratio(codec_dai, + channels * width); + if (ret) { + dev_err(dev, "Failed to set bclk ratio : %d\n", ret); + return ret; + } + } + + return ret; +} + static void aif1_shutdown(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; @@ -168,6 +262,7 @@ static void aif1_shutdown(struct snd_pcm_substream *substream) static const struct snd_soc_ops sof_pcm512x_ops = { .startup = aif1_startup, + .hw_params = aif1_hw_params, .shutdown = aif1_shutdown, }; From ef061d19d440dda1b40f3945df4d182821292382 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Sun, 29 Mar 2020 10:08:45 -0500 Subject: [PATCH 09/19] ASoC: Intel: sof-pcm512x: select HIFIBERRY_DACPRO clk This configuration is needed to get the GPIO-controller clocks. Signed-off-by: Pierre-Louis Bossart --- sound/soc/intel/boards/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index dce3e7eacef4a6..ca287410cadb65 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -483,10 +483,12 @@ config SND_SOC_INTEL_SOF_RT5682_MACH config SND_SOC_INTEL_SOF_PCM512x_MACH tristate "SOF with TI PCM512x codec" depends on I2C && ACPI + depends on COMMON_CLK depends on (SND_SOC_SOF_HDA_AUDIO_CODEC && (MFD_INTEL_LPSS || COMPILE_TEST)) ||\ (SND_SOC_SOF_BAYTRAIL && (X86_INTEL_LPSS || COMPILE_TEST)) depends on SND_HDA_CODEC_HDMI select SND_SOC_PCM512x_I2C + select COMMON_CLK_HIFIBERRY_DACPRO help This adds support for ASoC machine driver for SOF platforms with TI PCM512x I2S audio codec. From 38195bb88ab6007f96d1c122bdfa7f2300ece9ac Mon Sep 17 00:00:00 2001 From: Daniel Matuschek Date: Mon, 4 Aug 2014 10:06:56 +0200 Subject: [PATCH 10/19] clk: hifiberry-dacpro: initial import This patch imports the clock code from the Raspberry v5.5-y tree. The ASoC machine driver initially present in this patch was dropped. The comments are also dropped but all sign-offs are kept below. The patch authorship was modified with explicit permission from Daniel Matuschek to make sure it matches the Signed-off tag. This patch generates a lot of checkpatch.pl warnings that are corrected in follow-up patches. Signed-off-by: DigitalDreamtime Signed-off-by: Daniel Matuschek Signed-off-by: Matthias Reichl Signed-off-by: Hui Wang Signed-off-by: Pierre-Louis Bossart --- drivers/clk/Kconfig | 3 + drivers/clk/Makefile | 1 + drivers/clk/clk-hifiberry-dacpro.c | 160 +++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 drivers/clk/clk-hifiberry-dacpro.c diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index bcb257baed06da..6bfffc99e3fd3c 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -70,6 +70,9 @@ config COMMON_CLK_HI655X multi-function device has one fixed-rate oscillator, clocked at 32KHz. +config COMMON_CLK_HIFIBERRY_DACPRO + tristate + config COMMON_CLK_SCMI tristate "Clock driver controlled via SCMI interface" depends on ARM_SCMI_PROTOCOL || COMPILE_TEST diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index f4169cc2fd3182..43ae7596de7b7d 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_MACH_ASPEED_G6) += clk-ast2600.o obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o obj-$(CONFIG_CLK_HSDK) += clk-hsdk-pll.o obj-$(CONFIG_COMMON_CLK_LOCHNAGAR) += clk-lochnagar.o +obj-$(CONFIG_COMMON_CLK_HIFIBERRY_DACPRO) += clk-hifiberry-dacpro.o obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o obj-$(CONFIG_COMMON_CLK_MAX9485) += clk-max9485.o obj-$(CONFIG_ARCH_MILBEAUT_M10V) += clk-milbeaut.o diff --git a/drivers/clk/clk-hifiberry-dacpro.c b/drivers/clk/clk-hifiberry-dacpro.c new file mode 100644 index 00000000000000..9e263446582367 --- /dev/null +++ b/drivers/clk/clk-hifiberry-dacpro.c @@ -0,0 +1,160 @@ +/* + * Clock Driver for HiFiBerry DAC Pro + * + * Author: Stuart MacLean + * Copyright 2015 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Clock rate of CLK44EN attached to GPIO6 pin */ +#define CLK_44EN_RATE 22579200UL +/* Clock rate of CLK48EN attached to GPIO3 pin */ +#define CLK_48EN_RATE 24576000UL + +/** + * struct hifiberry_dacpro_clk - Common struct to the HiFiBerry DAC Pro + * @hw: clk_hw for the common clk framework + * @mode: 0 => CLK44EN, 1 => CLK48EN + */ +struct clk_hifiberry_hw { + struct clk_hw hw; + uint8_t mode; +}; + +#define to_hifiberry_clk(_hw) container_of(_hw, struct clk_hifiberry_hw, hw) + +static const struct of_device_id clk_hifiberry_dacpro_dt_ids[] = { + { .compatible = "hifiberry,dacpro-clk",}, + { } +}; +MODULE_DEVICE_TABLE(of, clk_hifiberry_dacpro_dt_ids); + +static unsigned long clk_hifiberry_dacpro_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return (to_hifiberry_clk(hw)->mode == 0) ? CLK_44EN_RATE : + CLK_48EN_RATE; +} + +static long clk_hifiberry_dacpro_round_rate(struct clk_hw *hw, + unsigned long rate, unsigned long *parent_rate) +{ + long actual_rate; + + if (rate <= CLK_44EN_RATE) { + actual_rate = (long)CLK_44EN_RATE; + } else if (rate >= CLK_48EN_RATE) { + actual_rate = (long)CLK_48EN_RATE; + } else { + long diff44Rate = (long)(rate - CLK_44EN_RATE); + long diff48Rate = (long)(CLK_48EN_RATE - rate); + + if (diff44Rate < diff48Rate) + actual_rate = (long)CLK_44EN_RATE; + else + actual_rate = (long)CLK_48EN_RATE; + } + return actual_rate; +} + + +static int clk_hifiberry_dacpro_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + unsigned long actual_rate; + struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw); + + actual_rate = (unsigned long)clk_hifiberry_dacpro_round_rate(hw, rate, + &parent_rate); + clk->mode = (actual_rate == CLK_44EN_RATE) ? 0 : 1; + return 0; +} + + +const struct clk_ops clk_hifiberry_dacpro_rate_ops = { + .recalc_rate = clk_hifiberry_dacpro_recalc_rate, + .round_rate = clk_hifiberry_dacpro_round_rate, + .set_rate = clk_hifiberry_dacpro_set_rate, +}; + +static int clk_hifiberry_dacpro_probe(struct platform_device *pdev) +{ + int ret; + struct clk_hifiberry_hw *proclk; + struct clk *clk; + struct device *dev; + struct clk_init_data init; + + dev = &pdev->dev; + + proclk = kzalloc(sizeof(struct clk_hifiberry_hw), GFP_KERNEL); + if (!proclk) + return -ENOMEM; + + init.name = "clk-hifiberry-dacpro"; + init.ops = &clk_hifiberry_dacpro_rate_ops; + init.flags = 0; + init.parent_names = NULL; + init.num_parents = 0; + + proclk->mode = 0; + proclk->hw.init = &init; + + clk = devm_clk_register(dev, &proclk->hw); + if (!IS_ERR(clk)) { + ret = of_clk_add_provider(dev->of_node, of_clk_src_simple_get, + clk); + } else { + dev_err(dev, "Fail to register clock driver\n"); + kfree(proclk); + ret = PTR_ERR(clk); + } + return ret; +} + +static int clk_hifiberry_dacpro_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + return 0; +} + +static struct platform_driver clk_hifiberry_dacpro_driver = { + .probe = clk_hifiberry_dacpro_probe, + .remove = clk_hifiberry_dacpro_remove, + .driver = { + .name = "clk-hifiberry-dacpro", + .of_match_table = clk_hifiberry_dacpro_dt_ids, + }, +}; + +static int __init clk_hifiberry_dacpro_init(void) +{ + return platform_driver_register(&clk_hifiberry_dacpro_driver); +} +core_initcall(clk_hifiberry_dacpro_init); + +static void __exit clk_hifiberry_dacpro_exit(void) +{ + platform_driver_unregister(&clk_hifiberry_dacpro_driver); +} +module_exit(clk_hifiberry_dacpro_exit); + +MODULE_DESCRIPTION("HiFiBerry DAC Pro clock driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:clk-hifiberry-dacpro"); From daa2132be066fbd87225ef09151055645237ee18 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Sun, 29 Mar 2020 09:30:39 -0500 Subject: [PATCH 11/19] clk: hifiberry-dacpro: update SDPX/copyright Reformat license information and add Intel copyright Signed-off-by: Pierre-Louis Bossart --- drivers/clk/clk-hifiberry-dacpro.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/drivers/clk/clk-hifiberry-dacpro.c b/drivers/clk/clk-hifiberry-dacpro.c index 9e263446582367..eb67a8c47c4903 100644 --- a/drivers/clk/clk-hifiberry-dacpro.c +++ b/drivers/clk/clk-hifiberry-dacpro.c @@ -1,17 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* - * Clock Driver for HiFiBerry DAC Pro - * - * Author: Stuart MacLean - * Copyright 2015 + * Copyright (c) 2015 Stuart MacLean + * Copyright (c) 2020 Intel Corporation * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. + * Clock Driver for HiFiBerry DAC Pro * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. */ #include From e3277c29aa0e91f11661f830e6c7079e5b3ad325 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Sun, 29 Mar 2020 09:39:45 -0500 Subject: [PATCH 12/19] clk: hifiberry-dacpro: style cleanups, use devm_ Lots of small issues, xmas style, alignment, wrong comments, memory leak Signed-off-by: Pierre-Louis Bossart --- drivers/clk/clk-hifiberry-dacpro.c | 42 +++++++++++------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/drivers/clk/clk-hifiberry-dacpro.c b/drivers/clk/clk-hifiberry-dacpro.c index eb67a8c47c4903..78ede325d23706 100644 --- a/drivers/clk/clk-hifiberry-dacpro.c +++ b/drivers/clk/clk-hifiberry-dacpro.c @@ -21,13 +21,13 @@ #define CLK_48EN_RATE 24576000UL /** - * struct hifiberry_dacpro_clk - Common struct to the HiFiBerry DAC Pro + * struct clk_hifiberry_hw - Common struct to the HiFiBerry DAC Pro * @hw: clk_hw for the common clk framework * @mode: 0 => CLK44EN, 1 => CLK48EN */ struct clk_hifiberry_hw { struct clk_hw hw; - uint8_t mode; + u8 mode; }; #define to_hifiberry_clk(_hw) container_of(_hw, struct clk_hifiberry_hw, hw) @@ -39,14 +39,15 @@ static const struct of_device_id clk_hifiberry_dacpro_dt_ids[] = { MODULE_DEVICE_TABLE(of, clk_hifiberry_dacpro_dt_ids); static unsigned long clk_hifiberry_dacpro_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) + unsigned long parent_rate) { return (to_hifiberry_clk(hw)->mode == 0) ? CLK_44EN_RATE : CLK_48EN_RATE; } static long clk_hifiberry_dacpro_round_rate(struct clk_hw *hw, - unsigned long rate, unsigned long *parent_rate) + unsigned long rate, + unsigned long *parent_rate) { long actual_rate; @@ -66,21 +67,20 @@ static long clk_hifiberry_dacpro_round_rate(struct clk_hw *hw, return actual_rate; } - static int clk_hifiberry_dacpro_set_rate(struct clk_hw *hw, - unsigned long rate, unsigned long parent_rate) + unsigned long rate, + unsigned long parent_rate) { - unsigned long actual_rate; struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw); + unsigned long actual_rate; actual_rate = (unsigned long)clk_hifiberry_dacpro_round_rate(hw, rate, - &parent_rate); + &parent_rate); clk->mode = (actual_rate == CLK_44EN_RATE) ? 0 : 1; return 0; } - -const struct clk_ops clk_hifiberry_dacpro_rate_ops = { +static const struct clk_ops clk_hifiberry_dacpro_rate_ops = { .recalc_rate = clk_hifiberry_dacpro_recalc_rate, .round_rate = clk_hifiberry_dacpro_round_rate, .set_rate = clk_hifiberry_dacpro_set_rate, @@ -88,15 +88,15 @@ const struct clk_ops clk_hifiberry_dacpro_rate_ops = { static int clk_hifiberry_dacpro_probe(struct platform_device *pdev) { - int ret; struct clk_hifiberry_hw *proclk; - struct clk *clk; - struct device *dev; struct clk_init_data init; + struct device *dev; + struct clk *clk; + int ret; dev = &pdev->dev; - proclk = kzalloc(sizeof(struct clk_hifiberry_hw), GFP_KERNEL); + proclk = devm_kzalloc(dev, sizeof(*proclk), GFP_KERNEL); if (!proclk) return -ENOMEM; @@ -115,7 +115,6 @@ static int clk_hifiberry_dacpro_probe(struct platform_device *pdev) clk); } else { dev_err(dev, "Fail to register clock driver\n"); - kfree(proclk); ret = PTR_ERR(clk); } return ret; @@ -135,18 +134,7 @@ static struct platform_driver clk_hifiberry_dacpro_driver = { .of_match_table = clk_hifiberry_dacpro_dt_ids, }, }; - -static int __init clk_hifiberry_dacpro_init(void) -{ - return platform_driver_register(&clk_hifiberry_dacpro_driver); -} -core_initcall(clk_hifiberry_dacpro_init); - -static void __exit clk_hifiberry_dacpro_exit(void) -{ - platform_driver_unregister(&clk_hifiberry_dacpro_driver); -} -module_exit(clk_hifiberry_dacpro_exit); +module_platform_driver(clk_hifiberry_dacpro_driver); MODULE_DESCRIPTION("HiFiBerry DAC Pro clock driver"); MODULE_LICENSE("GPL v2"); From abdc298aab08a46df245472431ccd8759b6eccea Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Sun, 29 Mar 2020 09:47:18 -0500 Subject: [PATCH 13/19] clk: hifiberry-dacpro: add OF dependency Make sure OF is enabled, in case ACPI platforms use OF matching with PRP0001 and .compatible string Signed-off-by: Pierre-Louis Bossart --- drivers/clk/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 6bfffc99e3fd3c..5b9f829d84fe6b 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -72,6 +72,7 @@ config COMMON_CLK_HI655X config COMMON_CLK_HIFIBERRY_DACPRO tristate + depends on OF config COMMON_CLK_SCMI tristate "Clock driver controlled via SCMI interface" From 8623d2782516eae7821843f12732e2305967aeea Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Sun, 29 Mar 2020 10:05:01 -0500 Subject: [PATCH 14/19] clk: hifiberry-dacpro: transition to _hw functions devm_clk_register() and of_clk_add_provider() are deprecated, use the recommended functions. Signed-off-by: Pierre-Louis Bossart --- drivers/clk/clk-hifiberry-dacpro.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/clk/clk-hifiberry-dacpro.c b/drivers/clk/clk-hifiberry-dacpro.c index 78ede325d23706..bf0616c959da45 100644 --- a/drivers/clk/clk-hifiberry-dacpro.c +++ b/drivers/clk/clk-hifiberry-dacpro.c @@ -91,7 +91,6 @@ static int clk_hifiberry_dacpro_probe(struct platform_device *pdev) struct clk_hifiberry_hw *proclk; struct clk_init_data init; struct device *dev; - struct clk *clk; int ret; dev = &pdev->dev; @@ -109,14 +108,15 @@ static int clk_hifiberry_dacpro_probe(struct platform_device *pdev) proclk->mode = 0; proclk->hw.init = &init; - clk = devm_clk_register(dev, &proclk->hw); - if (!IS_ERR(clk)) { - ret = of_clk_add_provider(dev->of_node, of_clk_src_simple_get, - clk); - } else { + ret = devm_clk_hw_register(dev, &proclk->hw); + if (ret) { dev_err(dev, "Fail to register clock driver\n"); - ret = PTR_ERR(clk); + return ret; } + + ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_simple_get, + &proclk->hw); + return ret; } From 59969693444d9f6233d81918c54dd676957af975 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Sun, 29 Mar 2020 09:49:18 -0500 Subject: [PATCH 15/19] clk: hifiberry-dacpro: add ACPI support On ACPI platforms the of_ functions are irrelevant, conditionally compile them out and add devm_clk_hw_register_clkdev() call instead. Signed-off-by: Pierre-Louis Bossart --- drivers/clk/clk-hifiberry-dacpro.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/clk/clk-hifiberry-dacpro.c b/drivers/clk/clk-hifiberry-dacpro.c index bf0616c959da45..d01a90fed51b4c 100644 --- a/drivers/clk/clk-hifiberry-dacpro.c +++ b/drivers/clk/clk-hifiberry-dacpro.c @@ -114,15 +114,22 @@ static int clk_hifiberry_dacpro_probe(struct platform_device *pdev) return ret; } +#ifndef CONFIG_ACPI ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_simple_get, &proclk->hw); +#else + ret = devm_clk_hw_register_clkdev(dev, &proclk->hw, + init.name, NULL); +#endif return ret; } static int clk_hifiberry_dacpro_remove(struct platform_device *pdev) { +#ifndef CONFIG_ACPI of_clk_del_provider(pdev->dev.of_node); +#endif return 0; } From 01ed837f04fade41350a4213494f4577adaa16e8 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Sun, 29 Mar 2020 11:20:25 -0500 Subject: [PATCH 16/19] clk: hifiberry-dacpro: add "sclk" lookup devm_clk_get() fails on ACPI platforms when a NULL string is used. Create a "sclk" lookup to make sure codec and machine drivers can get the clock. Signed-off-by: Pierre-Louis Bossart --- drivers/clk/clk-hifiberry-dacpro.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/clk/clk-hifiberry-dacpro.c b/drivers/clk/clk-hifiberry-dacpro.c index d01a90fed51b4c..36210f52c62410 100644 --- a/drivers/clk/clk-hifiberry-dacpro.c +++ b/drivers/clk/clk-hifiberry-dacpro.c @@ -24,10 +24,12 @@ * struct clk_hifiberry_hw - Common struct to the HiFiBerry DAC Pro * @hw: clk_hw for the common clk framework * @mode: 0 => CLK44EN, 1 => CLK48EN + * @sclk_lookup: handle for "sclk" */ struct clk_hifiberry_hw { struct clk_hw hw; u8 mode; + struct clk_lookup *sclk_lookup; }; #define to_hifiberry_clk(_hw) container_of(_hw, struct clk_hifiberry_hw, hw) @@ -121,15 +123,34 @@ static int clk_hifiberry_dacpro_probe(struct platform_device *pdev) ret = devm_clk_hw_register_clkdev(dev, &proclk->hw, init.name, NULL); #endif + if (ret) { + dev_err(dev, "Fail to add clock driver\n"); + return ret; + } + + proclk->sclk_lookup = clkdev_hw_create(&proclk->hw, "sclk", NULL); + if (!proclk->sclk_lookup) { +#ifndef CONFIG_ACPI + of_clk_del_provider(dev->of_node); +#endif + return -ENOMEM; + } + + platform_set_drvdata(pdev, proclk); return ret; } static int clk_hifiberry_dacpro_remove(struct platform_device *pdev) { + struct clk_hifiberry_hw *proclk = platform_get_drvdata(pdev); + + clkdev_drop(proclk->sclk_lookup); + #ifndef CONFIG_ACPI of_clk_del_provider(pdev->dev.of_node); #endif + return 0; } From d24d3f7fcd77d109093c9235220be1d7f13ad148 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Sun, 29 Mar 2020 11:56:33 -0500 Subject: [PATCH 17/19] clk: hifiberry-dacpro: toggle GPIOs on prepare/unprepare Now that the PCM512x driver exposes GPIOs, we can set their values as needed in this clk driver (instead of doing nothing). This clk driver does not have access to the codec regmap, so it only toggles GPIOs. The user (typically a machine driver) should verify that the clocks are present by testing the PCM512x_RATE4_DET register (reports if the sclk is seen by the codec). Signed-off-by: Pierre-Louis Bossart --- drivers/clk/clk-hifiberry-dacpro.c | 110 +++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/drivers/clk/clk-hifiberry-dacpro.c b/drivers/clk/clk-hifiberry-dacpro.c index 36210f52c62410..31a50f7e4809bb 100644 --- a/drivers/clk/clk-hifiberry-dacpro.c +++ b/drivers/clk/clk-hifiberry-dacpro.c @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -20,16 +22,35 @@ /* Clock rate of CLK48EN attached to GPIO3 pin */ #define CLK_48EN_RATE 24576000UL +static struct gpiod_lookup_table pcm512x_gpios_table = { + /* .dev_id set during probe */ + .table = { + GPIO_LOOKUP("pcm512x-gpio", 2, "PCM512x-GPIO3", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("pcm512x-gpio", 5, "PCM512x-GPIO6", GPIO_ACTIVE_HIGH), + { }, + }, +}; + /** * struct clk_hifiberry_hw - Common struct to the HiFiBerry DAC Pro + * @dev: device * @hw: clk_hw for the common clk framework * @mode: 0 => CLK44EN, 1 => CLK48EN * @sclk_lookup: handle for "sclk" + * @gpio_44: gpiod desc for 44.1kHz support + * @gpio_48: gpiod desc for 48 kHz support + * @prepared: boolean caching clock state + * @gpio_initialized: boolean flag used to take gpio references. */ struct clk_hifiberry_hw { + struct device *dev; struct clk_hw hw; u8 mode; struct clk_lookup *sclk_lookup; + struct gpio_desc *gpio_44; + struct gpio_desc *gpio_48; + bool prepared; + bool gpio_initialized; }; #define to_hifiberry_clk(_hw) container_of(_hw, struct clk_hifiberry_hw, hw) @@ -69,6 +90,88 @@ static long clk_hifiberry_dacpro_round_rate(struct clk_hw *hw, return actual_rate; } +static int clk_hifiberry_dacpro_is_prepared(struct clk_hw *hw) +{ + struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw); + + return clk->prepared; +} + +static int clk_hifiberry_dacpro_prepare(struct clk_hw *hw) +{ + struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw); + + /* + * The gpios are handled here to avoid any dependencies on + * probe. + * + * The user of the clock should verify with the PCM512 + * registers that the clock are actually present and stable. + * This driver only toggles the relevant GPIOs. + */ + if (!clk->gpio_initialized) { + + clk->gpio_44 = devm_gpiod_get(clk->dev, + "PCM512x-GPIO6", + GPIOD_OUT_LOW); + if (IS_ERR(clk->gpio_44)) { + dev_err(clk->dev, "gpio44 not found\n"); + return PTR_ERR(clk->gpio_44); + } + + clk->gpio_48 = devm_gpiod_get(clk->dev, + "PCM512x-GPIO3", + GPIOD_OUT_LOW); + if (IS_ERR(clk->gpio_48)) { + dev_err(clk->dev, "gpio48 not found\n"); + return PTR_ERR(clk->gpio_48); + } + + clk->gpio_initialized = true; + } + + if (clk->prepared) + return 0; + + switch (clk->mode) { + case 0: + /* 44.1 kHz */ + gpiod_set_value_cansleep(clk->gpio_44, 1); + break; + case 1: + /* 48 kHz */ + gpiod_set_value_cansleep(clk->gpio_48, 1); + break; + default: + return -EINVAL; + } + + clk->prepared = 1; + + return 0; +} + +static void clk_hifiberry_dacpro_unprepare(struct clk_hw *hw) +{ + struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw); + + if (!clk->prepared) + return; + + switch (clk->mode) { + case 0: + gpiod_set_value_cansleep(clk->gpio_44, 0); + break; + case 1: + gpiod_set_value_cansleep(clk->gpio_48, 0); + break; + default: + return; + } + + clk->prepared = false; +} + static int clk_hifiberry_dacpro_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) @@ -83,6 +186,9 @@ static int clk_hifiberry_dacpro_set_rate(struct clk_hw *hw, } static const struct clk_ops clk_hifiberry_dacpro_rate_ops = { + .is_prepared = clk_hifiberry_dacpro_is_prepared, + .prepare = clk_hifiberry_dacpro_prepare, + .unprepare = clk_hifiberry_dacpro_unprepare, .recalc_rate = clk_hifiberry_dacpro_recalc_rate, .round_rate = clk_hifiberry_dacpro_round_rate, .set_rate = clk_hifiberry_dacpro_set_rate, @@ -97,6 +203,9 @@ static int clk_hifiberry_dacpro_probe(struct platform_device *pdev) dev = &pdev->dev; + pcm512x_gpios_table.dev_id = dev_name(dev); + gpiod_add_lookup_table(&pcm512x_gpios_table); + proclk = devm_kzalloc(dev, sizeof(*proclk), GFP_KERNEL); if (!proclk) return -ENOMEM; @@ -107,6 +216,7 @@ static int clk_hifiberry_dacpro_probe(struct platform_device *pdev) init.parent_names = NULL; init.num_parents = 0; + proclk->dev = dev; proclk->mode = 0; proclk->hw.init = &init; From 84ed808ff4b89f6509f90bd55f7fc98260d1718c Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Mon, 30 Mar 2020 14:41:42 -0500 Subject: [PATCH 18/19] clk: hifiberry-dacpro: add delay on clock prepare/deprepare Add a delay to make sure the PCM512x codec can detect SCLK presence. The initial code from the Raspberry tree used msleep(2), which can be up to 20ms. A delay of 5-10ms seems fine in practice. Signed-off-by: Pierre-Louis Bossart --- drivers/clk/clk-hifiberry-dacpro.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/clk/clk-hifiberry-dacpro.c b/drivers/clk/clk-hifiberry-dacpro.c index 31a50f7e4809bb..045dc104e44d36 100644 --- a/drivers/clk/clk-hifiberry-dacpro.c +++ b/drivers/clk/clk-hifiberry-dacpro.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -145,6 +146,8 @@ static int clk_hifiberry_dacpro_prepare(struct clk_hw *hw) default: return -EINVAL; } + /* wait for SCLK update to be detected by PCM512x codec */ + usleep_range(5000, 10000); clk->prepared = 1; @@ -168,6 +171,8 @@ static void clk_hifiberry_dacpro_unprepare(struct clk_hw *hw) default: return; } + /* wait for SCLK update to be detected by PCM512x codec */ + usleep_range(5000, 10000); clk->prepared = false; } From 44249629cd5e97c2e7bb90543c4856f805e469c9 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Thu, 2 Apr 2020 11:49:23 -0500 Subject: [PATCH 19/19] ASoC: dt-bindings: add document for Hifiberry DAC+ PRO clock The Hifiberry DAC+ PRO relies on two local audio oscillators exposed with the clock framework. Signed-off-by: Pierre-Louis Bossart --- .../bindings/sound/hifiberry-dacpro.yaml | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/hifiberry-dacpro.yaml diff --git a/Documentation/devicetree/bindings/sound/hifiberry-dacpro.yaml b/Documentation/devicetree/bindings/sound/hifiberry-dacpro.yaml new file mode 100644 index 00000000000000..9305a1a0ccd709 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/hifiberry-dacpro.yaml @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/hifiberry-dacpro.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Hifiberry DAC+ Pro clock driver + +maintainers: + - Pierre-Louis Bossart + +description: | + The Hifiberry DAC+ PRO provides two oscillators for enhanced audio + quality. The clk driver allow for select and configuration of the + clock source. + +properties: + "#clock-cells": + const: 0 + + compatible: + items: + - const: hifiberry,dacpro-clk + reg: + maxItems: 1 + +required: + - "#clock-cells" + - compatible + +examples: + - | + dacpro_osc: dacpro_osc { + compatible = "hifiberry,dacpro-clk"; + #clock-cells = <0>; + }; + +...