[alsa-devel] [PATCH 2/2] ALSA: hda - Add driver for Tegra SoC HDA

Dylan Reid dgreid at chromium.org
Thu Apr 10 06:22:20 CEST 2014


On Wed, Apr 9, 2014 at 2:22 AM, Thierry Reding <thierry.reding at gmail.com> wrote:
> On Tue, Apr 08, 2014 at 12:15:33PM -0700, Dylan Reid wrote:
>> This adds a driver for the HDA block in Tegra SoCs.  The HDA bus is
>> used to communicate with the HDMI codec on Tegra124.
>>
>> Most of the code is re-used from the Intel/PCI HDA driver.  It brings
>> over only two of thw module params, power_save and probe_mask.
>
> s/thw/the/?

Thanks a lot for the thorough and detailed review, Theirry.  It is
very much appreciated, this looks much better after addressing your
comments, v2 on the way.

>
>> diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra124-hda.txt b/Documentation/devicetree/bindings/sound/nvidia,tegra124-hda.txt
>> new file mode 100644
>> index 0000000..c9256d3
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra124-hda.txt
>> @@ -0,0 +1,20 @@
>> +NVIDIA Tegra124 HDA controller
>> +
>> +Required properties:
>> +- compatible : "nvidia,tegra124-hda"
>
> From what I can tell this module hasn't changed significantly since
> Tegra30 and therefore should be backwards compatible.
>
> The convention so far has always been to name the document after the
> first SoC generation that has the IP block. Similarily the driver should
> match nvidia,tegra30-hda and use compatible strings for later SoC
> generations.
>
> Maybe an example will clarify what I mean:
>
>         tegra30.dtsi:
>
>                 hda at 70030000 {
>                         compatible = "nvidia,tegra30-hda";
>                         ...
>                 };
>
>         tegra124.dtsi:
>
>                 hda at 70030000 {
>                         compatible = "nvidia,tegra124-hda", "nvidia,tegra30-hda";
>                         ...
>                 };
>
> I suspect that it will be fine if the driver only matched the compatible
> string "nvidia,tegra30-hda" because the block hasn't changed since then.
>
>> +- clocks : Must contain an entry for each required entry in clock-names.
>> +- clock-names : Must include the following entries: hda, hdacodec_2x, hda2hdmi
>
> I think in addition to the clocks this will also need resets and
> reset-names properties.

Thanks, hadn't seen that one yet, I'll add it.

>
>> --- a/sound/pci/hda/Kconfig
>> +++ b/sound/pci/hda/Kconfig
>> @@ -20,6 +20,20 @@ config SND_HDA_INTEL
>>         To compile this driver as a module, choose M here: the module
>>         will be called snd-hda-intel.
>>
>> +config SND_HDA_TEGRA
>> +     tristate "Tegra HD Audio"
>
> Perhaps "NVIDIA Tegra HD Audio"?
>
>> +     select SND_HDA
>> +     help
>> +       Say Y here to support the HDA controller present in Nvidia
>
> Nit: While it's not used consistently everywhere, I'd prefer if we could
> move towards using only "NVIDIA" rather than the current mix (which does
> include "Nvidia" and "nVidia").
>
>> diff --git a/sound/pci/hda/hda_tegra.c b/sound/pci/hda/hda_tegra.c
> [...]
>> +#include <linux/clk.h>
>> +#include <linux/clocksource.h>
>> +#include <linux/delay.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/moduleparam.h>
>> +#include <linux/init.h>
>> +#include <linux/slab.h>
>> +#include <linux/mutex.h>
>> +#include <linux/reboot.h>
>> +#include <linux/io.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/of_device.h>
>> +#include <linux/time.h>
>> +#include <linux/completion.h>
>> +
>> +#include <sound/core.h>
>> +#include <sound/initval.h>
>> +#include <linux/firmware.h>
>
> The ordering looks weird here. I'd prefer these to be alphabetically
> sorted.
>
>> +#define DRV_NAME "tegra-hda"
>
> This is only used in one place (in the platform_driver initialization),
> so I don't think the #define is needed here.
>
>> +/* Defines for Nvidia Tegra HDA support */
>> +#define NVIDIA_TEGRA_HDA_BAR0_OFFSET           0x8000
>> +
>> +#define NVIDIA_TEGRA_HDA_CFG_CMD_OFFSET        0x1004
>> +#define NVIDIA_TEGRA_HDA_CFG_BAR0_OFFSET       0x1010
>
> I think the _OFFSET suffix here isn't required. It just makes the name
> needlessly long. And since all of these definitions aren't visible
> outside of this file, I think the NVIDIA_TEGRA_ prefix can be dropped as
> well.
>
>> +struct hda_tegra_data {
>
> Perhaps just "hda_tegra"?

Good call on shortening these, saved a few wrapped lines.

>
>> +     struct azx chip;
>> +     struct platform_device *pdev;
>
> Can't this be simply struct device?

Yes, the pdev was only needed at probe time.

>
>> +     struct clk **platform_clks;
>> +     int platform_clk_count;
>
> Why the "platform_" prefix? Also I'm not a huge fan of accumulating
> clocks into an array like this. I'm not sure it has much of an advantage
> code-wise, since there's quite a bit of setup code required to allocate
> the array and populate it with the proper clocks.

10 lines less code without the array =)

