root/drivers/mfd/arizona-core.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Arizona core driver
 *
 * Copyright 2012 Wolfson Microelectronics plc
 *
 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/of.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/ktime.h>
#include <linux/platform_device.h>

#include <linux/mfd/arizona/core.h>
#include <linux/mfd/arizona/registers.h>

#include "arizona.h"

static const char * const wm5102_core_supplies[] = {
        "AVDD",
        "DBVDD1",
};

int arizona_clk32k_enable(struct arizona *arizona)
{
        int ret = 0;

        mutex_lock(&arizona->clk_lock);

        arizona->clk32k_ref++;

        if (arizona->clk32k_ref == 1) {
                switch (arizona->pdata.clk32k_src) {
                case ARIZONA_32KZ_MCLK1:
                        ret = pm_runtime_resume_and_get(arizona->dev);
                        if (ret != 0)
                                goto err_ref;
                        ret = clk_prepare_enable(arizona->mclk[ARIZONA_MCLK1]);
                        if (ret != 0) {
                                pm_runtime_put_sync(arizona->dev);
                                goto err_ref;
                        }
                        break;
                case ARIZONA_32KZ_MCLK2:
                        ret = clk_prepare_enable(arizona->mclk[ARIZONA_MCLK2]);
                        if (ret != 0)
                                goto err_ref;
                        break;
                }

                ret = regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1,
                                         ARIZONA_CLK_32K_ENA,
                                         ARIZONA_CLK_32K_ENA);
        }

err_ref:
        if (ret != 0)
                arizona->clk32k_ref--;

        mutex_unlock(&arizona->clk_lock);

        return ret;
}
EXPORT_SYMBOL_GPL(arizona_clk32k_enable);

int arizona_clk32k_disable(struct arizona *arizona)
{
        mutex_lock(&arizona->clk_lock);

        WARN_ON(arizona->clk32k_ref <= 0);

        arizona->clk32k_ref--;

        if (arizona->clk32k_ref == 0) {
                regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1,
                                   ARIZONA_CLK_32K_ENA, 0);

                switch (arizona->pdata.clk32k_src) {
                case ARIZONA_32KZ_MCLK1:
                        pm_runtime_put_sync(arizona->dev);
                        clk_disable_unprepare(arizona->mclk[ARIZONA_MCLK1]);
                        break;
                case ARIZONA_32KZ_MCLK2:
                        clk_disable_unprepare(arizona->mclk[ARIZONA_MCLK2]);
                        break;
                }
        }

        mutex_unlock(&arizona->clk_lock);

        return 0;
}
EXPORT_SYMBOL_GPL(arizona_clk32k_disable);

static irqreturn_t arizona_clkgen_err(int irq, void *data)
{
        struct arizona *arizona = data;

        dev_err(arizona->dev, "CLKGEN error\n");

        return IRQ_HANDLED;
}

static irqreturn_t arizona_underclocked(int irq, void *data)
{
        struct arizona *arizona = data;
        unsigned int val;
        int ret;

        ret = regmap_read(arizona->regmap, ARIZONA_INTERRUPT_RAW_STATUS_8,
                          &val);
        if (ret != 0) {
                dev_err(arizona->dev, "Failed to read underclock status: %d\n",
                        ret);
                return IRQ_NONE;
        }

        if (val & ARIZONA_AIF3_UNDERCLOCKED_STS)
                dev_err(arizona->dev, "AIF3 underclocked\n");
        if (val & ARIZONA_AIF2_UNDERCLOCKED_STS)
                dev_err(arizona->dev, "AIF2 underclocked\n");
        if (val & ARIZONA_AIF1_UNDERCLOCKED_STS)
                dev_err(arizona->dev, "AIF1 underclocked\n");
        if (val & ARIZONA_ISRC3_UNDERCLOCKED_STS)
                dev_err(arizona->dev, "ISRC3 underclocked\n");
        if (val & ARIZONA_ISRC2_UNDERCLOCKED_STS)
                dev_err(arizona->dev, "ISRC2 underclocked\n");
        if (val & ARIZONA_ISRC1_UNDERCLOCKED_STS)
                dev_err(arizona->dev, "ISRC1 underclocked\n");
        if (val & ARIZONA_FX_UNDERCLOCKED_STS)
                dev_err(arizona->dev, "FX underclocked\n");
        if (val & ARIZONA_ASRC_UNDERCLOCKED_STS)
                dev_err(arizona->dev, "ASRC underclocked\n");
        if (val & ARIZONA_DAC_UNDERCLOCKED_STS)
                dev_err(arizona->dev, "DAC underclocked\n");
        if (val & ARIZONA_ADC_UNDERCLOCKED_STS)
                dev_err(arizona->dev, "ADC underclocked\n");
        if (val & ARIZONA_MIXER_UNDERCLOCKED_STS)
                dev_err(arizona->dev, "Mixer dropped sample\n");

        return IRQ_HANDLED;
}

