321 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * IMG parallel output controller driver4 *5 * Copyright (C) 2015 Imagination Technologies Ltd.6 *7 * Author: Damien Horsley <Damien.Horsley@imgtec.com>8 */9 10#include <linux/clk.h>11#include <linux/init.h>12#include <linux/kernel.h>13#include <linux/module.h>14#include <linux/of.h>15#include <linux/platform_device.h>16#include <linux/pm_runtime.h>17#include <linux/reset.h>18 19#include <sound/core.h>20#include <sound/dmaengine_pcm.h>21#include <sound/initval.h>22#include <sound/pcm.h>23#include <sound/pcm_params.h>24#include <sound/soc.h>25 26#define IMG_PRL_OUT_TX_FIFO 027 28#define IMG_PRL_OUT_CTL 0x429#define IMG_PRL_OUT_CTL_CH_MASK BIT(4)30#define IMG_PRL_OUT_CTL_PACKH_MASK BIT(3)31#define IMG_PRL_OUT_CTL_EDGE_MASK BIT(2)32#define IMG_PRL_OUT_CTL_ME_MASK BIT(1)33#define IMG_PRL_OUT_CTL_SRST_MASK BIT(0)34 35struct img_prl_out {36 void __iomem *base;37 struct clk *clk_sys;38 struct clk *clk_ref;39 struct snd_dmaengine_dai_dma_data dma_data;40 struct device *dev;41 struct reset_control *rst;42};43 44static int img_prl_out_suspend(struct device *dev)45{46 struct img_prl_out *prl = dev_get_drvdata(dev);47 48 clk_disable_unprepare(prl->clk_ref);49 50 return 0;51}52 53static int img_prl_out_resume(struct device *dev)54{55 struct img_prl_out *prl = dev_get_drvdata(dev);56 int ret;57 58 ret = clk_prepare_enable(prl->clk_ref);59 if (ret) {60 dev_err(dev, "clk_enable failed: %d\n", ret);61 return ret;62 }63 64 return 0;65}66 67static inline void img_prl_out_writel(struct img_prl_out *prl,68 u32 val, u32 reg)69{70 writel(val, prl->base + reg);71}72 73static inline u32 img_prl_out_readl(struct img_prl_out *prl, u32 reg)74{75 return readl(prl->base + reg);76}77 78static void img_prl_out_reset(struct img_prl_out *prl)79{80 u32 ctl;81 82 ctl = img_prl_out_readl(prl, IMG_PRL_OUT_CTL) &83 ~IMG_PRL_OUT_CTL_ME_MASK;84 85 reset_control_assert(prl->rst);86 reset_control_deassert(prl->rst);87 88 img_prl_out_writel(prl, ctl, IMG_PRL_OUT_CTL);89}90 91static int img_prl_out_trigger(struct snd_pcm_substream *substream, int cmd,92 struct snd_soc_dai *dai)93{94 struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);95 u32 reg;96 97 switch (cmd) {98 case SNDRV_PCM_TRIGGER_START:99 case SNDRV_PCM_TRIGGER_RESUME:100 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:101 reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL);102 reg |= IMG_PRL_OUT_CTL_ME_MASK;103 img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL);104 break;105 case SNDRV_PCM_TRIGGER_STOP:106 case SNDRV_PCM_TRIGGER_SUSPEND:107 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:108 img_prl_out_reset(prl);109 break;110 default:111 return -EINVAL;112 }113 114 return 0;115}116 117static int img_prl_out_hw_params(struct snd_pcm_substream *substream,118 struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)119{120 struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);121 unsigned int rate, channels;122 u32 reg, control_set = 0;123 124 rate = params_rate(params);125 channels = params_channels(params);126 127 switch (params_format(params)) {128 case SNDRV_PCM_FORMAT_S32_LE:129 control_set |= IMG_PRL_OUT_CTL_PACKH_MASK;130 break;131 case SNDRV_PCM_FORMAT_S24_LE:132 break;133 default:134 return -EINVAL;135 }136 137 if (channels != 2)138 return -EINVAL;139 140 clk_set_rate(prl->clk_ref, rate * 256);141 142 reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL);143 reg = (reg & ~IMG_PRL_OUT_CTL_PACKH_MASK) | control_set;144 img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL);145 146 return 0;147}148 149static int img_prl_out_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)150{151 struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);152 u32 reg, control_set = 0;153 int ret;154 155 switch (fmt & SND_SOC_DAIFMT_INV_MASK) {156 case SND_SOC_DAIFMT_NB_NF:157 break;158 case SND_SOC_DAIFMT_NB_IF:159 control_set |= IMG_PRL_OUT_CTL_EDGE_MASK;160 break;161 default:162 return -EINVAL;163 }164 165 ret = pm_runtime_resume_and_get(prl->dev);166 if (ret < 0)167 return ret;168 169 reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL);170 reg = (reg & ~IMG_PRL_OUT_CTL_EDGE_MASK) | control_set;171 img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL);172 pm_runtime_put(prl->dev);173 174 return 0;175}176 177static int img_prl_out_dai_probe(struct snd_soc_dai *dai)178{179 struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);180 181 snd_soc_dai_init_dma_data(dai, &prl->dma_data, NULL);182 183 return 0;184}185 186static const struct snd_soc_dai_ops img_prl_out_dai_ops = {187 .probe = img_prl_out_dai_probe,188 .trigger = img_prl_out_trigger,189 .hw_params = img_prl_out_hw_params,190 .set_fmt = img_prl_out_set_fmt191};192 193static struct snd_soc_dai_driver img_prl_out_dai = {194 .playback = {195 .channels_min = 2,196 .channels_max = 2,197 .rates = SNDRV_PCM_RATE_8000_192000,198 .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE199 },200 .ops = &img_prl_out_dai_ops201};202 203static const struct snd_soc_component_driver img_prl_out_component = {204 .name = "img-prl-out",205 .legacy_dai_naming = 1,206};207 208static int img_prl_out_probe(struct platform_device *pdev)209{210 struct img_prl_out *prl;211 struct resource *res;212 void __iomem *base;213 int ret;214 struct device *dev = &pdev->dev;215 216 prl = devm_kzalloc(&pdev->dev, sizeof(*prl), GFP_KERNEL);217 if (!prl)218 return -ENOMEM;219 220 platform_set_drvdata(pdev, prl);221 222 prl->dev = &pdev->dev;223 224 base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);225 if (IS_ERR(base))226 return PTR_ERR(base);227 228 prl->base = base;229 230 prl->rst = devm_reset_control_get_exclusive(&pdev->dev, "rst");231 if (IS_ERR(prl->rst))232 return dev_err_probe(&pdev->dev, PTR_ERR(prl->rst),233 "No top level reset found\n");234 235 prl->clk_sys = devm_clk_get(&pdev->dev, "sys");236 if (IS_ERR(prl->clk_sys))237 return dev_err_probe(dev, PTR_ERR(prl->clk_sys),238 "Failed to acquire clock 'sys'\n");239 240 prl->clk_ref = devm_clk_get(&pdev->dev, "ref");241 if (IS_ERR(prl->clk_ref))242 return dev_err_probe(dev, PTR_ERR(prl->clk_ref),243 "Failed to acquire clock 'ref'\n");244 245 ret = clk_prepare_enable(prl->clk_sys);246 if (ret)247 return ret;248 249 img_prl_out_writel(prl, IMG_PRL_OUT_CTL_EDGE_MASK, IMG_PRL_OUT_CTL);250 img_prl_out_reset(prl);251 252 pm_runtime_enable(&pdev->dev);253 if (!pm_runtime_enabled(&pdev->dev)) {254 ret = img_prl_out_resume(&pdev->dev);255 if (ret)256 goto err_pm_disable;257 }258 259 prl->dma_data.addr = res->start + IMG_PRL_OUT_TX_FIFO;260 prl->dma_data.addr_width = 4;261 prl->dma_data.maxburst = 4;262 263 ret = devm_snd_soc_register_component(&pdev->dev,264 &img_prl_out_component,265 &img_prl_out_dai, 1);266 if (ret)267 goto err_suspend;268 269 ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);270 if (ret)271 goto err_suspend;272 273 return 0;274 275err_suspend:276 if (!pm_runtime_status_suspended(&pdev->dev))277 img_prl_out_suspend(&pdev->dev);278err_pm_disable:279 pm_runtime_disable(&pdev->dev);280 clk_disable_unprepare(prl->clk_sys);281 282 return ret;283}284 285static void img_prl_out_dev_remove(struct platform_device *pdev)286{287 struct img_prl_out *prl = platform_get_drvdata(pdev);288 289 pm_runtime_disable(&pdev->dev);290 if (!pm_runtime_status_suspended(&pdev->dev))291 img_prl_out_suspend(&pdev->dev);292 293 clk_disable_unprepare(prl->clk_sys);294}295 296static const struct of_device_id img_prl_out_of_match[] = {297 { .compatible = "img,parallel-out" },298 {}299};300MODULE_DEVICE_TABLE(of, img_prl_out_of_match);301 302static const struct dev_pm_ops img_prl_out_pm_ops = {303 SET_RUNTIME_PM_OPS(img_prl_out_suspend,304 img_prl_out_resume, NULL)305};306 307static struct platform_driver img_prl_out_driver = {308 .driver = {309 .name = "img-parallel-out",310 .of_match_table = img_prl_out_of_match,311 .pm = &img_prl_out_pm_ops312 },313 .probe = img_prl_out_probe,314 .remove = img_prl_out_dev_remove315};316module_platform_driver(img_prl_out_driver);317 318MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");319MODULE_DESCRIPTION("IMG Parallel Output Driver");320MODULE_LICENSE("GPL v2");321