| /* |
| * Core MFD support for Cirrus Logic Madera codecs |
| * |
| * Copyright 2015-2016 Cirrus Logic |
| * |
| * 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. |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/gpio.h> |
| #include <linux/mfd/core.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/notifier.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/regulator/machine.h> |
| #include <linux/slab.h> |
| |
| #include <linux/mfd/madera/core.h> |
| #include <linux/mfd/madera/registers.h> |
| |
| #include "madera.h" |
| |
| #define CS47L35_SILICON_ID 0x6360 |
| #define CS47L85_SILICON_ID 0x6338 |
| #define CS47L90_SILICON_ID 0x6364 |
| #define CS47L92_SILICON_ID 0x6371 |
| |
| struct madera_sysclk_state { |
| unsigned int fll; |
| unsigned int sysclk; |
| }; |
| |
| static const char * const madera_core_supplies[] = { |
| "AVDD", |
| "DBVDD1", |
| }; |
| |
| static const struct mfd_cell madera_ldo1_devs[] = { |
| { .name = "madera-ldo1" }, |
| }; |
| |
| static const char * const cs47l35_supplies[] = { |
| "MICVDD", |
| "DBVDD2", |
| "CPVDD1", |
| "CPVDD2", |
| "SPKVDD", |
| }; |
| |
| static const struct mfd_cell cs47l35_devs[] = { |
| { .name = "madera-irq" }, |
| { .name = "madera-micsupp" }, |
| { .name = "madera-extcon" }, |
| { .name = "madera-gpio" }, |
| { .name = "madera-haptics" }, |
| { .name = "madera-pwm" }, |
| { |
| .name = "cs47l35-codec", |
| .parent_supplies = cs47l35_supplies, |
| .num_parent_supplies = ARRAY_SIZE(cs47l35_supplies), |
| }, |
| }; |
| |
| static const char * const cs47l85_supplies[] = { |
| "MICVDD", |
| "DBVDD2", |
| "DBVDD3", |
| "DBVDD4", |
| "CPVDD1", |
| "CPVDD2", |
| "SPKVDDL", |
| "SPKVDDR", |
| }; |
| |
| static const struct mfd_cell cs47l85_devs[] = { |
| { .name = "madera-irq" }, |
| { .name = "madera-micsupp" }, |
| { .name = "madera-extcon" }, |
| { .name = "madera-gpio" }, |
| { .name = "madera-haptics" }, |
| { .name = "madera-pwm" }, |
| { |
| .name = "cs47l85-codec", |
| .parent_supplies = cs47l85_supplies, |
| .num_parent_supplies = ARRAY_SIZE(cs47l85_supplies), |
| }, |
| }; |
| |
| static const char * const cs47l90_supplies[] = { |
| "MICVDD", |
| "DBVDD2", |
| "DBVDD3", |
| "DBVDD4", |
| "CPVDD1", |
| "CPVDD2", |
| }; |
| |
| static const struct mfd_cell cs47l90_devs[] = { |
| { .name = "madera-irq" }, |
| { .name = "madera-micsupp" }, |
| { .name = "madera-extcon" }, |
| { .name = "madera-gpio" }, |
| { .name = "madera-haptics" }, |
| { .name = "madera-pwm" }, |
| { |
| .name = "cs47l90-codec", |
| .parent_supplies = cs47l90_supplies, |
| .num_parent_supplies = ARRAY_SIZE(cs47l90_supplies), |
| }, |
| }; |
| |
| static const char * const cs47l92_supplies[] = { |
| "MICVDD", |
| "CPVDD1", |
| "CPVDD2", |
| }; |
| |
| static const struct mfd_cell cs47l92_devs[] = { |
| { .name = "madera-irq" }, |
| { .name = "madera-micsupp" }, |
| { .name = "madera-extcon" }, |
| { .name = "madera-gpio" }, |
| { .name = "madera-haptics" }, |
| { .name = "madera-pwm" }, |
| { |
| .name = "cs47l92-codec", |
| .parent_supplies = cs47l92_supplies, |
| .num_parent_supplies = ARRAY_SIZE(cs47l92_supplies), |
| }, |
| }; |
| |
| const char *madera_name_from_type(enum madera_type type) |
| { |
| switch (type) { |
| case CS47L35: |
| return "CS47L35"; |
| case CS47L85: |
| return "CS47L85"; |
| case CS47L90: |
| return "CS47L90"; |
| case CS47L91: |
| return "CS47L91"; |
| case CS47L92: |
| return "CS47L92"; |
| case CS47L93: |
| return "CS47L93"; |
| case WM1840: |
| return "WM1840"; |
| default: |
| return "Unknown"; |
| } |
| } |
| EXPORT_SYMBOL_GPL(madera_name_from_type); |
| |
| #ifdef CONFIG_OF |
| const struct of_device_id madera_of_match[] = { |
| { .compatible = "cirrus,cs47l35", .data = (void *)CS47L35 }, |
| { .compatible = "cirrus,cs47l85", .data = (void *)CS47L85 }, |
| { .compatible = "cirrus,cs47l90", .data = (void *)CS47L90 }, |
| { .compatible = "cirrus,cs47l91", .data = (void *)CS47L91 }, |
| { .compatible = "cirrus,wm1840", .data = (void *)WM1840 }, |
| { .compatible = "cirrus,cs47l92", .data = (void *)CS47L92 }, |
| { .compatible = "cirrus,cs47l93", .data = (void *)CS47L93 }, |
| {}, |
| }; |
| EXPORT_SYMBOL_GPL(madera_of_match); |
| #endif |
| |
| static int madera_poll_reg(struct madera *madera, |
| int timeout, unsigned int reg, |
| unsigned int mask, unsigned int target) |
| { |
| unsigned int val = 0; |
| int ret, i; |
| |
| for (i = 0; i < timeout; i++) { |
| ret = regmap_read(madera->regmap, reg, &val); |
| if (ret != 0) { |
| dev_err(madera->dev, "Failed to read reg %u: %d\n", |
| reg, ret); |
| continue; |
| } |
| |
| if ((val & mask) == target) |
| return 0; |
| |
| msleep(1); |
| } |
| |
| dev_err(madera->dev, "Polling reg %u timed out: %x\n", reg, val); |
| return -ETIMEDOUT; |
| } |
| |
| static int madera_wait_for_boot(struct madera *madera) |
| { |
| int ret; |
| |
| /* |
| * We can't use an interrupt as we need to runtime resume to do so, |
| * we won't race with the interrupt handler as it'll be blocked on |
| * runtime resume. |
| */ |
| ret = madera_poll_reg(madera, 5, MADERA_IRQ1_RAW_STATUS_1, |
| MADERA_BOOT_DONE_STS1, MADERA_BOOT_DONE_STS1); |
| |
| if (!ret) |
| regmap_write(madera->regmap, MADERA_IRQ1_STATUS_1, |
| MADERA_BOOT_DONE_EINT1); |
| |
| pm_runtime_mark_last_busy(madera->dev); |
| |
| return ret; |
| } |
| |
| static inline void madera_enable_reset(struct madera *madera) |
| { |
| if (gpio_is_valid(madera->pdata.reset)) |
| gpio_set_value_cansleep(madera->pdata.reset, 0); |
| } |
| |
| static void madera_disable_reset(struct madera *madera) |
| { |
| if (gpio_is_valid(madera->pdata.reset)) { |
| gpio_set_value_cansleep(madera->pdata.reset, 1); |
| msleep(1); |
| } |
| } |
| |
| static int madera_soft_reset(struct madera *madera) |
| { |
| int ret; |
| |
| ret = regmap_write(madera->regmap, MADERA_SOFTWARE_RESET, 0); |
| if (ret != 0) { |
| dev_err(madera->dev, "Failed to soft reset device: %d\n", ret); |
| return ret; |
| } |
| msleep(1); |
| return 0; |
| } |
| |
| static int madera_dcvdd_notify(struct notifier_block *nb, |
| unsigned long action, void *data) |
| { |
| struct madera *madera = container_of(nb, struct madera, |
| dcvdd_notifier); |
| |
| dev_dbg(madera->dev, "DCVDD notify %lx\n", action); |
| |
| if (action & REGULATOR_EVENT_DISABLE) |
| madera->dcvdd_powered_off = true; |
| |
| return NOTIFY_DONE; |
| } |
| |
| #ifdef CONFIG_PM |
| static int madera_runtime_resume(struct device *dev) |
| { |
| struct madera *madera = dev_get_drvdata(dev); |
| bool force_reset = false; |
| int ret; |
| |
| dev_dbg(madera->dev, "Leaving sleep mode\n"); |
| |
| /* If DCVDD didn't power off we must force a reset so that the |
| * cache syncs correctly. If we have a hardware reset this must |
| * be done before powering up DCVDD. If not, we'll use a software |
| * reset after powering-up DCVDD |
| */ |
| if (!madera->dcvdd_powered_off) { |
| dev_dbg(madera->dev, "DCVDD did not power off, forcing reset\n"); |
| force_reset = true; |
| madera_enable_reset(madera); |
| } |
| |
| ret = regulator_enable(madera->dcvdd); |
| if (ret) { |
| dev_err(madera->dev, "Failed to enable DCVDD: %d\n", ret); |
| return ret; |
| } |
| |
| regcache_cache_only(madera->regmap, false); |
| regcache_cache_only(madera->regmap_32bit, false); |
| |
| if (force_reset) { |
| if (gpio_is_valid(madera->pdata.reset)) { |
| madera_disable_reset(madera); |
| } else { |
| ret = madera_soft_reset(madera); |
| if (ret) |
| goto err; |
| } |
| } |
| |
| ret = madera_wait_for_boot(madera); |
| if (ret) |
| goto err; |
| |
| mutex_lock(&madera->reg_setting_lock); |
| regmap_write(madera->regmap, 0x80, 0x3); |
| ret = regcache_sync_region(madera->regmap, MADERA_HP_CHARGE_PUMP_8, |
| MADERA_HP_CHARGE_PUMP_8); |
| regmap_write(madera->regmap, 0x80, 0x0); |
| mutex_unlock(&madera->reg_setting_lock); |
| |
| if (ret) { |
| dev_err(madera->dev, "Failed to restore keyed cache\n"); |
| goto err; |
| } |
| |
| ret = regcache_sync(madera->regmap); |
| if (ret) { |
| dev_err(madera->dev, |
| "Failed to restore 16-bit register cache\n"); |
| goto err; |
| } |
| |
| ret = regcache_sync(madera->regmap_32bit); |
| if (ret) { |
| dev_err(madera->dev, |
| "Failed to restore 32-bit register cache\n"); |
| goto err; |
| } |
| |
| return 0; |
| |
| err: |
| regcache_cache_only(madera->regmap_32bit, true); |
| regcache_cache_only(madera->regmap, true); |
| madera->dcvdd_powered_off = false; |
| regulator_disable(madera->dcvdd); |
| return ret; |
| } |
| |
| static int madera_runtime_suspend(struct device *dev) |
| { |
| struct madera *madera = dev_get_drvdata(dev); |
| |
| dev_dbg(madera->dev, "Entering sleep mode\n"); |
| |
| regcache_cache_only(madera->regmap, true); |
| regcache_mark_dirty(madera->regmap); |
| regcache_cache_only(madera->regmap_32bit, true); |
| regcache_mark_dirty(madera->regmap_32bit); |
| |
| madera->dcvdd_powered_off = false; |
| regulator_disable(madera->dcvdd); |
| |
| return 0; |
| } |
| #endif |
| |
| const struct dev_pm_ops madera_pm_ops = { |
| SET_RUNTIME_PM_OPS(madera_runtime_suspend, |
| madera_runtime_resume, |
| NULL) |
| }; |
| EXPORT_SYMBOL_GPL(madera_pm_ops); |
| |
| int madera_get_num_micbias(struct madera *madera, unsigned int *n_micbiases) |
| { |
| unsigned int biases; |
| |
| switch (madera->type) { |
| case CS47L35: |
| biases = 2; |
| break; |
| case CS47L85: |
| case WM1840: |
| biases = 4; |
| break; |
| case CS47L90: |
| case CS47L91: |
| case CS47L92: |
| case CS47L93: |
| biases = 2; |
| break; |
| default: |
| BUG(); |
| } |
| |
| if (n_micbiases) |
| *n_micbiases = biases; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(madera_get_num_micbias); |
| |
| int madera_get_num_childbias(struct madera *madera, |
| unsigned int micbias, |
| unsigned int *n_child_micbiases) |
| { |
| unsigned int children = 0; |
| |
| switch (madera->type) { |
| case CS47L35: |
| children = 2; |
| break; |
| case CS47L85: |
| case WM1840: |
| children = 0; |
| break; |
| case CS47L90: |
| case CS47L91: |
| children = 4; |
| break; |
| case CS47L92: |
| case CS47L93: |
| switch (micbias) { |
| case 1: |
| children = 4; |
| break; |
| case 2: |
| children = 2; |
| break; |
| default: |
| dev_err(madera->dev, "Unknown micbias: %u\n", micbias); |
| } |
| break; |
| default: |
| BUG(); |
| } |
| |
| if (n_child_micbiases) |
| *n_child_micbiases = children; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(madera_get_num_childbias); |
| |
| #ifdef CONFIG_OF |
| unsigned long madera_of_get_type(struct device *dev) |
| { |
| const struct of_device_id *id = of_match_device(madera_of_match, dev); |
| |
| if (id) |
| return (unsigned long)id->data; |
| else |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(madera_of_get_type); |
| |
| static void madera_of_report_error(struct madera *madera, const char *prop, |
| bool mandatory, int err) |
| { |
| switch (err) { |
| case -ENOENT: |
| if (mandatory) |
| dev_err(madera->dev, |
| "Mandatory DT property %s is missing\n", prop); |
| break; |
| default: |
| dev_err(madera->dev, |
| "DT property %s is malformed: %d\n", prop, err); |
| break; |
| } |
| } |
| |
| int madera_of_read_uint_array(struct madera *madera, const char *prop, |
| bool mandatory, |
| unsigned int *dest, int minlen, int maxlen) |
| { |
| struct device_node *np = madera->dev->of_node; |
| struct property *tempprop; |
| const __be32 *cur; |
| u32 val; |
| int n_elems, i, ret; |
| |
| n_elems = of_property_count_u32_elems(np, prop); |
| if (n_elems < 0) { |
| /* of_property_count_u32_elems uses -EINVAL to mean missing */ |
| if (n_elems == -EINVAL) |
| ret = -ENOENT; |
| else |
| ret = n_elems; |
| goto err; |
| } |
| if (n_elems < minlen) { |
| ret = -EOVERFLOW; |
| goto err; |
| } |
| if (n_elems == 0) |
| return 0; |
| |
| i = 0; |
| of_property_for_each_u32(np, prop, tempprop, cur, val) { |
| if (i == maxlen) |
| break; |
| |
| dest[i++] = val; |
| } |
| |
| return i; |
| |
| err: |
| madera_of_report_error(madera, prop, mandatory, ret); |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(madera_of_read_uint_array); |
| |
| int madera_of_read_uint(struct madera *madera, const char *prop, |
| bool mandatory, unsigned int *data) |
| { |
| u32 value; |
| int ret; |
| |
| ret = of_property_read_u32(madera->dev->of_node, prop, &value); |
| if (ret < 0) { |
| /* of_property_read_u32 uses EINVAL to mean missing */ |
| if (ret == -EINVAL) |
| ret = -ENOENT; |
| madera_of_report_error(madera, prop, mandatory, ret); |
| return ret; |
| } |
| |
| *data = value; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(madera_of_read_int); |
| |
| static int madera_of_get_gpio_defaults(struct madera *madera, const char *prop) |
| { |
| struct madera_pdata *pdata = &madera->pdata; |
| int n; |
| |
| |
| n = madera_of_read_uint_array(madera, prop, false, |
| pdata->gpio_defaults, |
| 0, ARRAY_SIZE(pdata->gpio_defaults)); |
| if (n < 0) |
| return n; |
| |
| /* |
| * All values are literal except out of range values |
| * which are chip default, translate into platform |
| * data which uses 0 as chip default and out of range |
| * as zero. |
| */ |
| while (n > 0) { |
| --n; |
| if (pdata->gpio_defaults[n] > 0xffff) |
| pdata->gpio_defaults[n] = 0; /* use chip default */ |
| else if (pdata->gpio_defaults[n] == 0) |
| pdata->gpio_defaults[n] = 0xffffffff; /* set to zero */ |
| } |
| |
| return 0; |
| } |
| |
| static int madera_of_get_micbias(struct madera *madera, |
| const char *prop, int index) |
| { |
| int ret, i; |
| int j = 0; |
| u32 micbias_config[4 + MADERA_MAX_CHILD_MICBIAS] = {0}; |
| |
| BUILD_BUG_ON(ARRAY_SIZE(madera->pdata.micbias[0].discharge) != |
| MADERA_MAX_CHILD_MICBIAS); |
| |
| ret = madera_of_read_uint_array(madera, prop, false, micbias_config, |
| ARRAY_SIZE(micbias_config), |
| ARRAY_SIZE(micbias_config)); |
| |
| if (ret > 0) { |
| madera->pdata.micbias[index].mV = micbias_config[j++]; |
| madera->pdata.micbias[index].ext_cap = micbias_config[j++]; |
| for (i = 0; i < MADERA_MAX_CHILD_MICBIAS; i++) |
| madera->pdata.micbias[index].discharge[i] = |
| micbias_config[j++]; |
| madera->pdata.micbias[index].soft_start = micbias_config[j++]; |
| madera->pdata.micbias[index].bypass = micbias_config[j]; |
| } |
| |
| return ret; |
| } |
| |
| static int madera_of_get_core_pdata(struct madera *madera) |
| { |
| struct madera_pdata *pdata = &madera->pdata; |
| |
| pdata->reset = of_get_named_gpio(madera->dev->of_node, |
| "cirrus,reset-gpios", 0); |
| if (pdata->reset < 0) { |
| madera_of_report_error(madera, "cirrus,reset-gpios", false, |
| pdata->reset); |
| pdata->reset = 0; |
| } |
| |
| madera_of_read_uint(madera, "cirrus,clk32k-src", false, |
| &pdata->clk32k_src); |
| |
| madera_of_get_micbias(madera, "cirrus,micbias1", 0); |
| madera_of_get_micbias(madera, "cirrus,micbias2", 1); |
| madera_of_get_micbias(madera, "cirrus,micbias3", 2); |
| madera_of_get_micbias(madera, "cirrus,micbias4", 3); |
| |
| /* We have to deal with the gpio defaults here. If none of the pins |
| * will be used as a GPIO it's not mandatory to include the GPIO |
| * child driver but the hardware block must still be setup correctly |
| */ |
| madera_of_get_gpio_defaults(madera, "cirrus,gpio-defaults"); |
| |
| return 0; |
| } |
| #endif |
| |
| static void madera_configure_gpio(struct madera *madera) |
| { |
| unsigned int val; |
| int i; |
| |
| /* We can't leave this to the GPIO driver because most of the |
| * pins are used as digital audio interfaces, not GPIOs, |
| * so must be configured correctly on boot# |
| */ |
| for (i = 0; i < ARRAY_SIZE(madera->pdata.gpio_defaults); i++) { |
| val = madera->pdata.gpio_defaults[i]; |
| |
| if (val == 0) |
| continue; /* leave at chip default */ |
| |
| if (val > 0xffff) |
| val = 0; /* write as zero */ |
| |
| regmap_write(madera->regmap, MADERA_GPIO1_CTRL_1 + i, val); |
| } |
| } |
| |
| static int madera_configure_clk32k(struct madera *madera) |
| { |
| unsigned int src_val; |
| int ret = 0; |
| |
| switch (madera->pdata.clk32k_src) { |
| case 0: |
| /* Default to MCLK2 */ |
| src_val = MADERA_32KZ_MCLK2 - 1; |
| break; |
| case MADERA_32KZ_MCLK1: |
| case MADERA_32KZ_MCLK2: |
| case MADERA_32KZ_SYSCLK: |
| src_val = (unsigned int)madera->pdata.clk32k_src - 1; |
| break; |
| default: |
| dev_err(madera->dev, "Invalid 32kHz clock source: %d\n", |
| madera->pdata.clk32k_src); |
| return -EINVAL; |
| } |
| |
| ret = regmap_update_bits(madera->regmap, MADERA_CLOCK_32K_1, |
| MADERA_CLK_32K_ENA_MASK | MADERA_CLK_32K_SRC_MASK, |
| MADERA_CLK_32K_ENA | src_val); |
| if (ret) |
| dev_err(madera->dev, "Failed to init 32kHz clock: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static void madera_configure_micbias(struct madera *madera) |
| { |
| unsigned int max_micbias = 0, num_child_micbias = 0; |
| unsigned int val, mask; |
| int i, j; |
| |
| madera_get_num_micbias(madera, &max_micbias); |
| |
| for (i = 0; i < max_micbias; i++) { |
| if (!madera->pdata.micbias[i].mV && |
| !madera->pdata.micbias[i].bypass) |
| continue; |
| |
| /* Apply default for bypass mode */ |
| if (!madera->pdata.micbias[i].mV) |
| madera->pdata.micbias[i].mV = 2800; |
| |
| val = (madera->pdata.micbias[i].mV - 1500) / 100; |
| |
| mask = MADERA_MICB1_LVL_MASK | MADERA_MICB1_EXT_CAP | |
| MADERA_MICB1_BYPASS | MADERA_MICB1_RATE; |
| |
| val <<= MADERA_MICB1_LVL_SHIFT; |
| |
| if (madera->pdata.micbias[i].ext_cap) |
| val |= MADERA_MICB1_EXT_CAP; |
| |
| madera_get_num_childbias(madera, i+1, &num_child_micbias); |
| |
| if (num_child_micbias == 0) { |
| mask |= MADERA_MICB1_DISCH; |
| if (madera->pdata.micbias[i].discharge[0]) |
| val |= MADERA_MICB1_DISCH; |
| } |
| |
| if (madera->pdata.micbias[i].soft_start) |
| val |= MADERA_MICB1_RATE; |
| |
| if (madera->pdata.micbias[i].bypass) |
| val |= MADERA_MICB1_BYPASS; |
| |
| regmap_update_bits(madera->regmap, MADERA_MIC_BIAS_CTRL_1 + i, |
| mask, val); |
| |
| if (num_child_micbias) { |
| val = 0; |
| mask = 0; |
| for (j = 0; j < num_child_micbias; j++) { |
| mask |= (MADERA_MICB1A_DISCH << (j * 4)); |
| if (madera->pdata.micbias[i].discharge[j]) |
| val |= (MADERA_MICB1A_DISCH << (j * 4)); |
| } |
| regmap_update_bits(madera->regmap, |
| MADERA_MIC_BIAS_CTRL_5 + (i * 2), |
| mask, val); |
| } |
| } |
| } |
| |
| int madera_dev_init(struct madera *madera) |
| { |
| struct device *dev = madera->dev; |
| const char *name; |
| unsigned int hwid, reg; |
| int (*patch_fn)(struct madera *) = NULL; |
| const struct mfd_cell *mfd_devs; |
| int n_devs, i; |
| int ret; |
| |
| dev_set_drvdata(madera->dev, madera); |
| mutex_init(&madera->reg_setting_lock); |
| BLOCKING_INIT_NOTIFIER_HEAD(&madera->notifier); |
| |
| /* default headphone impedance in case the extcon driver is not used */ |
| for (i = 0; i < ARRAY_SIZE(madera->hp_impedance_x100); ++i) |
| madera->hp_impedance_x100[i] = 3200; |
| |
| if (dev_get_platdata(madera->dev)) { |
| memcpy(&madera->pdata, dev_get_platdata(madera->dev), |
| sizeof(madera->pdata)); |
| |
| /* We use 0 in pdata to indicate a GPIO has not been set, |
| * translate to -1 so that gpio_is_valid() will work |
| */ |
| if (!madera->pdata.reset) |
| madera->pdata.reset = -1; |
| } else if (IS_ENABLED(CONFIG_OF)) { |
| madera_of_get_core_pdata(madera); |
| } |
| |
| regcache_cache_only(madera->regmap, true); |
| regcache_cache_only(madera->regmap_32bit, true); |
| |
| for (i = 0; i < ARRAY_SIZE(madera_core_supplies); i++) |
| madera->core_supplies[i].supply = madera_core_supplies[i]; |
| madera->num_core_supplies = ARRAY_SIZE(madera_core_supplies); |
| |
| switch (madera->type) { |
| case CS47L35: |
| case CS47L90: |
| case CS47L91: |
| case CS47L92: |
| case CS47L93: |
| break; |
| case CS47L85: |
| case WM1840: |
| ret = mfd_add_devices(madera->dev, -1, madera_ldo1_devs, |
| ARRAY_SIZE(madera_ldo1_devs), NULL, 0, NULL); |
| if (ret != 0) { |
| dev_err(dev, "Failed to add LDO1 child: %d\n", ret); |
| return ret; |
| } |
| break; |
| default: |
| dev_err(madera->dev, "Unknown device type %d\n", madera->type); |
| return -EINVAL; |
| } |
| |
| ret = devm_regulator_bulk_get(dev, madera->num_core_supplies, |
| madera->core_supplies); |
| if (ret) { |
| dev_err(dev, "Failed to request core supplies: %d\n", ret); |
| goto err_devs; |
| } |
| |
| /** |
| * Don't use devres here because the only device we have to get |
| * against is the MFD device and DCVDD will likely be supplied by |
| * one of its children. Meaning that the regulator will be |
| * destroyed by the time devres calls regulator put. |
| */ |
| madera->dcvdd = regulator_get(madera->dev, "DCVDD"); |
| if (IS_ERR(madera->dcvdd)) { |
| ret = PTR_ERR(madera->dcvdd); |
| dev_err(dev, "Failed to request DCVDD: %d\n", ret); |
| goto err_devs; |
| } |
| |
| madera->dcvdd_notifier.notifier_call = madera_dcvdd_notify; |
| ret = regulator_register_notifier(madera->dcvdd, |
| &madera->dcvdd_notifier); |
| if (ret) { |
| dev_err(dev, "Failed to register DCVDD notifier %d\n", ret); |
| goto err_dcvdd; |
| } |
| |
| if (gpio_is_valid(madera->pdata.reset)) { |
| /* Start out with /RESET low to put the chip into reset */ |
| ret = devm_gpio_request_one(madera->dev, madera->pdata.reset, |
| GPIOF_DIR_OUT | GPIOF_INIT_LOW, |
| "madera /RESET"); |
| if (ret) { |
| dev_err(dev, "Failed to request /RESET: %d\n", ret); |
| goto err_notifier; |
| } |
| } |
| |
| /* Ensure period of reset asserted before we apply the supplies */ |
| msleep(20); |
| |
| ret = regulator_bulk_enable(madera->num_core_supplies, |
| madera->core_supplies); |
| if (ret) { |
| dev_err(dev, "Failed to enable core supplies: %d\n", ret); |
| goto err_notifier; |
| } |
| |
| ret = regulator_enable(madera->dcvdd); |
| if (ret) { |
| dev_err(dev, "Failed to enable DCVDD: %d\n", ret); |
| goto err_enable; |
| } |
| |
| madera_disable_reset(madera); |
| |
| regcache_cache_only(madera->regmap, false); |
| regcache_cache_only(madera->regmap_32bit, false); |
| |
| /* Verify that this is a chip we know about before we |
| * starting doing any writes to its registers |
| */ |
| ret = regmap_read(madera->regmap, MADERA_SOFTWARE_RESET, ®); |
| if (ret) { |
| dev_err(dev, "Failed to read ID register: %d\n", ret); |
| goto err_reset; |
| } |
| |
| switch (reg) { |
| case CS47L35_SILICON_ID: |
| case CS47L85_SILICON_ID: |
| case CS47L90_SILICON_ID: |
| case CS47L92_SILICON_ID: |
| break; |
| default: |
| dev_err(madera->dev, "Unknown device ID: %x\n", reg); |
| goto err_reset; |
| } |
| |
| /* If we don't have a /RESET GPIO use a soft reset */ |
| if (!madera->pdata.reset) { |
| ret = madera_soft_reset(madera); |
| if (ret) |
| goto err_reset; |
| } |
| |
| ret = madera_wait_for_boot(madera); |
| if (ret) { |
| dev_err(madera->dev, "Device failed initial boot: %d\n", ret); |
| goto err_reset; |
| } |
| |
| /* Read the device ID information & do device specific stuff */ |
| ret = regmap_read(madera->regmap, MADERA_SOFTWARE_RESET, &hwid); |
| if (ret) { |
| dev_err(dev, "Failed to read ID register: %d\n", ret); |
| goto err_reset; |
| } |
| |
| ret = regmap_read(madera->regmap, MADERA_HARDWARE_REVISION, |
| &madera->rev); |
| if (ret) { |
| dev_err(dev, "Failed to read revision register: %d\n", ret); |
| goto err_reset; |
| } |
| madera->rev &= MADERA_HW_REVISION_MASK; |
| |
| name = madera_name_from_type(madera->type); |
| |
| switch (hwid) { |
| case CS47L35_SILICON_ID: |
| /* should have failed SPI/I2C registration if not supported */ |
| BUG_ON(!IS_ENABLED(CONFIG_MFD_CS47L35)); |
| |
| switch (madera->type) { |
| case CS47L35: |
| patch_fn = cs47l35_patch; |
| mfd_devs = cs47l35_devs; |
| n_devs = ARRAY_SIZE(cs47l35_devs); |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } break; |
| break; |
| case CS47L85_SILICON_ID: |
| /* should have failed SPI/I2C registration if not supported */ |
| BUG_ON(!IS_ENABLED(CONFIG_MFD_CS47L85)); |
| |
| switch (madera->type) { |
| case CS47L85: |
| case WM1840: |
| patch_fn = cs47l85_patch; |
| mfd_devs = cs47l85_devs; |
| n_devs = ARRAY_SIZE(cs47l85_devs); |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| break; |
| case CS47L90_SILICON_ID: |
| /* should have failed SPI/I2C registration if not supported */ |
| BUG_ON(!IS_ENABLED(CONFIG_MFD_CS47L90)); |
| |
| switch (madera->type) { |
| case CS47L90: |
| case CS47L91: |
| patch_fn = cs47l90_patch; |
| mfd_devs = cs47l90_devs; |
| n_devs = ARRAY_SIZE(cs47l90_devs); |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } break; |
| break; |
| case CS47L92_SILICON_ID: |
| /* should have failed SPI/I2C registration if not supported */ |
| BUG_ON(!IS_ENABLED(CONFIG_MFD_CS47L92)); |
| |
| switch (madera->type) { |
| case CS47L92: |
| case CS47L93: |
| patch_fn = cs47l92_patch; |
| mfd_devs = cs47l92_devs; |
| n_devs = ARRAY_SIZE(cs47l92_devs); |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } break; |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| if (ret == -EINVAL) { |
| dev_err(madera->dev, "Device ID 0x%x is not a %s\n", |
| hwid, name); |
| goto err_reset; |
| } |
| |
| dev_info(dev, "%s silicon revision %d\n", name, madera->rev); |
| |
| /* Apply hardware patch */ |
| if (patch_fn) { |
| ret = patch_fn(madera); |
| if (ret) { |
| dev_err(madera->dev, "Failed to apply patch %d\n", ret); |
| goto err_reset; |
| } |
| } |
| |
| madera_configure_gpio(madera); |
| |
| ret = madera_configure_clk32k(madera); |
| if (ret) |
| goto err_reset; |
| |
| madera_configure_micbias(madera); |
| |
| pm_runtime_set_active(madera->dev); |
| pm_runtime_enable(madera->dev); |
| pm_runtime_set_autosuspend_delay(madera->dev, 100); |
| pm_runtime_use_autosuspend(madera->dev); |
| |
| ret = mfd_add_devices(madera->dev, -1, mfd_devs, n_devs, NULL, 0, NULL); |
| if (ret) { |
| dev_err(madera->dev, "Failed to add subdevices: %d\n", ret); |
| goto err_reset; |
| } |
| |
| return 0; |
| |
| err_reset: |
| madera_enable_reset(madera); |
| regulator_disable(madera->dcvdd); |
| err_enable: |
| regulator_bulk_disable(madera->num_core_supplies, |
| madera->core_supplies); |
| err_notifier: |
| regulator_unregister_notifier(madera->dcvdd, &madera->dcvdd_notifier); |
| err_dcvdd: |
| regulator_put(madera->dcvdd); |
| err_devs: |
| mfd_remove_devices(dev); |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(madera_dev_init); |
| |
| int madera_dev_exit(struct madera *madera) |
| { |
| pm_runtime_disable(madera->dev); |
| |
| regulator_disable(madera->dcvdd); |
| regulator_unregister_notifier(madera->dcvdd, &madera->dcvdd_notifier); |
| regulator_put(madera->dcvdd); |
| |
| mfd_remove_devices(madera->dev); |
| madera_enable_reset(madera); |
| |
| regulator_bulk_disable(madera->num_core_supplies, |
| madera->core_supplies); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(madera_dev_exit); |