static irqreturn_t arizona_overclocked(int irq, void *data)
{
        struct arizona *arizona = data;
        unsigned int val[3];
        int ret;

        ret = regmap_bulk_read(arizona->regmap, ARIZONA_INTERRUPT_RAW_STATUS_6,
                               &val[0], 3);
        if (ret != 0) {
                dev_err(arizona->dev, "Failed to read overclock status: %d\n",
                        ret);
                return IRQ_NONE;
        }

        switch (arizona->type) {
        case WM8998:
        case WM1814:
                /* Some bits are shifted on WM8998,
                 * rearrange to match the standard bit layout
                 */
                val[0] = ((val[0] & 0x60e0) >> 1) |
                         ((val[0] & 0x1e00) >> 2) |
                         (val[0] & 0x000f);
                break;
        default:
                break;
        }

        if (val[0] & ARIZONA_PWM_OVERCLOCKED_STS)
                dev_err(arizona->dev, "PWM overclocked\n");
        if (val[0] & ARIZONA_FX_CORE_OVERCLOCKED_STS)
                dev_err(arizona->dev, "FX core overclocked\n");
        if (val[0] & ARIZONA_DAC_SYS_OVERCLOCKED_STS)
                dev_err(arizona->dev, "DAC SYS overclocked\n");
        if (val[0] & ARIZONA_DAC_WARP_OVERCLOCKED_STS)
                dev_err(arizona->dev, "DAC WARP overclocked\n");
        if (val[0] & ARIZONA_ADC_OVERCLOCKED_STS)
                dev_err(arizona->dev, "ADC overclocked\n");
        if (val[0] & ARIZONA_MIXER_OVERCLOCKED_STS)
                dev_err(arizona->dev, "Mixer overclocked\n");
        if (val[0] & ARIZONA_AIF3_SYNC_OVERCLOCKED_STS)
                dev_err(arizona->dev, "AIF3 overclocked\n");
        if (val[0] & ARIZONA_AIF2_SYNC_OVERCLOCKED_STS)
                dev_err(arizona->dev, "AIF2 overclocked\n");
        if (val[0] & ARIZONA_AIF1_SYNC_OVERCLOCKED_STS)
                dev_err(arizona->dev, "AIF1 overclocked\n");
        if (val[0] & ARIZONA_PAD_CTRL_OVERCLOCKED_STS)
                dev_err(arizona->dev, "Pad control overclocked\n");

        if (val[1] & ARIZONA_SLIMBUS_SUBSYS_OVERCLOCKED_STS)
                dev_err(arizona->dev, "Slimbus subsystem overclocked\n");
        if (val[1] & ARIZONA_SLIMBUS_ASYNC_OVERCLOCKED_STS)
                dev_err(arizona->dev, "Slimbus async overclocked\n");
        if (val[1] & ARIZONA_SLIMBUS_SYNC_OVERCLOCKED_STS)
                dev_err(arizona->dev, "Slimbus sync overclocked\n");
        if (val[1] & ARIZONA_ASRC_ASYNC_SYS_OVERCLOCKED_STS)
                dev_err(arizona->dev, "ASRC async system overclocked\n");
        if (val[1] & ARIZONA_ASRC_ASYNC_WARP_OVERCLOCKED_STS)
                dev_err(arizona->dev, "ASRC async WARP overclocked\n");
        if (val[1] & ARIZONA_ASRC_SYNC_SYS_OVERCLOCKED_STS)
                dev_err(arizona->dev, "ASRC sync system overclocked\n");
        if (val[1] & ARIZONA_ASRC_SYNC_WARP_OVERCLOCKED_STS)
                dev_err(arizona->dev, "ASRC sync WARP overclocked\n");
        if (val[1] & ARIZONA_ADSP2_1_OVERCLOCKED_STS)
                dev_err(arizona->dev, "DSP1 overclocked\n");
        if (val[1] & ARIZONA_ISRC3_OVERCLOCKED_STS)
                dev_err(arizona->dev, "ISRC3 overclocked\n");
        if (val[1] & ARIZONA_ISRC2_OVERCLOCKED_STS)
                dev_err(arizona->dev, "ISRC2 overclocked\n");
        if (val[1] & ARIZONA_ISRC1_OVERCLOCKED_STS)
                dev_err(arizona->dev, "ISRC1 overclocked\n");

        if (val[2] & ARIZONA_SPDIF_OVERCLOCKED_STS)
                dev_err(arizona->dev, "SPDIF overclocked\n");

        return IRQ_HANDLED;
}

#define ARIZONA_REG_POLL_DELAY_US 7500

static inline bool arizona_poll_reg_delay(ktime_t timeout)
{
        if (ktime_compare(ktime_get(), timeout) > 0)
                return false;

        usleep_range(ARIZONA_REG_POLL_DELAY_US / 2, ARIZONA_REG_POLL_DELAY_US);

        return true;
}

static int arizona_poll_reg(struct arizona *arizona,
                            int timeout_ms, unsigned int reg,
                            unsigned int mask, unsigned int target)
{
        ktime_t timeout = ktime_add_us(ktime_get(), timeout_ms * USEC_PER_MSEC);
        unsigned int val = 0;
        int ret;

        do {
                ret = regmap_read(arizona->regmap, reg, &val);

                if ((val & mask) == target)
                        return 0;
        } while (arizona_poll_reg_delay(timeout));

        if (ret) {
                dev_err(arizona->dev, "Failed polling reg 0x%x: %d\n",
                        reg, ret);
                return ret;
        }

        dev_err(arizona->dev, "Polling reg 0x%x timed out: %x\n", reg, val);
        return -ETIMEDOUT;
}

static int arizona_wait_for_boot(struct arizona *arizona)
{
        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 = arizona_poll_reg(arizona, 30, ARIZONA_INTERRUPT_RAW_STATUS_5,
                               ARIZONA_BOOT_DONE_STS, ARIZONA_BOOT_DONE_STS);

        if (!ret)
                regmap_write(arizona->regmap, ARIZONA_INTERRUPT_STATUS_5,
                             ARIZONA_BOOT_DONE_STS);

        pm_runtime_mark_last_busy(arizona->dev);

        return ret;
}

static inline void arizona_enable_reset(struct arizona *arizona)
{
        if (arizona->pdata.reset)
                gpiod_set_raw_value_cansleep(arizona->pdata.reset, 0);
}

