Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: (GPL-2.0 OR MIT)
0002 //
0003 // Copyright (c) 2018 BayLibre, SAS.
0004 // Author: Jerome Brunet <jbrunet@baylibre.com>
0005 
0006 /* This driver implements the frontend capture DAI of AXG based SoCs */
0007 
0008 #include <linux/clk.h>
0009 #include <linux/regmap.h>
0010 #include <linux/module.h>
0011 #include <linux/of_platform.h>
0012 #include <sound/pcm_params.h>
0013 #include <sound/soc.h>
0014 #include <sound/soc-dai.h>
0015 
0016 #include "axg-fifo.h"
0017 
0018 #define CTRL0_TODDR_SEL_RESAMPLE    BIT(30)
0019 #define CTRL0_TODDR_EXT_SIGNED      BIT(29)
0020 #define CTRL0_TODDR_PP_MODE     BIT(28)
0021 #define CTRL0_TODDR_SYNC_CH     BIT(27)
0022 #define CTRL0_TODDR_TYPE_MASK       GENMASK(15, 13)
0023 #define CTRL0_TODDR_TYPE(x)     ((x) << 13)
0024 #define CTRL0_TODDR_MSB_POS_MASK    GENMASK(12, 8)
0025 #define CTRL0_TODDR_MSB_POS(x)      ((x) << 8)
0026 #define CTRL0_TODDR_LSB_POS_MASK    GENMASK(7, 3)
0027 #define CTRL0_TODDR_LSB_POS(x)      ((x) << 3)
0028 #define CTRL1_TODDR_FORCE_FINISH    BIT(25)
0029 #define CTRL1_SEL_SHIFT         28
0030 
0031 #define TODDR_MSB_POS   31
0032 
0033 static int axg_toddr_pcm_new(struct snd_soc_pcm_runtime *rtd,
0034                  struct snd_soc_dai *dai)
0035 {
0036     return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_CAPTURE);
0037 }
0038 
0039 static int g12a_toddr_dai_prepare(struct snd_pcm_substream *substream,
0040                   struct snd_soc_dai *dai)
0041 {
0042     struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
0043 
0044     /* Reset the write pointer to the FIFO_INIT_ADDR */
0045     regmap_update_bits(fifo->map, FIFO_CTRL1,
0046                CTRL1_TODDR_FORCE_FINISH, 0);
0047     regmap_update_bits(fifo->map, FIFO_CTRL1,
0048                CTRL1_TODDR_FORCE_FINISH, CTRL1_TODDR_FORCE_FINISH);
0049     regmap_update_bits(fifo->map, FIFO_CTRL1,
0050                CTRL1_TODDR_FORCE_FINISH, 0);
0051 
0052     return 0;
0053 }
0054 
0055 static int axg_toddr_dai_hw_params(struct snd_pcm_substream *substream,
0056                    struct snd_pcm_hw_params *params,
0057                    struct snd_soc_dai *dai)
0058 {
0059     struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
0060     unsigned int type, width;
0061 
0062     switch (params_physical_width(params)) {
0063     case 8:
0064         type = 0; /* 8 samples of 8 bits */
0065         break;
0066     case 16:
0067         type = 2; /* 4 samples of 16 bits - right justified */
0068         break;
0069     case 32:
0070         type = 4; /* 2 samples of 32 bits - right justified */
0071         break;
0072     default:
0073         return -EINVAL;
0074     }
0075 
0076     width = params_width(params);
0077 
0078     regmap_update_bits(fifo->map, FIFO_CTRL0,
0079                CTRL0_TODDR_TYPE_MASK |
0080                CTRL0_TODDR_MSB_POS_MASK |
0081                CTRL0_TODDR_LSB_POS_MASK,
0082                CTRL0_TODDR_TYPE(type) |
0083                CTRL0_TODDR_MSB_POS(TODDR_MSB_POS) |
0084                CTRL0_TODDR_LSB_POS(TODDR_MSB_POS - (width - 1)));
0085 
0086     return 0;
0087 }
0088 
0089 static int axg_toddr_dai_startup(struct snd_pcm_substream *substream,
0090                  struct snd_soc_dai *dai)
0091 {
0092     struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
0093     int ret;
0094 
0095     /* Enable pclk to access registers and clock the fifo ip */
0096     ret = clk_prepare_enable(fifo->pclk);
0097     if (ret)
0098         return ret;
0099 
0100     /* Select orginal data - resampling not supported ATM */
0101     regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_SEL_RESAMPLE, 0);
0102 
0103     /* Only signed format are supported ATM */
0104     regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_EXT_SIGNED,
0105                CTRL0_TODDR_EXT_SIGNED);
0106 
0107     /* Apply single buffer mode to the interface */
0108     regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_PP_MODE, 0);
0109 
0110     return 0;
0111 }
0112 
0113 static void axg_toddr_dai_shutdown(struct snd_pcm_substream *substream,
0114                    struct snd_soc_dai *dai)
0115 {
0116     struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
0117 
0118     clk_disable_unprepare(fifo->pclk);
0119 }
0120 
0121 static const struct snd_soc_dai_ops axg_toddr_ops = {
0122     .hw_params  = axg_toddr_dai_hw_params,
0123     .startup    = axg_toddr_dai_startup,
0124     .shutdown   = axg_toddr_dai_shutdown,
0125 };
0126 
0127 static struct snd_soc_dai_driver axg_toddr_dai_drv = {
0128     .name = "TODDR",
0129     .capture = {
0130         .stream_name    = "Capture",
0131         .channels_min   = 1,
0132         .channels_max   = AXG_FIFO_CH_MAX,
0133         .rates      = AXG_FIFO_RATES,
0134         .formats    = AXG_FIFO_FORMATS,
0135     },
0136     .ops        = &axg_toddr_ops,
0137     .pcm_new    = axg_toddr_pcm_new,
0138 };
0139 
0140 static const char * const axg_toddr_sel_texts[] = {
0141     "IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 5", "IN 6", "IN 7"
0142 };
0143 
0144 static SOC_ENUM_SINGLE_DECL(axg_toddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT,
0145                 axg_toddr_sel_texts);
0146 
0147 static const struct snd_kcontrol_new axg_toddr_in_mux =
0148     SOC_DAPM_ENUM("Input Source", axg_toddr_sel_enum);
0149 
0150 static const struct snd_soc_dapm_widget axg_toddr_dapm_widgets[] = {
0151     SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_toddr_in_mux),
0152     SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
0153     SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
0154     SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
0155     SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
0156     SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
0157     SND_SOC_DAPM_AIF_IN("IN 5", NULL, 0, SND_SOC_NOPM, 0, 0),
0158     SND_SOC_DAPM_AIF_IN("IN 6", NULL, 0, SND_SOC_NOPM, 0, 0),
0159     SND_SOC_DAPM_AIF_IN("IN 7", NULL, 0, SND_SOC_NOPM, 0, 0),
0160 };
0161 
0162 static const struct snd_soc_dapm_route axg_toddr_dapm_routes[] = {
0163     { "Capture", NULL, "SRC SEL" },
0164     { "SRC SEL", "IN 0", "IN 0" },
0165     { "SRC SEL", "IN 1", "IN 1" },
0166     { "SRC SEL", "IN 2", "IN 2" },
0167     { "SRC SEL", "IN 3", "IN 3" },
0168     { "SRC SEL", "IN 4", "IN 4" },
0169     { "SRC SEL", "IN 5", "IN 5" },
0170     { "SRC SEL", "IN 6", "IN 6" },
0171     { "SRC SEL", "IN 7", "IN 7" },
0172 };
0173 
0174 static const struct snd_soc_component_driver axg_toddr_component_drv = {
0175     .dapm_widgets       = axg_toddr_dapm_widgets,
0176     .num_dapm_widgets   = ARRAY_SIZE(axg_toddr_dapm_widgets),
0177     .dapm_routes        = axg_toddr_dapm_routes,
0178     .num_dapm_routes    = ARRAY_SIZE(axg_toddr_dapm_routes),
0179     .open           = axg_fifo_pcm_open,
0180     .close          = axg_fifo_pcm_close,
0181     .hw_params      = axg_fifo_pcm_hw_params,
0182     .hw_free        = axg_fifo_pcm_hw_free,
0183     .pointer        = axg_fifo_pcm_pointer,
0184     .trigger        = axg_fifo_pcm_trigger,
0185     .legacy_dai_naming  = 1,
0186 };
0187 
0188 static const struct axg_fifo_match_data axg_toddr_match_data = {
0189     .field_threshold    = REG_FIELD(FIFO_CTRL1, 16, 23),
0190     .component_drv      = &axg_toddr_component_drv,
0191     .dai_drv        = &axg_toddr_dai_drv
0192 };
0193 
0194 static int g12a_toddr_dai_startup(struct snd_pcm_substream *substream,
0195                  struct snd_soc_dai *dai)
0196 {
0197     struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
0198     int ret;
0199 
0200     ret = axg_toddr_dai_startup(substream, dai);
0201     if (ret)
0202         return ret;
0203 
0204     /*
0205      * Make sure the first channel ends up in the at beginning of the output
0206      * As weird as it looks, without this the first channel may be misplaced
0207      * in memory, with a random shift of 2 channels.
0208      */
0209     regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_SYNC_CH,
0210                CTRL0_TODDR_SYNC_CH);
0211 
0212     return 0;
0213 }
0214 
0215 static const struct snd_soc_dai_ops g12a_toddr_ops = {
0216     .prepare    = g12a_toddr_dai_prepare,
0217     .hw_params  = axg_toddr_dai_hw_params,
0218     .startup    = g12a_toddr_dai_startup,
0219     .shutdown   = axg_toddr_dai_shutdown,
0220 };
0221 
0222 static struct snd_soc_dai_driver g12a_toddr_dai_drv = {
0223     .name = "TODDR",
0224     .capture = {
0225         .stream_name    = "Capture",
0226         .channels_min   = 1,
0227         .channels_max   = AXG_FIFO_CH_MAX,
0228         .rates      = AXG_FIFO_RATES,
0229         .formats    = AXG_FIFO_FORMATS,
0230     },
0231     .ops        = &g12a_toddr_ops,
0232     .pcm_new    = axg_toddr_pcm_new,
0233 };
0234 
0235 static const struct snd_soc_component_driver g12a_toddr_component_drv = {
0236     .dapm_widgets       = axg_toddr_dapm_widgets,
0237     .num_dapm_widgets   = ARRAY_SIZE(axg_toddr_dapm_widgets),
0238     .dapm_routes        = axg_toddr_dapm_routes,
0239     .num_dapm_routes    = ARRAY_SIZE(axg_toddr_dapm_routes),
0240     .open           = axg_fifo_pcm_open,
0241     .close          = axg_fifo_pcm_close,
0242     .hw_params      = g12a_fifo_pcm_hw_params,
0243     .hw_free        = axg_fifo_pcm_hw_free,
0244     .pointer        = axg_fifo_pcm_pointer,
0245     .trigger        = axg_fifo_pcm_trigger,
0246     .legacy_dai_naming  = 1,
0247 };
0248 
0249 static const struct axg_fifo_match_data g12a_toddr_match_data = {
0250     .field_threshold    = REG_FIELD(FIFO_CTRL1, 16, 23),
0251     .component_drv      = &g12a_toddr_component_drv,
0252     .dai_drv        = &g12a_toddr_dai_drv
0253 };
0254 
0255 static const char * const sm1_toddr_sel_texts[] = {
0256     "IN 0", "IN 1", "IN 2",  "IN 3",  "IN 4",  "IN 5",  "IN 6",  "IN 7",
0257     "IN 8", "IN 9", "IN 10", "IN 11", "IN 12", "IN 13", "IN 14", "IN 15"
0258 };
0259 
0260 static SOC_ENUM_SINGLE_DECL(sm1_toddr_sel_enum, FIFO_CTRL1, CTRL1_SEL_SHIFT,
0261                 sm1_toddr_sel_texts);
0262 
0263 static const struct snd_kcontrol_new sm1_toddr_in_mux =
0264     SOC_DAPM_ENUM("Input Source", sm1_toddr_sel_enum);
0265 
0266 static const struct snd_soc_dapm_widget sm1_toddr_dapm_widgets[] = {
0267     SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &sm1_toddr_in_mux),
0268     SND_SOC_DAPM_AIF_IN("IN 0",  NULL, 0, SND_SOC_NOPM, 0, 0),
0269     SND_SOC_DAPM_AIF_IN("IN 1",  NULL, 0, SND_SOC_NOPM, 0, 0),
0270     SND_SOC_DAPM_AIF_IN("IN 2",  NULL, 0, SND_SOC_NOPM, 0, 0),
0271     SND_SOC_DAPM_AIF_IN("IN 3",  NULL, 0, SND_SOC_NOPM, 0, 0),
0272     SND_SOC_DAPM_AIF_IN("IN 4",  NULL, 0, SND_SOC_NOPM, 0, 0),
0273     SND_SOC_DAPM_AIF_IN("IN 5",  NULL, 0, SND_SOC_NOPM, 0, 0),
0274     SND_SOC_DAPM_AIF_IN("IN 6",  NULL, 0, SND_SOC_NOPM, 0, 0),
0275     SND_SOC_DAPM_AIF_IN("IN 7",  NULL, 0, SND_SOC_NOPM, 0, 0),
0276     SND_SOC_DAPM_AIF_IN("IN 8",  NULL, 0, SND_SOC_NOPM, 0, 0),
0277     SND_SOC_DAPM_AIF_IN("IN 9",  NULL, 0, SND_SOC_NOPM, 0, 0),
0278     SND_SOC_DAPM_AIF_IN("IN 10", NULL, 0, SND_SOC_NOPM, 0, 0),
0279     SND_SOC_DAPM_AIF_IN("IN 11", NULL, 0, SND_SOC_NOPM, 0, 0),
0280     SND_SOC_DAPM_AIF_IN("IN 12", NULL, 0, SND_SOC_NOPM, 0, 0),
0281     SND_SOC_DAPM_AIF_IN("IN 13", NULL, 0, SND_SOC_NOPM, 0, 0),
0282     SND_SOC_DAPM_AIF_IN("IN 14", NULL, 0, SND_SOC_NOPM, 0, 0),
0283     SND_SOC_DAPM_AIF_IN("IN 15", NULL, 0, SND_SOC_NOPM, 0, 0),
0284 };
0285 
0286 static const struct snd_soc_dapm_route sm1_toddr_dapm_routes[] = {
0287     { "Capture", NULL, "SRC SEL" },
0288     { "SRC SEL", "IN 0",  "IN 0" },
0289     { "SRC SEL", "IN 1",  "IN 1" },
0290     { "SRC SEL", "IN 2",  "IN 2" },
0291     { "SRC SEL", "IN 3",  "IN 3" },
0292     { "SRC SEL", "IN 4",  "IN 4" },
0293     { "SRC SEL", "IN 5",  "IN 5" },
0294     { "SRC SEL", "IN 6",  "IN 6" },
0295     { "SRC SEL", "IN 7",  "IN 7" },
0296     { "SRC SEL", "IN 8",  "IN 8" },
0297     { "SRC SEL", "IN 9",  "IN 9" },
0298     { "SRC SEL", "IN 10", "IN 10" },
0299     { "SRC SEL", "IN 11", "IN 11" },
0300     { "SRC SEL", "IN 12", "IN 12" },
0301     { "SRC SEL", "IN 13", "IN 13" },
0302     { "SRC SEL", "IN 14", "IN 14" },
0303     { "SRC SEL", "IN 15", "IN 15" },
0304 };
0305 
0306 static const struct snd_soc_component_driver sm1_toddr_component_drv = {
0307     .dapm_widgets       = sm1_toddr_dapm_widgets,
0308     .num_dapm_widgets   = ARRAY_SIZE(sm1_toddr_dapm_widgets),
0309     .dapm_routes        = sm1_toddr_dapm_routes,
0310     .num_dapm_routes    = ARRAY_SIZE(sm1_toddr_dapm_routes),
0311     .open           = axg_fifo_pcm_open,
0312     .close          = axg_fifo_pcm_close,
0313     .hw_params      = g12a_fifo_pcm_hw_params,
0314     .hw_free        = axg_fifo_pcm_hw_free,
0315     .pointer        = axg_fifo_pcm_pointer,
0316     .trigger        = axg_fifo_pcm_trigger,
0317     .legacy_dai_naming  = 1,
0318 };
0319 
0320 static const struct axg_fifo_match_data sm1_toddr_match_data = {
0321     .field_threshold    = REG_FIELD(FIFO_CTRL1, 12, 23),
0322     .component_drv      = &sm1_toddr_component_drv,
0323     .dai_drv        = &g12a_toddr_dai_drv
0324 };
0325 
0326 static const struct of_device_id axg_toddr_of_match[] = {
0327     {
0328         .compatible = "amlogic,axg-toddr",
0329         .data = &axg_toddr_match_data,
0330     }, {
0331         .compatible = "amlogic,g12a-toddr",
0332         .data = &g12a_toddr_match_data,
0333     }, {
0334         .compatible = "amlogic,sm1-toddr",
0335         .data = &sm1_toddr_match_data,
0336     }, {}
0337 };
0338 MODULE_DEVICE_TABLE(of, axg_toddr_of_match);
0339 
0340 static struct platform_driver axg_toddr_pdrv = {
0341     .probe = axg_fifo_probe,
0342     .driver = {
0343         .name = "axg-toddr",
0344         .of_match_table = axg_toddr_of_match,
0345     },
0346 };
0347 module_platform_driver(axg_toddr_pdrv);
0348 
0349 MODULE_DESCRIPTION("Amlogic AXG capture fifo driver");
0350 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
0351 MODULE_LICENSE("GPL v2");