summaryrefslogtreecommitdiff
path: root/drivers/mfd/madera-core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd/madera-core.c')
-rw-r--r--drivers/mfd/madera-core.c261
1 files changed, 220 insertions, 41 deletions
diff --git a/drivers/mfd/madera-core.c b/drivers/mfd/madera-core.c
index 2a77988d0462..2f74a8c644a3 100644
--- a/drivers/mfd/madera-core.c
+++ b/drivers/mfd/madera-core.c
@@ -1,24 +1,19 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Core MFD support for Cirrus Logic Madera codecs
*
* Copyright (C) 2015-2018 Cirrus Logic
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by the
- * Free Software Foundation; version 2.
*/
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/err.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/of.h>
-#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
@@ -31,19 +26,52 @@
#include "madera.h"
+#define CS47L15_SILICON_ID 0x6370
#define CS47L35_SILICON_ID 0x6360
#define CS47L85_SILICON_ID 0x6338
#define CS47L90_SILICON_ID 0x6364
+#define CS47L92_SILICON_ID 0x6371
#define MADERA_32KZ_MCLK2 1
+#define MADERA_RESET_MIN_US 2000
+#define MADERA_RESET_MAX_US 3000
+
+#define ERRATA_DCVDD_MIN_US 10000
+#define ERRATA_DCVDD_MAX_US 15000
+
static const char * const madera_core_supplies[] = {
"AVDD",
"DBVDD1",
};
static const struct mfd_cell madera_ldo1_devs[] = {
- { .name = "madera-ldo1" },
+ {
+ .name = "madera-ldo1",
+ .level = MFD_DEP_LEVEL_HIGH,
+ },
+};
+
+static const char * const cs47l15_supplies[] = {
+ "MICVDD",
+ "CPVDD1",
+ "SPKVDD",
+};
+
+static const struct mfd_cell cs47l15_devs[] = {
+ { .name = "madera-pinctrl", },
+ { .name = "madera-irq", },
+ { .name = "madera-gpio", },
+ {
+ .name = "madera-extcon",
+ .parent_supplies = cs47l15_supplies,
+ .num_parent_supplies = 1, /* We only need MICVDD */
+ },
+ {
+ .name = "cs47l15-codec",
+ .parent_supplies = cs47l15_supplies,
+ .num_parent_supplies = ARRAY_SIZE(cs47l15_supplies),
+ },
};
static const char * const cs47l35_supplies[] = {
@@ -59,7 +87,11 @@ static const struct mfd_cell cs47l35_devs[] = {
{ .name = "madera-irq", },
{ .name = "madera-micsupp", },
{ .name = "madera-gpio", },
- { .name = "madera-extcon", },
+ {
+ .name = "madera-extcon",
+ .parent_supplies = cs47l35_supplies,
+ .num_parent_supplies = 1, /* We only need MICVDD */
+ },
{
.name = "cs47l35-codec",
.parent_supplies = cs47l35_supplies,
@@ -81,9 +113,13 @@ static const char * const cs47l85_supplies[] = {
static const struct mfd_cell cs47l85_devs[] = {
{ .name = "madera-pinctrl", },
{ .name = "madera-irq", },
- { .name = "madera-micsupp" },
+ { .name = "madera-micsupp", },
{ .name = "madera-gpio", },
- { .name = "madera-extcon", },
+ {
+ .name = "madera-extcon",
+ .parent_supplies = cs47l85_supplies,
+ .num_parent_supplies = 1, /* We only need MICVDD */
+ },
{
.name = "cs47l85-codec",
.parent_supplies = cs47l85_supplies,
@@ -105,7 +141,11 @@ static const struct mfd_cell cs47l90_devs[] = {
{ .name = "madera-irq", },
{ .name = "madera-micsupp", },
{ .name = "madera-gpio", },
- { .name = "madera-extcon", },
+ {
+ .name = "madera-extcon",
+ .parent_supplies = cs47l90_supplies,
+ .num_parent_supplies = 1, /* We only need MICVDD */
+ },
{
.name = "cs47l90-codec",
.parent_supplies = cs47l90_supplies,
@@ -113,10 +153,35 @@ static const struct mfd_cell cs47l90_devs[] = {
},
};
+static const char * const cs47l92_supplies[] = {
+ "MICVDD",
+ "CPVDD1",
+ "CPVDD2",
+};
+
+static const struct mfd_cell cs47l92_devs[] = {
+ { .name = "madera-pinctrl", },
+ { .name = "madera-irq", },
+ { .name = "madera-micsupp", },
+ { .name = "madera-gpio", },
+ {
+ .name = "madera-extcon",
+ .parent_supplies = cs47l92_supplies,
+ .num_parent_supplies = 1, /* We only need MICVDD */
+ },
+ {
+ .name = "cs47l92-codec",
+ .parent_supplies = cs47l92_supplies,
+ .num_parent_supplies = ARRAY_SIZE(cs47l92_supplies),
+ },
+};
+
/* Used by madera-i2c and madera-spi drivers */
const char *madera_name_from_type(enum madera_type type)
{
switch (type) {
+ case CS47L15:
+ return "CS47L15";
case CS47L35:
return "CS47L35";
case CS47L85:
@@ -125,6 +190,12 @@ const char *madera_name_from_type(enum madera_type type)
return "CS47L90";
case CS47L91:
return "CS47L91";
+ case CS42L92:
+ return "CS42L92";
+ case CS47L92:
+ return "CS47L92";
+ case CS47L93:
+ return "CS47L93";
case WM1840:
return "WM1840";
default:
@@ -136,10 +207,10 @@ EXPORT_SYMBOL_GPL(madera_name_from_type);
#define MADERA_BOOT_POLL_INTERVAL_USEC 5000
#define MADERA_BOOT_POLL_TIMEOUT_USEC 25000
-static int madera_wait_for_boot(struct madera *madera)
+static int madera_wait_for_boot_noack(struct madera *madera)
{
ktime_t timeout;
- unsigned int val;
+ unsigned int val = 0;
int ret = 0;
/*
@@ -163,6 +234,13 @@ static int madera_wait_for_boot(struct madera *madera)
ret = -ETIMEDOUT;
}
+ return ret;
+}
+
+static int madera_wait_for_boot(struct madera *madera)
+{
+ int ret = madera_wait_for_boot_noack(madera);
+
/*
* BOOT_DONE defaults to unmasked on boot so we must ack it.
* Do this even after a timeout to avoid interrupt storms.
@@ -186,16 +264,13 @@ static int madera_soft_reset(struct madera *madera)
}
/* Allow time for internal clocks to startup after reset */
- usleep_range(1000, 2000);
+ usleep_range(MADERA_RESET_MIN_US, MADERA_RESET_MAX_US);
return 0;
}
static void madera_enable_hard_reset(struct madera *madera)
{
- if (!madera->pdata.reset)
- return;
-
/*
* There are many existing out-of-tree users of these codecs that we
* can't break so preserve the expected behaviour of setting the line
@@ -206,11 +281,9 @@ static void madera_enable_hard_reset(struct madera *madera)
static void madera_disable_hard_reset(struct madera *madera)
{
- if (!madera->pdata.reset)
- return;
-
gpiod_set_raw_value_cansleep(madera->pdata.reset, 1);
- usleep_range(1000, 2000);
+
+ usleep_range(MADERA_RESET_MIN_US, MADERA_RESET_MAX_US);
}
static int __maybe_unused madera_runtime_resume(struct device *dev)
@@ -220,6 +293,9 @@ static int __maybe_unused madera_runtime_resume(struct device *dev)
dev_dbg(dev, "Leaving sleep mode\n");
+ if (!madera->reset_errata)
+ madera_enable_hard_reset(madera);
+
ret = regulator_enable(madera->dcvdd);
if (ret) {
dev_err(dev, "Failed to enable DCVDD: %d\n", ret);
@@ -229,6 +305,23 @@ static int __maybe_unused madera_runtime_resume(struct device *dev)
regcache_cache_only(madera->regmap, false);
regcache_cache_only(madera->regmap_32bit, false);
+ if (madera->reset_errata)
+ usleep_range(ERRATA_DCVDD_MIN_US, ERRATA_DCVDD_MAX_US);
+ else
+ madera_disable_hard_reset(madera);
+
+ if (!madera->pdata.reset || madera->reset_errata) {
+ ret = madera_wait_for_boot(madera);
+ if (ret)
+ goto err;
+
+ ret = madera_soft_reset(madera);
+ if (ret) {
+ dev_err(dev, "Failed to reset: %d\n", ret);
+ goto err;
+ }
+ }
+
ret = madera_wait_for_boot(madera);
if (ret)
goto err;
@@ -279,31 +372,31 @@ const struct dev_pm_ops madera_pm_ops = {
EXPORT_SYMBOL_GPL(madera_pm_ops);
const struct of_device_id madera_of_match[] = {
+ { .compatible = "cirrus,cs47l15", .data = (void *)CS47L15 },
{ .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,cs42l92", .data = (void *)CS42L92 },
+ { .compatible = "cirrus,cs47l92", .data = (void *)CS47L92 },
+ { .compatible = "cirrus,cs47l93", .data = (void *)CS47L93 },
{ .compatible = "cirrus,wm1840", .data = (void *)WM1840 },
{}
};
+MODULE_DEVICE_TABLE(of, madera_of_match);
EXPORT_SYMBOL_GPL(madera_of_match);
static int madera_get_reset_gpio(struct madera *madera)
{
struct gpio_desc *reset;
- int ret;
if (madera->pdata.reset)
return 0;
reset = devm_gpiod_get_optional(madera->dev, "reset", GPIOD_OUT_LOW);
- if (IS_ERR(reset)) {
- ret = PTR_ERR(reset);
- if (ret != -EPROBE_DEFER)
- dev_err(madera->dev, "Failed to request /RESET: %d\n",
- ret);
- return ret;
- }
+ if (IS_ERR(reset))
+ return dev_err_probe(madera->dev, PTR_ERR(reset),
+ "Failed to request /RESET");
/*
* A hard reset is needed for full reset of the chip. We allow running
@@ -326,6 +419,10 @@ static void madera_set_micbias_info(struct madera *madera)
* childbiases for each micbias. Unspecified values default to 0.
*/
switch (madera->type) {
+ case CS47L15:
+ madera->num_micbias = 1;
+ madera->num_childbias[0] = 3;
+ return;
case CS47L35:
madera->num_micbias = 2;
madera->num_childbias[0] = 2;
@@ -342,6 +439,13 @@ static void madera_set_micbias_info(struct madera *madera)
madera->num_childbias[0] = 4;
madera->num_childbias[1] = 4;
return;
+ case CS42L92:
+ case CS47L92:
+ case CS47L93:
+ madera->num_micbias = 2;
+ madera->num_childbias[0] = 4;
+ madera->num_childbias[1] = 2;
+ return;
default:
return;
}
@@ -352,7 +456,7 @@ int madera_dev_init(struct madera *madera)
struct device *dev = madera->dev;
unsigned int hwid;
int (*patch_fn)(struct madera *) = NULL;
- const struct mfd_cell *mfd_devs;
+ const struct mfd_cell *mfd_devs = NULL;
int n_devs = 0;
int i, ret;
@@ -371,6 +475,21 @@ int madera_dev_init(struct madera *madera)
sizeof(madera->pdata));
}
+ madera->mclk[MADERA_MCLK1].id = "mclk1";
+ madera->mclk[MADERA_MCLK2].id = "mclk2";
+ madera->mclk[MADERA_MCLK3].id = "mclk3";
+
+ ret = devm_clk_bulk_get_optional(madera->dev, ARRAY_SIZE(madera->mclk),
+ madera->mclk);
+ if (ret) {
+ dev_err(madera->dev, "Failed to get clocks: %d\n", ret);
+ return ret;
+ }
+
+ /* Not using devm_clk_get to prevent breakage of existing DTs */
+ if (!madera->mclk[MADERA_MCLK2].clk)
+ dev_warn(madera->dev, "Missing MCLK2, requires 32kHz clock\n");
+
ret = madera_get_reset_gpio(madera);
if (ret)
return ret;
@@ -389,9 +508,15 @@ int madera_dev_init(struct madera *madera)
* No devm_ because we need to control shutdown order of children.
*/
switch (madera->type) {
+ case CS47L15:
+ madera->reset_errata = true;
+ break;
case CS47L35:
case CS47L90:
case CS47L91:
+ case CS42L92:
+ case CS47L92:
+ case CS47L93:
break;
case CS47L85:
case WM1840:
@@ -436,17 +561,29 @@ int madera_dev_init(struct madera *madera)
goto err_dcvdd;
}
+ if (madera->reset_errata)
+ madera_disable_hard_reset(madera);
+
ret = regulator_enable(madera->dcvdd);
if (ret) {
dev_err(dev, "Failed to enable DCVDD: %d\n", ret);
goto err_enable;
}
- madera_disable_hard_reset(madera);
+ if (madera->reset_errata)
+ usleep_range(ERRATA_DCVDD_MIN_US, ERRATA_DCVDD_MAX_US);
+ else
+ madera_disable_hard_reset(madera);
regcache_cache_only(madera->regmap, false);
regcache_cache_only(madera->regmap_32bit, false);
+ ret = madera_wait_for_boot_noack(madera);
+ if (ret) {
+ dev_err(madera->dev, "Device failed initial boot: %d\n", ret);
+ goto err_reset;
+ }
+
/*
* Now we can power up and verify that this is a chip we know about
* before we start doing any writes to its registers.
@@ -458,6 +595,19 @@ int madera_dev_init(struct madera *madera)
}
switch (hwid) {
+ case CS47L15_SILICON_ID:
+ if (IS_ENABLED(CONFIG_MFD_CS47L15)) {
+ switch (madera->type) {
+ case CS47L15:
+ patch_fn = &cs47l15_patch;
+ mfd_devs = cs47l15_devs;
+ n_devs = ARRAY_SIZE(cs47l15_devs);
+ break;
+ default:
+ break;
+ }
+ }
+ break;
case CS47L35_SILICON_ID:
if (IS_ENABLED(CONFIG_MFD_CS47L35)) {
switch (madera->type) {
@@ -499,13 +649,28 @@ int madera_dev_init(struct madera *madera)
}
}
break;
+ case CS47L92_SILICON_ID:
+ if (IS_ENABLED(CONFIG_MFD_CS47L92)) {
+ switch (madera->type) {
+ case CS42L92:
+ case CS47L92:
+ case CS47L93:
+ patch_fn = cs47l92_patch;
+ mfd_devs = cs47l92_devs;
+ n_devs = ARRAY_SIZE(cs47l92_devs);
+ break;
+ default:
+ break;
+ }
+ }
+ break;
default:
dev_err(madera->dev, "Unknown device ID: %x\n", hwid);
ret = -EINVAL;
goto err_reset;
}
- if (!n_devs) {
+ if (!n_devs || !mfd_devs) {
dev_err(madera->dev, "Device ID 0x%x not a %s\n", hwid,
madera->type_name);
ret = -ENODEV;
@@ -516,7 +681,7 @@ int madera_dev_init(struct madera *madera)
* It looks like a device we support. If we don't have a hard reset
* we can now attempt a soft reset.
*/
- if (!madera->pdata.reset) {
+ if (!madera->pdata.reset || madera->reset_errata) {
ret = madera_soft_reset(madera);
if (ret)
goto err_reset;
@@ -524,7 +689,7 @@ int madera_dev_init(struct madera *madera)
ret = madera_wait_for_boot(madera);
if (ret) {
- dev_err(madera->dev, "Device failed initial boot: %d\n", ret);
+ dev_err(madera->dev, "Failed to clear boot done: %d\n", ret);
goto err_reset;
}
@@ -549,13 +714,19 @@ int madera_dev_init(struct madera *madera)
}
/* Init 32k clock sourced from MCLK2 */
+ ret = clk_prepare_enable(madera->mclk[MADERA_MCLK2].clk);
+ if (ret) {
+ dev_err(madera->dev, "Failed to enable 32k clock: %d\n", ret);
+ goto err_reset;
+ }
+
ret = regmap_update_bits(madera->regmap,
MADERA_CLOCK_32K_1,
MADERA_CLK_32K_ENA_MASK | MADERA_CLK_32K_SRC_MASK,
MADERA_CLK_32K_ENA | MADERA_32KZ_MCLK2);
if (ret) {
dev_err(madera->dev, "Failed to init 32k clock: %d\n", ret);
- goto err_reset;
+ goto err_clock;
}
pm_runtime_set_active(madera->dev);
@@ -576,6 +747,8 @@ int madera_dev_init(struct madera *madera)
err_pm_runtime:
pm_runtime_disable(madera->dev);
+err_clock:
+ clk_disable_unprepare(madera->mclk[MADERA_MCLK2].clk);
err_reset:
madera_enable_hard_reset(madera);
regulator_disable(madera->dcvdd);
@@ -596,16 +769,22 @@ int madera_dev_exit(struct madera *madera)
/* Prevent any IRQs being serviced while we clean up */
disable_irq(madera->irq);
- /*
- * DCVDD could be supplied by a child node, we must disable it before
- * removing the children, and prevent PM runtime from turning it back on
- */
+ pm_runtime_get_sync(madera->dev);
+
+ mfd_remove_devices(madera->dev);
+
pm_runtime_disable(madera->dev);
regulator_disable(madera->dcvdd);
regulator_put(madera->dcvdd);
- mfd_remove_devices(madera->dev);
+ mfd_remove_devices_late(madera->dev);
+
+ pm_runtime_set_suspended(madera->dev);
+ pm_runtime_put_noidle(madera->dev);
+
+ clk_disable_unprepare(madera->mclk[MADERA_MCLK2].clk);
+
madera_enable_hard_reset(madera);
regulator_bulk_disable(madera->num_core_supplies,