static void arizona_disable_reset(struct arizona *arizona)
{
        if (arizona->pdata.reset) {
                switch (arizona->type) {
                case WM5110:
                case WM8280:
                        /* Meet requirements for minimum reset duration */
                        usleep_range(5000, 10000);
                        break;
                default:
                        break;
                }

                gpiod_set_raw_value_cansleep(arizona->pdata.reset, 1);
                usleep_range(1000, 5000);
        }
}

struct arizona_sysclk_state {
        unsigned int fll;
        unsigned int sysclk;
};

static int arizona_enable_freerun_sysclk(struct arizona *arizona,
                                         struct arizona_sysclk_state *state)
{
        int ret, err;

        /* Cache existing FLL and SYSCLK settings */
        ret = regmap_read(arizona->regmap, ARIZONA_FLL1_CONTROL_1, &state->fll);
        if (ret) {
                dev_err(arizona->dev, "Failed to cache FLL settings: %d\n",
                        ret);
                return ret;
        }
        ret = regmap_read(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1,
                          &state->sysclk);
        if (ret) {
                dev_err(arizona->dev, "Failed to cache SYSCLK settings: %d\n",
                        ret);
                return ret;
        }

        /* Start up SYSCLK using the FLL in free running mode */
        ret = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1,
                        ARIZONA_FLL1_ENA | ARIZONA_FLL1_FREERUN);
        if (ret) {
                dev_err(arizona->dev,
                        "Failed to start FLL in freerunning mode: %d\n",
                        ret);
                return ret;
        }
        ret = arizona_poll_reg(arizona, 180, ARIZONA_INTERRUPT_RAW_STATUS_5,
                               ARIZONA_FLL1_CLOCK_OK_STS,
                               ARIZONA_FLL1_CLOCK_OK_STS);
        if (ret)
                goto err_fll;

        ret = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, 0x0144);
        if (ret) {
                dev_err(arizona->dev, "Failed to start SYSCLK: %d\n", ret);
                goto err_fll;
        }

        return 0;

err_fll:
        err = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, state->fll);
        if (err)
                dev_err(arizona->dev,
                        "Failed to re-apply old FLL settings: %d\n", err);

        return ret;
}

static int arizona_disable_freerun_sysclk(struct arizona *arizona,
                                          struct arizona_sysclk_state *state)
{
        int ret;

        ret = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1,
                           state->sysclk);
        if (ret) {
                dev_err(arizona->dev,
                        "Failed to re-apply old SYSCLK settings: %d\n", ret);
                return ret;
        }

        ret = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, state->fll);
        if (ret) {
                dev_err(arizona->dev,
                        "Failed to re-apply old FLL settings: %d\n", ret);
                return ret;
        }

        return 0;
}

static int wm5102_apply_hardware_patch(struct arizona *arizona)
{
        struct arizona_sysclk_state state;
        int err, ret;

        ret = arizona_enable_freerun_sysclk(arizona, &state);
        if (ret)
                return ret;

        /* Start the write sequencer and wait for it to finish */
        ret = regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_0,
                           ARIZONA_WSEQ_ENA | ARIZONA_WSEQ_START | 160);
        if (ret) {
                dev_err(arizona->dev, "Failed to start write sequencer: %d\n",
                        ret);
                goto err;
        }

        ret = arizona_poll_reg(arizona, 30, ARIZONA_WRITE_SEQUENCER_CTRL_1,
                               ARIZONA_WSEQ_BUSY, 0);
        if (ret)
                regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_0,
                             ARIZONA_WSEQ_ABORT);

err:
        err = arizona_disable_freerun_sysclk(arizona, &state);

        return ret ?: err;
}

/*
 * Register patch to some of the CODECs internal write sequences
 * to ensure a clean exit from the low power sleep state.
 */
static const struct reg_sequence wm5110_sleep_patch[] = {
        { 0x337A, 0xC100 },
        { 0x337B, 0x0041 },
        { 0x3300, 0xA210 },
        { 0x3301, 0x050C },
};

static int wm5110_apply_sleep_patch(struct arizona *arizona)
{
        struct arizona_sysclk_state state;
        int err, ret;

        ret = arizona_enable_freerun_sysclk(arizona, &state);
        if (ret)
                return ret;

        ret = regmap_multi_reg_write_bypassed(arizona->regmap,
                                              wm5110_sleep_patch,
                                              ARRAY_SIZE(wm5110_sleep_patch));

        err = arizona_disable_freerun_sysclk(arizona, &state);

        return ret ?: err;
}

static int wm5102_clear_write_sequencer(struct arizona *arizona)
{
        int ret;

        ret = regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_3,
                           0x0);
        if (ret) {
                dev_err(arizona->dev,
                        "Failed to clear write sequencer state: %d\n", ret);
                return ret;
        }

        arizona_enable_reset(arizona);
        regulator_disable(arizona->dcvdd);

        msleep(20);

        ret = regulator_enable(arizona->dcvdd);
        if (ret) {
                dev_err(arizona->dev, "Failed to re-enable DCVDD: %d\n", ret);
                return ret;
        }
        arizona_disable_reset(arizona);

        return 0;
}

static int arizona_isolate_dcvdd(struct arizona *arizona)
{
        int ret;

        ret = regmap_update_bits(arizona->regmap,
                                 ARIZONA_ISOLATION_CONTROL,
                                 ARIZONA_ISOLATE_DCVDD1,
                                 ARIZONA_ISOLATE_DCVDD1);
        if (ret != 0)
                dev_err(arizona->dev, "Failed to isolate DCVDD: %d\n", ret);

        return ret;
}

static int arizona_connect_dcvdd(struct arizona *arizona)
{
        int ret;

        ret = regmap_update_bits(arizona->regmap,
                                 ARIZONA_ISOLATION_CONTROL,
                                 ARIZONA_ISOLATE_DCVDD1, 0);
        if (ret != 0)
                dev_err(arizona->dev, "Failed to connect DCVDD: %d\n", ret);

        return ret;
}

