blob: 2047d20423ca5856f8bd09dfcd7fbb2b0f09b961 [file] [log] [blame]
/*
* 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, &reg);
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);