// SPDX-License-Identifier: GPL-2.0 /* * This file is part the core part STM32 DFSDM driver * * Copyright (C) 2017, STMicroelectronics - All Rights Reserved * Author(s): Arnaud Pouliquen for STMicroelectronics. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "stm32-dfsdm.h" /** * struct stm32_dfsdm_dev_data - DFSDM compatible configuration data * @ipid: DFSDM identification number. Used only if hardware provides identification registers * @num_filters: DFSDM number of filters. Unused if identification registers are available * @num_channels: DFSDM number of channels. Unused if identification registers are available * @regmap_cfg: SAI register map configuration pointer */ struct stm32_dfsdm_dev_data { u32 ipid; unsigned int num_filters; unsigned int num_channels; const struct regmap_config *regmap_cfg; }; #define STM32H7_DFSDM_NUM_FILTERS 4 #define STM32H7_DFSDM_NUM_CHANNELS 8 static bool stm32_dfsdm_volatile_reg(struct device *dev, unsigned int reg) { if (reg < DFSDM_FILTER_BASE_ADR) return false; /* * Mask is done on register to avoid to list registers of all * filter instances. */ switch (reg & DFSDM_FILTER_REG_MASK) { case DFSDM_CR1(0) & DFSDM_FILTER_REG_MASK: case DFSDM_ISR(0) & DFSDM_FILTER_REG_MASK: case DFSDM_JDATAR(0) & DFSDM_FILTER_REG_MASK: case DFSDM_RDATAR(0) & DFSDM_FILTER_REG_MASK: return true; } return false; } static const struct regmap_config stm32h7_dfsdm_regmap_cfg = { .reg_bits = 32, .val_bits = 32, .reg_stride = sizeof(u32), .max_register = 0x2B8, .volatile_reg = stm32_dfsdm_volatile_reg, .fast_io = true, }; static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_data = { .num_filters = STM32H7_DFSDM_NUM_FILTERS, .num_channels = STM32H7_DFSDM_NUM_CHANNELS, .regmap_cfg = &stm32h7_dfsdm_regmap_cfg, }; static const struct regmap_config stm32mp1_dfsdm_regmap_cfg = { .reg_bits = 32, .val_bits = 32, .reg_stride = sizeof(u32), .max_register = 0x7fc, .volatile_reg = stm32_dfsdm_volatile_reg, .fast_io = true, }; static const struct stm32_dfsdm_dev_data stm32mp1_dfsdm_data = { .ipid = STM32MP15_IPIDR_NUMBER, .regmap_cfg = &stm32mp1_dfsdm_regmap_cfg, }; struct dfsdm_priv { struct platform_device *pdev; /* platform device */ struct stm32_dfsdm dfsdm; /* common data exported for all instances */ unsigned int spi_clk_out_div; /* SPI clkout divider value */ atomic_t n_active_ch; /* number of current active channels */ struct clk *clk; /* DFSDM clock */ struct clk *aclk; /* audio clock */ }; static inline struct dfsdm_priv *to_stm32_dfsdm_priv(struct stm32_dfsdm *dfsdm) { return container_of(dfsdm, struct dfsdm_priv, dfsdm); } static int stm32_dfsdm_clk_prepare_enable(struct stm32_dfsdm *dfsdm) { struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); int ret; ret = clk_prepare_enable(priv->clk); if (ret || !priv->aclk) return ret; ret = clk_prepare_enable(priv->aclk); if (ret) clk_disable_unprepare(priv->clk); return ret; } static void stm32_dfsdm_clk_disable_unprepare(struct stm32_dfsdm *dfsdm) { struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); clk_disable_unprepare(priv->aclk); clk_disable_unprepare(priv->clk); } /** * stm32_dfsdm_start_dfsdm - start global dfsdm interface. * * Enable interface if n_active_ch is not null. * @dfsdm: Handle used to retrieve dfsdm context. */ int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm) { struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); struct device *dev = &priv->pdev->dev; unsigned int clk_div = priv->spi_clk_out_div, clk_src; int ret; if (atomic_inc_return(&priv->n_active_ch) == 1) { ret = pm_runtime_resume_and_get(dev); if (ret < 0) goto error_ret; /* select clock source, e.g. 0 for "dfsdm" or 1 for "audio" */ clk_src = priv->aclk ? 1 : 0; ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), DFSDM_CHCFGR1_CKOUTSRC_MASK, DFSDM_CHCFGR1_CKOUTSRC(clk_src)); if (ret < 0) goto pm_put; /* Output the SPI CLKOUT (if clk_div == 0 clock if OFF) */ ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), DFSDM_CHCFGR1_CKOUTDIV_MASK, DFSDM_CHCFGR1_CKOUTDIV(clk_div)); if (ret < 0) goto pm_put; /* Global enable of DFSDM interface */ ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), DFSDM_CHCFGR1_DFSDMEN_MASK, DFSDM_CHCFGR1_DFSDMEN(1)); if (ret < 0) goto pm_put; } dev_dbg(dev, "%s: n_active_ch %d\n", __func__, atomic_read(&priv->n_active_ch)); return 0; pm_put: pm_runtime_put_sync(dev); error_ret: atomic_dec(&priv->n_active_ch); return ret; } EXPORT_SYMBOL_GPL(stm32_dfsdm_start_dfsdm); /** * stm32_dfsdm_stop_dfsdm - stop global DFSDM interface. * * Disable interface if n_active_ch is null * @dfsdm: Handle used to retrieve dfsdm context. */ int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm) { struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); int ret; if (atomic_dec_and_test(&priv->n_active_ch)) { /* Global disable of DFSDM interface */ ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), DFSDM_CHCFGR1_DFSDMEN_MASK, DFSDM_CHCFGR1_DFSDMEN(0)); if (ret < 0) return ret; /* Stop SPI CLKOUT */ ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), DFSDM_CHCFGR1_CKOUTDIV_MASK, DFSDM_CHCFGR1_CKOUTDIV(0)); if (ret < 0) return ret; pm_runtime_put_sync(&priv->pdev->dev); } dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__, atomic_read(&priv->n_active_ch)); return 0; } EXPORT_SYMBOL_GPL(stm32_dfsdm_stop_dfsdm); static int stm32_dfsdm_parse_of(struct platform_device *pdev, struct dfsdm_priv *priv) { struct device_node *node = pdev->dev.of_node; struct resource *res; unsigned long clk_freq, divider; unsigned int spi_freq, rem; int ret; if (!node) return -EINVAL; priv->dfsdm.base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(priv->dfsdm.base)) return PTR_ERR(priv->dfsdm.base); priv->dfsdm.phys_base = res->start; /* * "dfsdm" clock is mandatory for DFSDM peripheral clocking. * "dfsdm" or "audio" clocks can be used as source clock for * the SPI clock out signal and internal processing, depending * on use case. */ priv->clk = devm_clk_get(&pdev->dev, "dfsdm"); if (IS_ERR(priv->clk)) return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk), "Failed to get clock\n"); priv->aclk = devm_clk_get(&pdev->dev, "audio"); if (IS_ERR(priv->aclk)) priv->aclk = NULL; if (priv->aclk) clk_freq = clk_get_rate(priv->aclk); else clk_freq = clk_get_rate(priv->clk); /* SPI clock out frequency */ ret = of_property_read_u32(pdev->dev.of_node, "spi-max-frequency", &spi_freq); if (ret < 0) { /* No SPI master mode */ return 0; } divider = div_u64_rem(clk_freq, spi_freq, &rem); /* Round up divider when ckout isn't precise, not to exceed spi_freq */ if (rem) divider++; /* programmable divider is in range of [2:256] */ if (divider < 2 || divider > 256) { dev_err(&pdev->dev, "spi-max-frequency not achievable\n"); return -EINVAL; } /* SPI clock output divider is: divider = CKOUTDIV + 1 */ priv->spi_clk_out_div = divider - 1; priv->dfsdm.spi_master_freq = clk_freq / (priv->spi_clk_out_div + 1); if (rem) { dev_warn(&pdev->dev, "SPI clock not accurate\n"); dev_warn(&pdev->dev, "%ld = %d * %d + %d\n", clk_freq, spi_freq, priv->spi_clk_out_div + 1, rem); } return 0; }; static const struct of_device_id stm32_dfsdm_of_match[] = { { .compatible = "st,stm32h7-dfsdm", .data = &stm32h7_dfsdm_data, }, { .compatible = "st,stm32mp1-dfsdm", .data = &stm32mp1_dfsdm_data, }, { } }; MODULE_DEVICE_TABLE(of, stm32_dfsdm_of_match); static int stm32_dfsdm_probe_identification(struct platform_device *pdev, struct dfsdm_priv *priv, const struct stm32_dfsdm_dev_data *dev_data) { struct device_node *np = pdev->dev.of_node; struct device_node *child; struct stm32_dfsdm *dfsdm = &priv->dfsdm; const char *compat; int ret, count = 0; u32 id, val; if (!dev_data->ipid) { dfsdm->num_fls = dev_data->num_filters; dfsdm->num_chs = dev_data->num_channels; return 0; } ret = regmap_read(dfsdm->regmap, DFSDM_IPIDR, &id); if (ret) return ret; if (id != dev_data->ipid) { dev_err(&pdev->dev, "Unexpected IP version: 0x%x", id); return -EINVAL; } for_each_child_of_node(np, child) { ret = of_property_read_string(child, "compatible", &compat); if (ret) continue; /* Count only child nodes with dfsdm compatible */ if (strstr(compat, "dfsdm")) count++; } ret = regmap_read(dfsdm->regmap, DFSDM_HWCFGR, &val); if (ret) return ret; dfsdm->num_fls = FIELD_GET(DFSDM_HWCFGR_NBF_MASK, val); dfsdm->num_chs = FIELD_GET(DFSDM_HWCFGR_NBT_MASK, val); if (count > dfsdm->num_fls) { dev_err(&pdev->dev, "Unexpected child number: %d", count); return -EINVAL; } ret = regmap_read(dfsdm->regmap, DFSDM_VERR, &val); if (ret) return ret; dev_dbg(&pdev->dev, "DFSDM version: %lu.%lu. %d channels/%d filters\n", FIELD_GET(DFSDM_VERR_MAJREV_MASK, val), FIELD_GET(DFSDM_VERR_MINREV_MASK, val), dfsdm->num_chs, dfsdm->num_fls); return 0; } static int stm32_dfsdm_probe(struct platform_device *pdev) { struct dfsdm_priv *priv; const struct stm32_dfsdm_dev_data *dev_data; struct stm32_dfsdm *dfsdm; int ret; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->pdev = pdev; dev_data = of_device_get_match_data(&pdev->dev); dfsdm = &priv->dfsdm; ret = stm32_dfsdm_parse_of(pdev, priv); if (ret < 0) return ret; dfsdm->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "dfsdm", dfsdm->base, dev_data->regmap_cfg); if (IS_ERR(dfsdm->regmap)) { ret = PTR_ERR(dfsdm->regmap); dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n", __func__, ret); return ret; } ret = stm32_dfsdm_probe_identification(pdev, priv, dev_data); if (ret < 0) return ret; dfsdm->fl_list = devm_kcalloc(&pdev->dev, dfsdm->num_fls, sizeof(*dfsdm->fl_list), GFP_KERNEL); if (!dfsdm->fl_list) return -ENOMEM; dfsdm->ch_list = devm_kcalloc(&pdev->dev, dfsdm->num_chs, sizeof(*dfsdm->ch_list), GFP_KERNEL); if (!dfsdm->ch_list) return -ENOMEM; platform_set_drvdata(pdev, dfsdm); ret = stm32_dfsdm_clk_prepare_enable(dfsdm); if (ret) { dev_err(&pdev->dev, "Failed to start clock\n"); return ret; } pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); if (ret) goto pm_put; pm_runtime_put(&pdev->dev); return 0; pm_put: pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); stm32_dfsdm_clk_disable_unprepare(dfsdm); return ret; } static void stm32_dfsdm_core_remove(struct platform_device *pdev) { struct stm32_dfsdm *dfsdm = platform_get_drvdata(pdev); pm_runtime_get_sync(&pdev->dev); of_platform_depopulate(&pdev->dev); pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); stm32_dfsdm_clk_disable_unprepare(dfsdm); } static int stm32_dfsdm_core_suspend(struct device *dev) { struct stm32_dfsdm *dfsdm = dev_get_drvdata(dev); struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); int ret; ret = pm_runtime_force_suspend(dev); if (ret) return ret; /* Balance devm_regmap_init_mmio_clk() clk_prepare() */ clk_unprepare(priv->clk); return pinctrl_pm_select_sleep_state(dev); } static int stm32_dfsdm_core_resume(struct device *dev) { struct stm32_dfsdm *dfsdm = dev_get_drvdata(dev); struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); int ret; ret = pinctrl_pm_select_default_state(dev); if (ret) return ret; ret = clk_prepare(priv->clk); if (ret) return ret; return pm_runtime_force_resume(dev); } static int stm32_dfsdm_core_runtime_suspend(struct device *dev) { struct stm32_dfsdm *dfsdm = dev_get_drvdata(dev); stm32_dfsdm_clk_disable_unprepare(dfsdm); return 0; } static int stm32_dfsdm_core_runtime_resume(struct device *dev) { struct stm32_dfsdm *dfsdm = dev_get_drvdata(dev); return stm32_dfsdm_clk_prepare_enable(dfsdm); } static const struct dev_pm_ops stm32_dfsdm_core_pm_ops = { SYSTEM_SLEEP_PM_OPS(stm32_dfsdm_core_suspend, stm32_dfsdm_core_resume) RUNTIME_PM_OPS(stm32_dfsdm_core_runtime_suspend, stm32_dfsdm_core_runtime_resume, NULL) }; static struct platform_driver stm32_dfsdm_driver = { .probe = stm32_dfsdm_probe, .remove = stm32_dfsdm_core_remove, .driver = { .name = "stm32-dfsdm", .of_match_table = stm32_dfsdm_of_match, .pm = pm_ptr(&stm32_dfsdm_core_pm_ops), }, }; module_platform_driver(stm32_dfsdm_driver); MODULE_AUTHOR("Arnaud Pouliquen "); MODULE_DESCRIPTION("STMicroelectronics STM32 dfsdm driver"); MODULE_LICENSE("GPL v2");