static int arizona_is_jack_det_active(struct arizona *arizona)
{
        unsigned int val;
        int ret;

        ret = regmap_read(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, &val);
        if (ret) {
                dev_err(arizona->dev,
                        "Failed to check jack det status: %d\n", ret);
                return ret;
        } else if (val & ARIZONA_JD1_ENA) {
                return 1;
        } else {
                return 0;
        }
}

static int arizona_runtime_resume(struct device *dev)
{
        struct arizona *arizona = dev_get_drvdata(dev);
        int ret;

        dev_dbg(arizona->dev, "Leaving AoD mode\n");

        if (arizona->has_fully_powered_off) {
                dev_dbg(arizona->dev, "Re-enabling core supplies\n");

                ret = regulator_bulk_enable(arizona->num_core_supplies,
                                            arizona->core_supplies);
                if (ret) {
                        dev_err(dev, "Failed to enable core supplies: %d\n",
                                ret);
                        return ret;
                }
        }

        ret = regulator_enable(arizona->dcvdd);
        if (ret != 0) {
                dev_err(arizona->dev, "Failed to enable DCVDD: %d\n", ret);
                if (arizona->has_fully_powered_off)
                        regulator_bulk_disable(arizona->num_core_supplies,
                                               arizona->core_supplies);
                return ret;
        }

        if (arizona->has_fully_powered_off) {
                arizona_disable_reset(arizona);
                enable_irq(arizona->irq);
                arizona->has_fully_powered_off = false;
        }

        regcache_cache_only(arizona->regmap, false);

        switch (arizona->type) {
        case WM5102:
                if (arizona->external_dcvdd) {
                        ret = arizona_connect_dcvdd(arizona);
                        if (ret != 0)
                                goto err;
                }

                ret = wm5102_patch(arizona);
                if (ret != 0) {
                        dev_err(arizona->dev, "Failed to apply patch: %d\n",
                                ret);
                        goto err;
                }

                ret = wm5102_apply_hardware_patch(arizona);
                if (ret) {
                        dev_err(arizona->dev,
                                "Failed to apply hardware patch: %d\n",
                                ret);
                        goto err;
                }
                break;
        case WM5110:
        case WM8280:
                ret = arizona_wait_for_boot(arizona);
                if (ret)
                        goto err;

                if (arizona->external_dcvdd) {
                        ret = arizona_connect_dcvdd(arizona);
                        if (ret != 0)
                                goto err;
                } else {
                        /*
                         * As this is only called for the internal regulator
                         * (where we know voltage ranges available) it is ok
                         * to request an exact range.
                         */
                        ret = regulator_set_voltage(arizona->dcvdd,
                                                    1200000, 1200000);
                        if (ret < 0) {
                                dev_err(arizona->dev,
                                        "Failed to set resume voltage: %d\n",
                                        ret);
                                goto err;
                        }
                }

                ret = wm5110_apply_sleep_patch(arizona);
                if (ret) {
                        dev_err(arizona->dev,
                                "Failed to re-apply sleep patch: %d\n",
                                ret);
                        goto err;
                }
                break;
        case WM1831:
        case CS47L24:
                ret = arizona_wait_for_boot(arizona);
                if (ret != 0)
                        goto err;
                break;
        default:
                ret = arizona_wait_for_boot(arizona);
                if (ret != 0)
                        goto err;

                if (arizona->external_dcvdd) {
                        ret = arizona_connect_dcvdd(arizona);
                        if (ret != 0)
                                goto err;
                }
                break;
        }

        ret = regcache_sync(arizona->regmap);
        if (ret != 0) {
                dev_err(arizona->dev, "Failed to restore register cache\n");
                goto err;
        }

        return 0;

err:
        regcache_cache_only(arizona->regmap, true);
        regulator_disable(arizona->dcvdd);
        return ret;
}

static int arizona_runtime_suspend(struct device *dev)
{
        struct arizona *arizona = dev_get_drvdata(dev);
        int jd_active = 0;
        int ret;

        dev_dbg(arizona->dev, "Entering AoD mode\n");

        switch (arizona->type) {
        case WM5110:
        case WM8280:
                jd_active = arizona_is_jack_det_active(arizona);
                if (jd_active < 0)
                        return jd_active;

                if (arizona->external_dcvdd) {
                        ret = arizona_isolate_dcvdd(arizona);
                        if (ret != 0)
                                return ret;
                } else {
                        /*
                         * As this is only called for the internal regulator
                         * (where we know voltage ranges available) it is ok
                         * to request an exact range.
                         */
                        ret = regulator_set_voltage(arizona->dcvdd,
                                                    1175000, 1175000);
                        if (ret < 0) {
                                dev_err(arizona->dev,
                                        "Failed to set suspend voltage: %d\n",
                                        ret);
                                return ret;
                        }
                }
                break;
        case WM5102:
                jd_active = arizona_is_jack_det_active(arizona);
                if (jd_active < 0)
                        return jd_active;

                if (arizona->external_dcvdd) {
                        ret = arizona_isolate_dcvdd(arizona);
                        if (ret != 0)
                                return ret;
                }

                if (!jd_active) {
                        ret = regmap_write(arizona->regmap,
                                           ARIZONA_WRITE_SEQUENCER_CTRL_3, 0x0);
                        if (ret) {
                                dev_err(arizona->dev,
                                        "Failed to clear write sequencer: %d\n",
                                        ret);
                                return ret;
                        }
                }
                break;
        case WM1831:
        case CS47L24:
                break;
        default:
                jd_active = arizona_is_jack_det_active(arizona);
                if (jd_active < 0)
                        return jd_active;

                if (arizona->external_dcvdd) {
                        ret = arizona_isolate_dcvdd(arizona);
                        if (ret != 0)
                                return ret;
                }
                break;
        }

        regcache_cache_only(arizona->regmap, true);
        regcache_mark_dirty(arizona->regmap);
        regulator_disable(arizona->dcvdd);

        /* Allow us to completely power down if no jack detection */
        if (!jd_active) {
                dev_dbg(arizona->dev, "Fully powering off\n");

                arizona->has_fully_powered_off = true;

                disable_irq_nosync(arizona->irq);
                arizona_enable_reset(arizona);
                regulator_bulk_disable(arizona->num_core_supplies,
                                       arizona->core_supplies);
        }

        return 0;
}