>
>> +     void __iomem *remap_config_addr;
>
> This is a really long name. Perhaps simply "base" or "regs" would work
> just as well?
>
>> +};
>> +
>> +static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1};
>> +module_param_array(probe_mask, int, NULL, 0444);
>> +MODULE_PARM_DESC(probe_mask, "Bitmask to probe codecs (default = -1).");
>
> Is this really necessary? Do we need this with Tegra? Can't we simply
> assume that there's always only one codec? Or always use a probe_mask of
> -1? Alternatively, wouldn't this be better off in DT?

Removed per following discussion, thanks.

>
>> +static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
>> +static int *power_save_addr = &power_save;
>
> Why keep this in a separate variable? It seems to me like you can simply
> use &power_save directly at the one location where this is used?
>
>> +module_param(power_save, bint, 0644);
>> +MODULE_PARM_DESC(power_save,
>> +              "Automatic power-saving timeout (in seconds, 0 = disable).");
>
> Thinking more about it, this seems like the thing you'd want for
> something more generic such as PCI HDA where there may actually be more
> variance. Is this still useful on Tegra?

There are userspace tools that take advantage of this.  For example
laptop mode tools changes this setting based on whether the system is
attached to A/C or not.

>
>> +/*
>> + * DMA page allocation ops.
>> + */
>> +static int dma_alloc_pages(struct azx *chip,
>> +                        int type,
>> +                        size_t size,
>> +                        struct snd_dma_buffer *buf)
>
> This could be fewer lines:
>
>         static int dma_alloc_pages(struct azx *chip, int type, size_t size,
>                                    struct snd_dma_buffer *buf)
>
>> +{
>> +     return snd_dma_alloc_pages(type,
>> +                                chip->card->dev,
>> +                                size, buf);
>
> This fits perfectly on a single line.
>
>> +/*
>> + * Register access ops. Tegra HDA register access is DWORD only.
>> + */
>> +#define MASK_LONG_ALIGN              0x3UL
>> +#define SHIFT_BYTE           3
>
> I think these are obvious enough not to require symbolic names.
>
>> +#define SHIFT_BITS(addr)     \
>> +     (((unsigned int)(addr) & MASK_LONG_ALIGN) << SHIFT_BYTE)
>> +#define ADDR_ALIGN_L(addr)   \
>> +     (void *)((unsigned int)(addr) & ~MASK_LONG_ALIGN)
>
> Can you use ALIGN() or PTR_ALIGN() here?

Both of those end up using __ALIGN_KERNEL and that rounds up.  0x01 ->
0x04, here we want 0x01 -> 0x04.  Would have been nice, I didn't see
another pre-defined macro that did this.

>
>> +#define MASK(bits)           (BIT(bits) - 1)
>> +#define MASK_REG(addr, bits) (MASK(bits) << SHIFT_BITS(addr))
>> +
>> +static void tegra_hda_writel(u32 value, u32 *addr)
>> +{
>> +     writel(value, addr);
>> +}
>> +
>> +static u32 tegra_hda_readl(u32 *addr)
>> +{
>> +     return readl(addr);
>> +}
>> +
>> +static void tegra_hda_writew(u16 value, u16 *addr)
>> +{
>> +     unsigned int shift_bits = SHIFT_BITS(addr);
>> +     writel((readl(ADDR_ALIGN_L(addr)) & ~MASK_REG(addr, 16)) |
>> +            ((value) << shift_bits), ADDR_ALIGN_L(addr));
>> +}
>> +
>> +static u16 tegra_hda_readw(u16 *addr)
>> +{
>> +     return (readl(ADDR_ALIGN_L(addr)) >> SHIFT_BITS(addr)) & MASK(16);
>> +}
>> +
>> +static void tegra_hda_writeb(u8 value, u8 *addr)
>> +{
>> +     writel((readl(ADDR_ALIGN_L(addr)) & ~MASK_REG(addr, 8)) |
>> +            ((value) << SHIFT_BITS(addr)), ADDR_ALIGN_L(addr));
>> +}
>> +
>> +static u8 tegra_hda_readb(u8 *addr)
>> +{
>> +     return (readl(ADDR_ALIGN_L(addr)) >> SHIFT_BITS(addr)) & MASK(8);
>> +}
>
> It took me a really long time to review this and resolving all these
> references. Can we not make this simpler somehow? Perhaps unwinding this
> a bit may help, like this:
>
>         static void tegra_hda_writew(u16 value, u16 *addr)
>         {
>                 unsigned long shift = (addr & 0x3) << 3;
>                 unsigned long address = addr & ~0x3;
>                 u32 v;
>
>                 v = readl(address);
>                 v &= ~(0xffff << shift);
>                 v |= value << shift;
>                 writel(v, address);
>         }

Good suggestion, went with almost exactly that.  Thanks!

>
>> +static const struct hda_controller_ops tegra_hda_reg_ops = {
>
> Since these aren't only register operations, perhaps "tegra_hda_ops"?

Agreed.

>
>> +static int tegra_hda_acquire_irq(struct azx *chip, int do_disconnect)
>
> Shouldn't do_disconnect be bool?

Argument removed as it was left over from the PCI driver and not needed here.

>
>> +{
>> +     struct hda_tegra_data *tdata =
>
> Nit: tdata is somewhat generic, perhaps simply call this "hda"?
>
>> +             container_of(chip, struct hda_tegra_data, chip);
>
> This pattern is repeated a few times, so perhaps add a helper such as:
>
>         static inline struct hda_tegra *to_hda_tegra(struct azx *chip)
>         {
>                 return container_of(chip, struct hda_tegra, chip);
>         }
>
>> +     int irq_id = platform_get_irq(tdata->pdev, 0);
>
> I think this should be part of tegra_hda_probe().

I moved this with the rest of this function to be inline with the
first_init call made from probe().

>
>> +     if (devm_request_irq(chip->card->dev, irq_id, azx_interrupt,
>> +                          IRQF_SHARED, KBUILD_MODNAME, chip)) {
>
> Perhaps store the error so that you can return (and print) the exact
> error code?
>
>> +             dev_err(chip->card->dev,
>> +                     "unable to grab IRQ %d, disabling device\n",
>
> s/grab/request/?
>
>> +static void tegra_hda_reg_update_bits(void __iomem *base, unsigned int reg,
>> +                                   unsigned int mask, unsigned int val)
>> +{
>> +     unsigned int data;
>> +
>> +     data = readl(base + reg);
>> +     data &= ~mask;
>> +     data |= (val & mask);
>> +     writel(data, base + reg);
>> +}
>> +
>> +static void hda_tegra_init(struct hda_tegra_data *tdata)
>> +{
>> +     /*Enable the PCI access */
>> +     tegra_hda_reg_update_bits(tdata->remap_config_addr,
>> +                               NVIDIA_TEGRA_HDA_IPFS_CONFIG,
>> +                               NVIDIA_TEGRA_HDA_IPFS_EN_FPCI,
>> +                               NVIDIA_TEGRA_HDA_IPFS_EN_FPCI);
>
> I prefer explicit read-modify-write sequences...
>
>> +     /* Enable MEM/IO space and bus master */
>> +     tegra_hda_reg_update_bits(tdata->remap_config_addr,
>> +                               NVIDIA_TEGRA_HDA_CFG_CMD_OFFSET, 0x507,
>
> ... because it makes it easier to see that 0x507 is actually a mask and
> should get a symbolic name too, to make it obvious what fields are being
> cleared.

breaking these into explicit read/modify/write made this cleaner and
allowed several steps to be skipped.

>
>> +                               NVIDIA_TEGRA_HDA_ENABLE_MEM_SPACE |
>> +                               NVIDIA_TEGRA_HDA_ENABLE_IO_SPACE |
>> +                               NVIDIA_TEGRA_HDA_ENABLE_BUS_MASTER |
>> +                               NVIDIA_TEGRA_HDA_ENABLE_SERR);
>> +     tegra_hda_reg_update_bits(tdata->remap_config_addr,
>> +                               NVIDIA_TEGRA_HDA_CFG_BAR0_OFFSET, 0xFFFFFFFF,
>> +                               NVIDIA_TEGRA_HDA_BAR0_INIT_PROGRAM);
>
> Also these are just plain writes, so no need to actually clear the
> register first.
>
>> +
>> +     return;
>
> Unnecessary return statement.
>
>> +static void hda_tegra_enable_clocks(struct hda_tegra_data *data)
>> +{
>> +     int i;
>
> If you really insist on keeping the clock array, then this should be
> unsigned int.

Array gone.

>
>> +static int tegra_hda_runtime_resume(struct device *dev)
>> +{
>> +     struct snd_card *card = dev_get_drvdata(dev);
>> +     struct azx *chip = card->private_data;
>> +     struct hda_bus *bus;
>
> I don't think this particular temporary gains much.
>
>> +static const struct dev_pm_ops azx_pm = {
>
> Perhaps tegra_hda_pm since these are Tegra-specific?
>
>> +static int tegra_hda_free(struct azx *chip)
>> +{
>> +     int i;
>> +
>> +     if (chip->running)
>> +             pm_runtime_get_noresume(chip->card->dev);
>> +
>> +     tegra_hda_notifier_unregister(chip);
>> +
>> +     if (chip->initialized) {
>> +             for (i = 0; i < chip->num_streams; i++)
>> +                     azx_stream_stop(chip, &chip->azx_dev[i]);
>> +             azx_stop_chip(chip);
>> +     }
>> +
>> +     azx_free_stream_pages(chip);
>> +
>> +     return 0;
>> +}
>> +
>> +static int tegra_hda_dev_free(struct snd_device *device)
>> +{
>> +     return tegra_hda_free(device->device_data);
>> +}
>
> These can be merged, since tegra_hda_free() isn't used elsewhere.
>
>> +static int hda_tegra_init_chip(struct azx *chip)
>> +{
>> +     struct hda_tegra_data *tdata =
>> +             container_of(chip, struct hda_tegra_data, chip);
>> +     struct device *dev = &tdata->pdev->dev;
>> +     struct resource *res, *region;
>> +     int i;
>> +
>> +     tdata->platform_clk_count = ARRAY_SIZE(tegra_clk_names);
>> +     tdata->platform_clks = devm_kzalloc(dev,
>> +                                         tdata->platform_clk_count *
>> +                                             sizeof(*tdata->platform_clks),
>> +                                         GFP_KERNEL);
>
> That's devm_kcalloc().
>
>> +     res = platform_get_resource(tdata->pdev, IORESOURCE_MEM, 0);
>> +     if (!res)
>> +             return -EINVAL;
>> +
>> +     region = devm_request_mem_region(dev, res->start,
>> +                                      resource_size(res),
>> +                                      tdata->pdev->name);
>> +     if (!region)
>> +             return -ENOMEM;
>> +
>> +     chip->addr = res->start;
>> +     chip->remap_addr = devm_ioremap(dev, res->start, resource_size(res));
>> +     if (!chip->remap_addr)
>> +             return -ENXIO;
>
> All of the above can be rewritten as:
>
>         res = platform_get_resource(tdata->pdev, IORESOURCE_MEM, 0);
>         chip->remap_addr = devm_ioremap_resource(dev, res);
>         if (IS_ERR(chip->remap_addr))
>                 return PTR_ERR(chip->remap_addr);
>
>         chip->addr = res->start;
>
>> +     tdata->remap_config_addr = chip->remap_addr;
>> +     chip->remap_addr += NVIDIA_TEGRA_HDA_BAR0_OFFSET;
>> +     chip->addr += NVIDIA_TEGRA_HDA_BAR0_OFFSET;
>
> Perhaps a more intuitive way to write this would be (including my rename
> suggestions):
>
>         hda->regs = devm_ioremap_resource(...);
>         ...
>
>         chip->remap_addr = hda->regs + NVIDIA_TEGRA_HDA_BAR0_OFFSET;
>         chip->addr = res->start + NVIDIA_TEGRA_HDA_BAR0_OFFSET;
>
>> +
>> +     hda_tegra_enable_clocks(tdata);
>> +
>> +     hda_tegra_init(tdata);
>
> Shouldn't both of these return an error which can be propagated on
> failure?

hda_tegra_init can't create and error.  enable_clocks on the other
hand should handle clk_prepare_enable failing, I'll add that.

>
>> +static void power_down_all_codecs(struct azx *chip)
>> +{
>> +     /* The codecs were powered up in snd_hda_codec_new().
>> +      * Now all initialization done, so turn them down if possible
>> +      */
>> +     struct hda_codec *codec;
>
> Nit: Perhaps put the variable declaration before the comment. Or
> alternatively put the comment above the function.
>
> Also block comments are usually of this format:
>
>         /*
>          * bla...
>          * ... bla.
>          */
>
>> +static int tegra_hda_first_init(struct azx *chip)
>> +{
>> +     struct snd_card *card = chip->card;
>> +     int err;
>> +     unsigned short gcap;
>> +
>> +     err = hda_tegra_init_chip(chip);
>> +     if (err)
>> +             return err;
>> +
>> +     if (tegra_hda_acquire_irq(chip, 0) < 0)
>> +             return -EBUSY;
>
> Why not propagate whatever tegra_hda_acquire_irq() returned?

Done, code inlined here and returns any error code returned from request_irq.

>
>> +     synchronize_irq(chip->irq);
>> +
>> +     gcap = azx_readw(chip, GCAP);
>> +     dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap);
>> +
>> +     /* read number of streams from GCAP register instead of using
>> +      * hardcoded value
>> +      */
>> +     chip->capture_streams = (gcap >> 8) & 0x0f;
>> +     chip->playback_streams = (gcap >> 12) & 0x0f;
>> +     if (!chip->playback_streams && !chip->capture_streams) {
>> +             /* gcap didn't give any info, switching to old method */
>> +             chip->playback_streams = ICH6_NUM_PLAYBACK;
>> +             chip->capture_streams = ICH6_NUM_CAPTURE;
>> +     }
>> +     chip->capture_index_offset = 0;
>> +     chip->playback_index_offset = chip->capture_streams;
>> +     chip->num_streams = chip->playback_streams + chip->capture_streams;
>> +     chip->azx_dev = devm_kzalloc(card->dev,
>> +                                  chip->num_streams * sizeof(*chip->azx_dev),
>> +                                  GFP_KERNEL);
>
> devm_kcalloc()
>
>> +     strcpy(card->driver, "HDA-Tegra");
>
> Perhaps this should match the driver name ("tegra-hda")?
>
>> +/*
>> + * constructor
>> + */
>> +static int tegra_hda_create(struct snd_card *card,
>> +                         int dev,
>> +                         struct platform_device *pdev,
>> +                         unsigned int driver_caps,
>> +                         const struct hda_controller_ops *hda_ops,
>> +                         struct hda_tegra_data *tdata)
>
> This could be fewer lines.
>
>> +     err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
>> +     if (err < 0) {
>> +             dev_err(card->dev, "Error creating device [card]!\n");
>
> Perhaps drop "[card]"? Also I don't think the exclamation mark is
> required. It's already an error message.
>
>> +static unsigned int tegra_driver_flags = AZX_DCAPS_RIRB_DELAY |
>> +                                      AZX_DCAPS_PM_RUNTIME;
>
> Perhaps a slightly more future-proof way of doing this would be by
> adding a structure, like this:
>
>         struct tegra_hda_soc_info {
>                 unsigned long flags;
>         };
>
> And then instantiate that per SoC:
>
>         static const struct tegra_hda_soc_info tegra124_hda_soc_info = {
>                 .flags = ...;
>         };
>
> Although looking at the above AZX_* flags, they don't seem to be SoC-
> but but rather driver-specific, so they don't really belong in the OF
> device match table.

I think you're right, I'll move these out of the table and make the
flags local to the probe function.

>
>> +static const struct of_device_id tegra_platform_hda_match[] = {
>
> Why not simply "tegra_hda_match"?

Changed.

>
>> +     { .compatible = "nvidia,tegra124-hda", .data = &tegra_driver_flags },
>> +     {},
>> +};
>> +
>> +static int hda_tegra_probe(struct platform_device *pdev)
>
> There seems to be a mix between hda_tegra_ and tegra_hda_ prefixes
> throughout the driver. I like it when these are consistent because it is
> easier to refer to the functions (you don't always have to remember
> which function has which prefix).

Good point, the split was almost 50/50, change to always put hda before tegra.

>
>> +{
>> +     static int dev;
>> +     struct snd_card *card;
>> +     struct azx *chip;
>> +     struct hda_tegra_data *tdata;
>> +     const struct of_device_id *of_id;
>> +     const unsigned int *driver_data;
>> +     int err;
>> +
>> +     if (dev >= SNDRV_CARDS)
>> +             return -ENODEV;
>
> I'd really like to avoid this. But I also realize that this is something
> inherited from the sound subsystem, so I'm unsure about how easy it is
> to get rid of it.

In this case we're only going to have one card, I think we can safely
drop this, and set the dev_index in the chip structure to zero.

>
>> +     of_id = of_match_device(tegra_platform_hda_match, &pdev->dev);
>> +     if (!of_id)
>> +             return -EINVAL;
>
> Perhaps -ENODEV?

That's better.

>
>> +     dev_set_drvdata(&pdev->dev, card);
>
> platform_set_drvdata()?
>
>> +static struct platform_driver tegra_platform_hda = {
>> +     .driver = {
>> +             .name = DRV_NAME,
>> +             .owner = THIS_MODULE,
>
> This is no longer required.

Removed.

>
>> +             .pm = &azx_pm,
>> +             .of_match_table = tegra_platform_hda_match,
>> +     },
>> +     .probe = hda_tegra_probe,
>> +     .remove = hda_tegra_remove,
>> +};
>> +module_platform_driver(tegra_platform_hda);
>> +
>> +MODULE_DESCRIPTION("Tegra HDA bus driver");
>> +MODULE_LICENSE("GPL v2");
>
> The file header says "GPL v2 or later", so either this or the header
> needs to be updated.

Header updated.

>
>> +MODULE_DEVICE_TABLE(of, tegra_platform_hda_match);
>
> I think it's more idiomatic to put this directly below the OF match
> table.

Moved.

>
> Thierry


More information about the Alsa-devel mailing list