static int arizona_suspend(struct device *dev)
{
        struct arizona *arizona = dev_get_drvdata(dev);

        dev_dbg(arizona->dev, "Suspend, disabling IRQ\n");
        disable_irq(arizona->irq);

        return 0;
}

static int arizona_suspend_noirq(struct device *dev)
{
        struct arizona *arizona = dev_get_drvdata(dev);

        dev_dbg(arizona->dev, "Late suspend, reenabling IRQ\n");
        enable_irq(arizona->irq);

        return 0;
}

static int arizona_resume_noirq(struct device *dev)
{
        struct arizona *arizona = dev_get_drvdata(dev);

        dev_dbg(arizona->dev, "Early resume, disabling IRQ\n");
        disable_irq(arizona->irq);

        return 0;
}

static int arizona_resume(struct device *dev)
{
        struct arizona *arizona = dev_get_drvdata(dev);

        dev_dbg(arizona->dev, "Resume, reenabling IRQ\n");
        enable_irq(arizona->irq);

        return 0;
}

EXPORT_GPL_DEV_PM_OPS(arizona_pm_ops) = {
        RUNTIME_PM_OPS(arizona_runtime_suspend,
                       arizona_runtime_resume,
                       NULL)
        SYSTEM_SLEEP_PM_OPS(arizona_suspend, arizona_resume)
        NOIRQ_SYSTEM_SLEEP_PM_OPS(arizona_suspend_noirq,
                                  arizona_resume_noirq)
};

#ifdef CONFIG_OF
static int arizona_of_get_core_pdata(struct arizona *arizona)
{
        struct arizona_pdata *pdata = &arizona->pdata;
        int ret, i;

        /* Handle old non-standard DT binding */
        pdata->reset = devm_gpiod_get(arizona->dev, "wlf,reset", GPIOD_OUT_LOW);
        if (IS_ERR(pdata->reset)) {
                ret = PTR_ERR(pdata->reset);

                /*
                 * Reset missing will be caught when other binding is read
                 * but all other errors imply this binding is in use but has
                 * encountered a problem so should be handled.
                 */
                if (ret == -EPROBE_DEFER)
                        return ret;
                else if (ret != -ENOENT && ret != -ENOSYS)
                        dev_err(arizona->dev, "Reset GPIO malformed: %d\n",
                                ret);

                pdata->reset = NULL;
        }

        ret = of_property_read_u32_array(arizona->dev->of_node,
                                         "wlf,gpio-defaults",
                                         pdata->gpio_defaults,
                                         ARRAY_SIZE(pdata->gpio_defaults));
        if (ret >= 0) {
                /*
                 * 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.
                 */
                for (i = 0; i < ARRAY_SIZE(pdata->gpio_defaults); i++) {
                        if (pdata->gpio_defaults[i] > 0xffff)
                                pdata->gpio_defaults[i] = 0;
                        else if (pdata->gpio_defaults[i] == 0)
                                pdata->gpio_defaults[i] = 0x10000;
                }
        } else {
                dev_err(arizona->dev, "Failed to parse GPIO defaults: %d\n",
                        ret);
        }

        return 0;
}
#else
static inline int arizona_of_get_core_pdata(struct arizona *arizona)
{
        return 0;
}
#endif

static const struct mfd_cell early_devs[] = {
        { .name = "arizona-ldo1" },
};

static const char * const wm5102_supplies[] = {
        "MICVDD",
        "DBVDD2",
        "DBVDD3",
        "CPVDD",
        "SPKVDDL",
        "SPKVDDR",
};

static const struct mfd_cell wm5102_devs[] = {
        { .name = "arizona-micsupp" },
        { .name = "arizona-gpio" },
        { .name = "arizona-haptics" },
        { .name = "arizona-pwm" },
        {
                .name = "wm5102-codec",
                .parent_supplies = wm5102_supplies,
                .num_parent_supplies = ARRAY_SIZE(wm5102_supplies),
        },
};

static const struct mfd_cell wm5110_devs[] = {
        { .name = "arizona-micsupp" },
        { .name = "arizona-gpio" },
        { .name = "arizona-haptics" },
        { .name = "arizona-pwm" },
        {
                .name = "wm5110-codec",
                .parent_supplies = wm5102_supplies,
                .num_parent_supplies = ARRAY_SIZE(wm5102_supplies),
        },
};

static const char * const cs47l24_supplies[] = {
        "MICVDD",
        "CPVDD",
        "SPKVDD",
};

static const struct mfd_cell cs47l24_devs[] = {
        { .name = "arizona-gpio" },
        { .name = "arizona-haptics" },
        { .name = "arizona-pwm" },
        {
                .name = "cs47l24-codec",
                .parent_supplies = cs47l24_supplies,
                .num_parent_supplies = ARRAY_SIZE(cs47l24_supplies),
        },
};

static const char * const wm8997_supplies[] = {
        "MICVDD",
        "DBVDD2",
        "CPVDD",
        "SPKVDD",
};

static const struct mfd_cell wm8997_devs[] = {
        { .name = "arizona-micsupp" },
        { .name = "arizona-gpio" },
        { .name = "arizona-haptics" },
        { .name = "arizona-pwm" },
        {
                .name = "wm8997-codec",
                .parent_supplies = wm8997_supplies,
                .num_parent_supplies = ARRAY_SIZE(wm8997_supplies),
        },
};

static const struct mfd_cell wm8998_devs[] = {
        { .name = "arizona-micsupp" },
        { .name = "arizona-gpio" },
        { .name = "arizona-haptics" },
        { .name = "arizona-pwm" },
        {
                .name = "wm8998-codec",
                .parent_supplies = wm5102_supplies,
                .num_parent_supplies = ARRAY_SIZE(wm5102_supplies),
        },
};

int arizona_dev_init(struct arizona *arizona)
{
        static const char * const mclk_name[] = { "mclk1", "mclk2" };
        struct device *dev = arizona->dev;
        const char *type_name = NULL;
        unsigned int reg, val;
        int (*apply_patch)(struct arizona *) = NULL;
        const struct mfd_cell *subdevs = NULL;
        int n_subdevs = 0, ret, i;

        dev_set_drvdata(arizona->dev, arizona);
        mutex_init(&arizona->clk_lock);

        if (dev_get_platdata(arizona->dev)) {
                memcpy(&arizona->pdata, dev_get_platdata(arizona->dev),
                       sizeof(arizona->pdata));
        } else {
                ret = arizona_of_get_core_pdata(arizona);
                if (ret < 0)
                        return ret;
        }

        BUILD_BUG_ON(ARRAY_SIZE(arizona->mclk) != ARRAY_SIZE(mclk_name));
        for (i = 0; i < ARRAY_SIZE(arizona->mclk); i++) {
                arizona->mclk[i] = devm_clk_get(arizona->dev, mclk_name[i]);
                if (IS_ERR(arizona->mclk[i])) {
                        dev_info(arizona->dev, "Failed to get %s: %ld\n",
                                 mclk_name[i], PTR_ERR(arizona->mclk[i]));
                        arizona->mclk[i] = NULL;
                }
        }

        regcache_cache_only(arizona->regmap, true);

        switch (arizona->type) {
        case WM5102:
        case WM5110:
        case WM8280:
        case WM8997:
        case WM8998:
        case WM1814:
        case WM1831:
        case CS47L24:
                for (i = 0; i < ARRAY_SIZE(wm5102_core_supplies); i++)
                        arizona->core_supplies[i].supply
                                = wm5102_core_supplies[i];
                arizona->num_core_supplies = ARRAY_SIZE(wm5102_core_supplies);
                break;
        default:
                dev_err(arizona->dev, "Unknown device type %d\n",
                        arizona->type);
                return -ENODEV;
        }

        /* Mark DCVDD as external, LDO1 driver will clear if internal */
        arizona->external_dcvdd = true;

        switch (arizona->type) {
        case WM1831:
        case CS47L24:
                break; /* No LDO1 regulator */
        default:
                ret = mfd_add_devices(arizona->dev, -1, early_devs,
                                      ARRAY_SIZE(early_devs), NULL, 0, NULL);
                if (ret != 0) {
                        dev_err(dev, "Failed to add early children: %d\n", ret);
                        return ret;
                }
                break;
        }

        ret = devm_regulator_bulk_get(dev, arizona->num_core_supplies,
                                      arizona->core_supplies);
        if (ret != 0) {
                dev_err(dev, "Failed to request core supplies: %d\n",
                        ret);
                goto err_early;
        }

        /**
         * 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.
         */
        arizona->dcvdd = regulator_get(arizona->dev, "DCVDD");
        if (IS_ERR(arizona->dcvdd)) {
                ret = PTR_ERR(arizona->dcvdd);
                dev_err(dev, "Failed to request DCVDD: %d\n", ret);
                goto err_early;
        }

        if (!arizona->pdata.reset) {
                /* Start out with /RESET low to put the chip into reset */
                arizona->pdata.reset = devm_gpiod_get(arizona->dev, "reset",
                                                      GPIOD_OUT_LOW);
                if (IS_ERR(arizona->pdata.reset)) {
                        ret = PTR_ERR(arizona->pdata.reset);
                        if (ret == -EPROBE_DEFER)
                                goto err_dcvdd;

                        dev_err(arizona->dev,
                                "Reset GPIO missing/malformed: %d\n", ret);

                        arizona->pdata.reset = NULL;
                }
        }

        ret = regulator_bulk_enable(arizona->num_core_supplies,
                                    arizona->core_supplies);
        if (ret != 0) {
                dev_err(dev, "Failed to enable core supplies: %d\n",
                        ret);
                goto err_dcvdd;
        }

        ret = regulator_enable(arizona->dcvdd);
        if (ret != 0) {
                dev_err(dev, "Failed to enable DCVDD: %d\n", ret);
                goto err_enable;
        }

        arizona_disable_reset(arizona);

        regcache_cache_only(arizona->regmap, false);

        /* Verify that this is a chip we know about */
        ret = regmap_read(arizona->regmap, ARIZONA_SOFTWARE_RESET, &reg);
        if (ret != 0) {
                dev_err(dev, "Failed to read ID register: %d\n", ret);
                goto err_reset;
        }

        switch (reg) {
        case 0x5102:
        case 0x5110:
        case 0x6349:
        case 0x6363:
        case 0x8997:
                break;
        default:
                dev_err(arizona->dev, "Unknown device ID: %x\n", reg);
                ret = -ENODEV;
                goto err_reset;
        }

        /* If we have a /RESET GPIO we'll already be reset */
        if (!arizona->pdata.reset) {
                ret = regmap_write(arizona->regmap, ARIZONA_SOFTWARE_RESET, 0);
                if (ret != 0) {
                        dev_err(dev, "Failed to reset device: %d\n", ret);
                        goto err_reset;
                }

                usleep_range(1000, 5000);
        }

        /* Ensure device startup is complete */
        switch (arizona->type) {
        case WM5102:
                ret = regmap_read(arizona->regmap,
                                  ARIZONA_WRITE_SEQUENCER_CTRL_3, &val);
                if (ret) {
                        dev_err(dev,
                                "Failed to check write sequencer state: %d\n",
                                ret);
                } else if (val & 0x01) {
                        ret = wm5102_clear_write_sequencer(arizona);
                        if (ret)
                                goto err_reset;
                }
                break;
        default:
                break;
        }

        ret = arizona_wait_for_boot(arizona);
        if (ret) {
                dev_err(arizona->dev, "Device failed initial boot: %d\n", ret);
                goto err_reset;
        }

        /* Read the device ID information & do device specific stuff */
        ret = regmap_read(arizona->regmap, ARIZONA_SOFTWARE_RESET, &reg);
        if (ret != 0) {
                dev_err(dev, "Failed to read ID register: %d\n", ret);
                goto err_reset;
        }

        ret = regmap_read(arizona->regmap, ARIZONA_DEVICE_REVISION,
                          &arizona->rev);
        if (ret != 0) {
                dev_err(dev, "Failed to read revision register: %d\n", ret);
                goto err_reset;
        }
        arizona->rev &= ARIZONA_DEVICE_REVISION_MASK;

        switch (reg) {
        case 0x5102:
                if (IS_ENABLED(CONFIG_MFD_WM5102)) {
                        type_name = "WM5102";
                        if (arizona->type != WM5102) {
                                dev_warn(arizona->dev,
                                         "WM5102 registered as %d\n",
                                         arizona->type);
                                arizona->type = WM5102;
                        }

                        apply_patch = wm5102_patch;
                        arizona->rev &= 0x7;
                        subdevs = wm5102_devs;
                        n_subdevs = ARRAY_SIZE(wm5102_devs);
                }
                break;
        case 0x5110:
                if (IS_ENABLED(CONFIG_MFD_WM5110)) {
                        switch (arizona->type) {
                        case WM5110:
                                type_name = "WM5110";
                                break;
                        case WM8280:
                                type_name = "WM8280";
                                break;
                        default:
                                type_name = "WM5110";
                                dev_warn(arizona->dev,
                                         "WM5110 registered as %d\n",
                                         arizona->type);
                                arizona->type = WM5110;
                                break;
                        }

                        apply_patch = wm5110_patch;
                        subdevs = wm5110_devs;
                        n_subdevs = ARRAY_SIZE(wm5110_devs);
                }
                break;
        case 0x6363:
                if (IS_ENABLED(CONFIG_MFD_CS47L24)) {
                        switch (arizona->type) {
                        case CS47L24:
                                type_name = "CS47L24";
                                break;

                        case WM1831:
                                type_name = "WM1831";
                                break;

                        default:
                                dev_warn(arizona->dev,
                                         "CS47L24 registered as %d\n",
                                         arizona->type);
                                arizona->type = CS47L24;
                                break;
                        }

                        apply_patch = cs47l24_patch;
                        subdevs = cs47l24_devs;
                        n_subdevs = ARRAY_SIZE(cs47l24_devs);
                }
                break;
        case 0x8997:
                if (IS_ENABLED(CONFIG_MFD_WM8997)) {
                        type_name = "WM8997";
                        if (arizona->type != WM8997) {
                                dev_warn(arizona->dev,
                                         "WM8997 registered as %d\n",
                                         arizona->type);
                                arizona->type = WM8997;
                        }

                        apply_patch = wm8997_patch;
                        subdevs = wm8997_devs;
                        n_subdevs = ARRAY_SIZE(wm8997_devs);
                }
                break;
        case 0x6349:
                if (IS_ENABLED(CONFIG_MFD_WM8998)) {
                        switch (arizona->type) {
                        case WM8998:
                                type_name = "WM8998";
                                break;

                        case WM1814:
                                type_name = "WM1814";
                                break;

                        default:
                                type_name = "WM8998";
                                dev_warn(arizona->dev,
                                         "WM8998 registered as %d\n",
                                         arizona->type);
                                arizona->type = WM8998;
                        }

                        apply_patch = wm8998_patch;
                        subdevs = wm8998_devs;
                        n_subdevs = ARRAY_SIZE(wm8998_devs);
                }
                break;
        default:
                dev_err(arizona->dev, "Unknown device ID %x\n", reg);
                ret = -ENODEV;
                goto err_reset;
        }

        if (!subdevs) {
                dev_err(arizona->dev,
                        "No kernel support for device ID %x\n", reg);
                ret = -ENODEV;
                goto err_reset;
        }

        dev_info(dev, "%s revision %c\n", type_name, arizona->rev + 'A');

        if (apply_patch) {
                ret = apply_patch(arizona);
                if (ret != 0) {
                        dev_err(arizona->dev, "Failed to apply patch: %d\n",
                                ret);
                        goto err_reset;
                }

                switch (arizona->type) {
                case WM5102:
                        ret = wm5102_apply_hardware_patch(arizona);
                        if (ret) {
                                dev_err(arizona->dev,
                                        "Failed to apply hardware patch: %d\n",
                                        ret);
                                goto err_reset;
                        }
                        break;
                case WM5110:
                case WM8280:
                        ret = wm5110_apply_sleep_patch(arizona);
                        if (ret) {
                                dev_err(arizona->dev,
                                        "Failed to apply sleep patch: %d\n",
                                        ret);
                                goto err_reset;
                        }
                        break;
                default:
                        break;
                }
        }

        for (i = 0; i < ARRAY_SIZE(arizona->pdata.gpio_defaults); i++) {
                if (!arizona->pdata.gpio_defaults[i])
                        continue;

                regmap_write(arizona->regmap, ARIZONA_GPIO1_CTRL + i,
                             arizona->pdata.gpio_defaults[i]);
        }

        /* Chip default */
        if (!arizona->pdata.clk32k_src)
                arizona->pdata.clk32k_src = ARIZONA_32KZ_MCLK2;

        switch (arizona->pdata.clk32k_src) {
        case ARIZONA_32KZ_MCLK1:
        case ARIZONA_32KZ_MCLK2:
                regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1,
                                   ARIZONA_CLK_32K_SRC_MASK,
                                   arizona->pdata.clk32k_src - 1);
                arizona_clk32k_enable(arizona);
                break;
        case ARIZONA_32KZ_NONE:
                regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1,
                                   ARIZONA_CLK_32K_SRC_MASK, 2);
                break;
        default:
                dev_err(arizona->dev, "Invalid 32kHz clock source: %d\n",
                        arizona->pdata.clk32k_src);
                ret = -EINVAL;
                goto err_reset;
        }

        for (i = 0; i < ARIZONA_MAX_MICBIAS; i++) {
                if (!arizona->pdata.micbias[i].mV &&
                    !arizona->pdata.micbias[i].bypass)
                        continue;

                /* Apply default for bypass mode */
                if (!arizona->pdata.micbias[i].mV)
                        arizona->pdata.micbias[i].mV = 2800;

                val = (arizona->pdata.micbias[i].mV - 1500) / 100;

                val <<= ARIZONA_MICB1_LVL_SHIFT;

                if (arizona->pdata.micbias[i].ext_cap)
                        val |= ARIZONA_MICB1_EXT_CAP;

                if (arizona->pdata.micbias[i].discharge)
                        val |= ARIZONA_MICB1_DISCH;

                if (arizona->pdata.micbias[i].soft_start)
                        val |= ARIZONA_MICB1_RATE;

                if (arizona->pdata.micbias[i].bypass)
                        val |= ARIZONA_MICB1_BYPASS;

                regmap_update_bits(arizona->regmap,
                                   ARIZONA_MIC_BIAS_CTRL_1 + i,
                                   ARIZONA_MICB1_LVL_MASK |
                                   ARIZONA_MICB1_EXT_CAP |
                                   ARIZONA_MICB1_DISCH |
                                   ARIZONA_MICB1_BYPASS |
                                   ARIZONA_MICB1_RATE, val);
        }

        pm_runtime_set_active(arizona->dev);
        pm_runtime_enable(arizona->dev);

        /* Set up for interrupts */
        ret = arizona_irq_init(arizona);
        if (ret != 0)
                goto err_pm;

        pm_runtime_set_autosuspend_delay(arizona->dev, 100);
        pm_runtime_use_autosuspend(arizona->dev);

        arizona_request_irq(arizona, ARIZONA_IRQ_CLKGEN_ERR, "CLKGEN error",
                            arizona_clkgen_err, arizona);
        arizona_request_irq(arizona, ARIZONA_IRQ_OVERCLOCKED, "Overclocked",
                            arizona_overclocked, arizona);
        arizona_request_irq(arizona, ARIZONA_IRQ_UNDERCLOCKED, "Underclocked",
                            arizona_underclocked, arizona);

        ret = mfd_add_devices(arizona->dev, PLATFORM_DEVID_NONE,
                              subdevs, n_subdevs, NULL, 0, NULL);

        if (ret) {
                dev_err(arizona->dev, "Failed to add subdevices: %d\n", ret);
                goto err_irq;
        }

        return 0;

err_irq:
        arizona_irq_exit(arizona);
err_pm:
        pm_runtime_disable(arizona->dev);

        switch (arizona->pdata.clk32k_src) {
        case ARIZONA_32KZ_MCLK1:
        case ARIZONA_32KZ_MCLK2:
                arizona_clk32k_disable(arizona);
                break;
        default:
                break;
        }
err_reset:
        arizona_enable_reset(arizona);
        regulator_disable(arizona->dcvdd);
err_enable:
        regulator_bulk_disable(arizona->num_core_supplies,
                               arizona->core_supplies);
err_dcvdd:
        regulator_put(arizona->dcvdd);
err_early:
        mfd_remove_devices(dev);
        return ret;
}
EXPORT_SYMBOL_GPL(arizona_dev_init);

int arizona_dev_exit(struct arizona *arizona)
{
        disable_irq(arizona->irq);
        pm_runtime_disable(arizona->dev);

        regulator_disable(arizona->dcvdd);
        regulator_put(arizona->dcvdd);

        switch (arizona->pdata.clk32k_src) {
        case ARIZONA_32KZ_MCLK1:
        case ARIZONA_32KZ_MCLK2:
                arizona_clk32k_disable(arizona);
                break;
        default:
                break;
        }

        mfd_remove_devices(arizona->dev);
        arizona_free_irq(arizona, ARIZONA_IRQ_UNDERCLOCKED, arizona);
        arizona_free_irq(arizona, ARIZONA_IRQ_OVERCLOCKED, arizona);
        arizona_free_irq(arizona, ARIZONA_IRQ_CLKGEN_ERR, arizona);
        arizona_irq_exit(arizona);
        arizona_enable_reset(arizona);

        regulator_bulk_disable(arizona->num_core_supplies,
                               arizona->core_supplies);
        return 0;
}
EXPORT_SYMBOL_GPL(arizona_dev_exit);

MODULE_DESCRIPTION("Wolfson Arizona core driver");
MODULE_LICENSE("GPL v2");