Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* RPI3 doesn't boot in ARM64 mode on linux-next
From: Ard Biesheuvel @ 2016-11-25  8:12 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1480041098.1504.1.camel@crowfest.net>

On 25 November 2016 at 02:31, Michael Zoran <mzoran@crowfest.net> wrote:
> Last night I did a build of linux-next and I'm finding that I can no
> longer get my RPI 3 to boot in arm64 mode anymore.  It seems to be
> hanging very early in the boot process.
>
> I know linux-next is going through a very large number of changes now,
> so does anybody know if arm64(not just the RPI 3) is just broken in
> general at this point? 4.9rc6 is working fine.
>
> I don't have a JTAG debugger, so it's a bit hard for me to debug.  I've
> actually been looking for a decent cheap JTAG debugger for awhile now,
> so if anybody knows of one in the < $300.00 range it might be a good
> self Christmas present.
>

Are you using earlycon?

^ permalink raw reply

* [PATCH resend v3] ASoC: sun4i-codec: Add "Right Mixer" to "Line Out Mono Diff." route
From: Maxime Ripard @ 2016-11-25  8:12 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161124114649.5188-1-wens@csie.org>

On Thu, Nov 24, 2016 at 07:46:49PM +0800, Chen-Yu Tsai wrote:
> The mono differential output for "Line Out" downmixes the stereo audio
> from the mixer, instead of just taking the left channel.
> 
> Add a route from the "Right Mixer" to "Line Out Source Playback Route"
> through the "Mono Differential" path, so DAPM doesn't shut down
> everything if the left channel is muted.
> 
> Fixes: 0f909f98d7cb ("ASoC: sun4i-codec: Add support for A31 Line Out
> 		      playback")
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>

Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Thanks,
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20161125/9e1a3418/attachment.sig>

^ permalink raw reply

* [PATCH v3 2/6] iio: adc: Add support for STM32 ADC core
From: Fabrice Gasnier @ 2016-11-25  8:09 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <cd958ae5-90e4-b6c5-8a23-84c49011e8e3@kernel.org>

On 11/24/2016 09:40 PM, Jonathan Cameron wrote:
> On 21/11/16 08:54, Fabrice Gasnier wrote:
>> On 11/19/2016 01:17 PM, Jonathan Cameron wrote:
>>> On 15/11/16 15:30, Fabrice Gasnier wrote:
>>>> Add core driver for STMicroelectronics STM32 ADC (Analog to Digital
>>>> Converter). STM32 ADC can be composed of up to 3 ADCs with shared
>>>> resources like clock prescaler, common interrupt line and analog
>>>> reference voltage.
>>>> This core driver basically manages shared resources.
>>>>
>>>> Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
>>> There is nothing in here that demands selecting a fixed regulator.
>>> I've also switched the select regulator over to depends on inline with
>>> other drivers in IIO that have a hard dependency on regulators.
>>> Other than that which showed up during build tests, looks good to me.
>>> Shout if I've broken anything with this change.
>> Hi Jonathan, All,
>>
>> First many thanks.
>> This is not a big deal. Only thing is: I think patch 4 of this series (on stm32_defconfig) need to be updated
>> to accommodate this change. E.g. :
>> +CONFIG_REGULATOR=y
>> +CONFIG_REGULATOR_FIXED_VOLTAGE=y
>>
>> Shall I send a new version of this series (all patches), including your changes, with updated defconfig as well ?
>> Or only updated patch on defconfig is enough ?
> Just update those that haven't already been applied.
Hi,

I'll update these only.
Thanks,
Fabrice

>
> Thanks,
>
> Jonathan
>> Please advise,
>> Fabrice
>>> Applied to the togreg branch of iio.git and pushed out as testing for
>>> the autobuilders to play with it.
>>>
>>> Thanks,
>>>
>>> Jonathan
>>>> ---
>>>>    drivers/iio/adc/Kconfig          |  13 ++
>>>>    drivers/iio/adc/Makefile         |   1 +
>>>>    drivers/iio/adc/stm32-adc-core.c | 303 +++++++++++++++++++++++++++++++++++++++
>>>>    drivers/iio/adc/stm32-adc-core.h |  52 +++++++
>>>>    4 files changed, 369 insertions(+)
>>>>    create mode 100644 drivers/iio/adc/stm32-adc-core.c
>>>>    create mode 100644 drivers/iio/adc/stm32-adc-core.h
>>>>
>>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>>> index 7edcf32..ff30239 100644
>>>> --- a/drivers/iio/adc/Kconfig
>>>> +++ b/drivers/iio/adc/Kconfig
>>>> @@ -419,6 +419,19 @@ config ROCKCHIP_SARADC
>>>>          To compile this driver as a module, choose M here: the
>>>>          module will be called rockchip_saradc.
>>>>    +config STM32_ADC_CORE
>>>> +    tristate "STMicroelectronics STM32 adc core"
>>>> +    depends on ARCH_STM32 || COMPILE_TEST
>>>> +    depends on OF
>>>> +    select REGULATOR
>>>> +    select REGULATOR_FIXED_VOLTAGE
>>>> +    help
>>>> +      Select this option to enable the core driver for STMicroelectronics
>>>> +      STM32 analog-to-digital converter (ADC).
>>>> +
>>>> +      This driver can also be built as a module.  If so, the module
>>>> +      will be called stm32-adc-core.
>>>> +
>>>>    config STX104
>>>>        tristate "Apex Embedded Systems STX104 driver"
>>>>        depends on X86 && ISA_BUS_API
>>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>>> index 7a40c04..a1e8f44 100644
>>>> --- a/drivers/iio/adc/Makefile
>>>> +++ b/drivers/iio/adc/Makefile
>>>> @@ -41,6 +41,7 @@ obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>>>>    obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>>>>    obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>>>>    obj-$(CONFIG_STX104) += stx104.o
>>>> +obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o
>>>>    obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>>>>    obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>>>>    obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
>>>> diff --git a/drivers/iio/adc/stm32-adc-core.c b/drivers/iio/adc/stm32-adc-core.c
>>>> new file mode 100644
>>>> index 0000000..4214b0c
>>>> --- /dev/null
>>>> +++ b/drivers/iio/adc/stm32-adc-core.c
>>>> @@ -0,0 +1,303 @@
>>>> +/*
>>>> + * This file is part of STM32 ADC driver
>>>> + *
>>>> + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
>>>> + * Author: Fabrice Gasnier <fabrice.gasnier@st.com>.
>>>> + *
>>>> + * Inspired from: fsl-imx25-tsadc
>>>> + *
>>>> + * License type: GPLv2
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or modify it
>>>> + * under the terms of the GNU General Public License version 2 as published by
>>>> + * the Free Software Foundation.
>>>> + *
>>>> + * This program is distributed in the hope that it will be useful, but
>>>> + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
>>>> + * or FITNESS FOR A PARTICULAR PURPOSE.
>>>> + * See the GNU General Public License for more details.
>>>> + *
>>>> + * You should have received a copy of the GNU General Public License along with
>>>> + * this program. If not, see <http://www.gnu.org/licenses/>.
>>>> + */
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/irqchip/chained_irq.h>
>>>> +#include <linux/irqdesc.h>
>>>> +#include <linux/irqdomain.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <linux/regulator/consumer.h>
>>>> +#include <linux/slab.h>
>>>> +
>>>> +#include "stm32-adc-core.h"
>>>> +
>>>> +/* STM32F4 - common registers for all ADC instances: 1, 2 & 3 */
>>>> +#define STM32F4_ADC_CSR            (STM32_ADCX_COMN_OFFSET + 0x00)
>>>> +#define STM32F4_ADC_CCR            (STM32_ADCX_COMN_OFFSET + 0x04)
>>>> +
>>>> +/* STM32F4_ADC_CSR - bit fields */
>>>> +#define STM32F4_EOC3            BIT(17)
>>>> +#define STM32F4_EOC2            BIT(9)
>>>> +#define STM32F4_EOC1            BIT(1)
>>>> +
>>>> +/* STM32F4_ADC_CCR - bit fields */
>>>> +#define STM32F4_ADC_ADCPRE_SHIFT    16
>>>> +#define STM32F4_ADC_ADCPRE_MASK        GENMASK(17, 16)
>>>> +
>>>> +/* STM32 F4 maximum analog clock rate (from datasheet) */
>>>> +#define STM32F4_ADC_MAX_CLK_RATE    36000000
>>>> +
>>>> +/**
>>>> + * struct stm32_adc_priv - stm32 ADC core private data
>>>> + * @irq:        irq for ADC block
>>>> + * @domain:        irq domain reference
>>>> + * @aclk:        clock reference for the analog circuitry
>>>> + * @vref:        regulator reference
>>>> + * @common:        common data for all ADC instances
>>>> + */
>>>> +struct stm32_adc_priv {
>>>> +    int                irq;
>>>> +    struct irq_domain        *domain;
>>>> +    struct clk            *aclk;
>>>> +    struct regulator        *vref;
>>>> +    struct stm32_adc_common        common;
>>>> +};
>>>> +
>>>> +static struct stm32_adc_priv *to_stm32_adc_priv(struct stm32_adc_common *com)
>>>> +{
>>>> +    return container_of(com, struct stm32_adc_priv, common);
>>>> +}
>>>> +
>>>> +/* STM32F4 ADC internal common clock prescaler division ratios */
>>>> +static int stm32f4_pclk_div[] = {2, 4, 6, 8};
>>>> +
>>>> +/**
>>>> + * stm32f4_adc_clk_sel() - Select stm32f4 ADC common clock prescaler
>>>> + * @priv: stm32 ADC core private data
>>>> + * Select clock prescaler used for analog conversions, before using ADC.
>>>> + */
>>>> +static int stm32f4_adc_clk_sel(struct platform_device *pdev,
>>>> +                   struct stm32_adc_priv *priv)
>>>> +{
>>>> +    unsigned long rate;
>>>> +    u32 val;
>>>> +    int i;
>>>> +
>>>> +    rate = clk_get_rate(priv->aclk);
>>>> +    for (i = 0; i < ARRAY_SIZE(stm32f4_pclk_div); i++) {
>>>> +        if ((rate / stm32f4_pclk_div[i]) <= STM32F4_ADC_MAX_CLK_RATE)
>>>> +            break;
>>>> +    }
>>>> +    if (i >= ARRAY_SIZE(stm32f4_pclk_div))
>>>> +        return -EINVAL;
>>>> +
>>>> +    val = readl_relaxed(priv->common.base + STM32F4_ADC_CCR);
>>>> +    val &= ~STM32F4_ADC_ADCPRE_MASK;
>>>> +    val |= i << STM32F4_ADC_ADCPRE_SHIFT;
>>>> +    writel_relaxed(val, priv->common.base + STM32F4_ADC_CCR);
>>>> +
>>>> +    dev_dbg(&pdev->dev, "Using analog clock source at %ld kHz\n",
>>>> +        rate / (stm32f4_pclk_div[i] * 1000));
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +/* ADC common interrupt for all instances */
>>>> +static void stm32_adc_irq_handler(struct irq_desc *desc)
>>>> +{
>>>> +    struct stm32_adc_priv *priv = irq_desc_get_handler_data(desc);
>>>> +    struct irq_chip *chip = irq_desc_get_chip(desc);
>>>> +    u32 status;
>>>> +
>>>> +    chained_irq_enter(chip, desc);
>>>> +    status = readl_relaxed(priv->common.base + STM32F4_ADC_CSR);
>>>> +
>>>> +    if (status & STM32F4_EOC1)
>>>> +        generic_handle_irq(irq_find_mapping(priv->domain, 0));
>>>> +
>>>> +    if (status & STM32F4_EOC2)
>>>> +        generic_handle_irq(irq_find_mapping(priv->domain, 1));
>>>> +
>>>> +    if (status & STM32F4_EOC3)
>>>> +        generic_handle_irq(irq_find_mapping(priv->domain, 2));
>>>> +
>>>> +    chained_irq_exit(chip, desc);
>>>> +};
>>>> +
>>>> +static int stm32_adc_domain_map(struct irq_domain *d, unsigned int irq,
>>>> +                irq_hw_number_t hwirq)
>>>> +{
>>>> +    irq_set_chip_data(irq, d->host_data);
>>>> +    irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_level_irq);
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static void stm32_adc_domain_unmap(struct irq_domain *d, unsigned int irq)
>>>> +{
>>>> +    irq_set_chip_and_handler(irq, NULL, NULL);
>>>> +    irq_set_chip_data(irq, NULL);
>>>> +}
>>>> +
>>>> +static const struct irq_domain_ops stm32_adc_domain_ops = {
>>>> +    .map = stm32_adc_domain_map,
>>>> +    .unmap  = stm32_adc_domain_unmap,
>>>> +    .xlate = irq_domain_xlate_onecell,
>>>> +};
>>>> +
>>>> +static int stm32_adc_irq_probe(struct platform_device *pdev,
>>>> +                   struct stm32_adc_priv *priv)
>>>> +{
>>>> +    struct device_node *np = pdev->dev.of_node;
>>>> +
>>>> +    priv->irq = platform_get_irq(pdev, 0);
>>>> +    if (priv->irq < 0) {
>>>> +        dev_err(&pdev->dev, "failed to get irq\n");
>>>> +        return priv->irq;
>>>> +    }
>>>> +
>>>> +    priv->domain = irq_domain_add_simple(np, STM32_ADC_MAX_ADCS, 0,
>>>> +                         &stm32_adc_domain_ops,
>>>> +                         priv);
>>>> +    if (!priv->domain) {
>>>> +        dev_err(&pdev->dev, "Failed to add irq domain\n");
>>>> +        return -ENOMEM;
>>>> +    }
>>>> +
>>>> +    irq_set_chained_handler(priv->irq, stm32_adc_irq_handler);
>>>> +    irq_set_handler_data(priv->irq, priv);
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static void stm32_adc_irq_remove(struct platform_device *pdev,
>>>> +                 struct stm32_adc_priv *priv)
>>>> +{
>>>> +    int hwirq;
>>>> +
>>>> +    for (hwirq = 0; hwirq < STM32_ADC_MAX_ADCS; hwirq++)
>>>> +        irq_dispose_mapping(irq_find_mapping(priv->domain, hwirq));
>>>> +    irq_domain_remove(priv->domain);
>>>> +    irq_set_chained_handler(priv->irq, NULL);
>>>> +}
>>>> +
>>>> +static int stm32_adc_probe(struct platform_device *pdev)
>>>> +{
>>>> +    struct stm32_adc_priv *priv;
>>>> +    struct device_node *np = pdev->dev.of_node;
>>>> +    struct resource *res;
>>>> +    int ret;
>>>> +
>>>> +    if (!pdev->dev.of_node)
>>>> +        return -ENODEV;
>>>> +
>>>> +    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
>>>> +    if (!priv)
>>>> +        return -ENOMEM;
>>>> +
>>>> +    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>>> +    priv->common.base = devm_ioremap_resource(&pdev->dev, res);
>>>> +    if (IS_ERR(priv->common.base))
>>>> +        return PTR_ERR(priv->common.base);
>>>> +
>>>> +    priv->vref = devm_regulator_get(&pdev->dev, "vref");
>>>> +    if (IS_ERR(priv->vref)) {
>>>> +        ret = PTR_ERR(priv->vref);
>>>> +        dev_err(&pdev->dev, "vref get failed, %d\n", ret);
>>>> +        return ret;
>>>> +    }
>>>> +
>>>> +    ret = regulator_enable(priv->vref);
>>>> +    if (ret < 0) {
>>>> +        dev_err(&pdev->dev, "vref enable failed\n");
>>>> +        return ret;
>>>> +    }
>>>> +
>>>> +    ret = regulator_get_voltage(priv->vref);
>>>> +    if (ret < 0) {
>>>> +        dev_err(&pdev->dev, "vref get voltage failed, %d\n", ret);
>>>> +        goto err_regulator_disable;
>>>> +    }
>>>> +    priv->common.vref_mv = ret / 1000;
>>>> +    dev_dbg(&pdev->dev, "vref+=%dmV\n", priv->common.vref_mv);
>>>> +
>>>> +    priv->aclk = devm_clk_get(&pdev->dev, "adc");
>>>> +    if (IS_ERR(priv->aclk)) {
>>>> +        ret = PTR_ERR(priv->aclk);
>>>> +        dev_err(&pdev->dev, "Can't get 'adc' clock\n");
>>>> +        goto err_regulator_disable;
>>>> +    }
>>>> +
>>>> +    ret = clk_prepare_enable(priv->aclk);
>>>> +    if (ret < 0) {
>>>> +        dev_err(&pdev->dev, "adc clk enable failed\n");
>>>> +        goto err_regulator_disable;
>>>> +    }
>>>> +
>>>> +    ret = stm32f4_adc_clk_sel(pdev, priv);
>>>> +    if (ret < 0) {
>>>> +        dev_err(&pdev->dev, "adc clk selection failed\n");
>>>> +        goto err_clk_disable;
>>>> +    }
>>>> +
>>>> +    ret = stm32_adc_irq_probe(pdev, priv);
>>>> +    if (ret < 0)
>>>> +        goto err_clk_disable;
>>>> +
>>>> +    platform_set_drvdata(pdev, &priv->common);
>>>> +
>>>> +    ret = of_platform_populate(np, NULL, NULL, &pdev->dev);
>>>> +    if (ret < 0) {
>>>> +        dev_err(&pdev->dev, "failed to populate DT children\n");
>>>> +        goto err_irq_remove;
>>>> +    }
>>>> +
>>>> +    return 0;
>>>> +
>>>> +err_irq_remove:
>>>> +    stm32_adc_irq_remove(pdev, priv);
>>>> +
>>>> +err_clk_disable:
>>>> +    clk_disable_unprepare(priv->aclk);
>>>> +
>>>> +err_regulator_disable:
>>>> +    regulator_disable(priv->vref);
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +static int stm32_adc_remove(struct platform_device *pdev)
>>>> +{
>>>> +    struct stm32_adc_common *common = platform_get_drvdata(pdev);
>>>> +    struct stm32_adc_priv *priv = to_stm32_adc_priv(common);
>>>> +
>>>> +    of_platform_depopulate(&pdev->dev);
>>>> +    stm32_adc_irq_remove(pdev, priv);
>>>> +    clk_disable_unprepare(priv->aclk);
>>>> +    regulator_disable(priv->vref);
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static const struct of_device_id stm32_adc_of_match[] = {
>>>> +    { .compatible = "st,stm32f4-adc-core" },
>>>> +    {},
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, stm32_adc_of_match);
>>>> +
>>>> +static struct platform_driver stm32_adc_driver = {
>>>> +    .probe = stm32_adc_probe,
>>>> +    .remove = stm32_adc_remove,
>>>> +    .driver = {
>>>> +        .name = "stm32-adc-core",
>>>> +        .of_match_table = stm32_adc_of_match,
>>>> +    },
>>>> +};
>>>> +module_platform_driver(stm32_adc_driver);
>>>> +
>>>> +MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>");
>>>> +MODULE_DESCRIPTION("STMicroelectronics STM32 ADC core driver");
>>>> +MODULE_LICENSE("GPL v2");
>>>> +MODULE_ALIAS("platform:stm32-adc-core");
>>>> diff --git a/drivers/iio/adc/stm32-adc-core.h b/drivers/iio/adc/stm32-adc-core.h
>>>> new file mode 100644
>>>> index 0000000..081fa5f
>>>> --- /dev/null
>>>> +++ b/drivers/iio/adc/stm32-adc-core.h
>>>> @@ -0,0 +1,52 @@
>>>> +/*
>>>> + * This file is part of STM32 ADC driver
>>>> + *
>>>> + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
>>>> + * Author: Fabrice Gasnier <fabrice.gasnier@st.com>.
>>>> + *
>>>> + * License type: GPLv2
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or modify it
>>>> + * under the terms of the GNU General Public License version 2 as published by
>>>> + * the Free Software Foundation.
>>>> + *
>>>> + * This program is distributed in the hope that it will be useful, but
>>>> + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
>>>> + * or FITNESS FOR A PARTICULAR PURPOSE.
>>>> + * See the GNU General Public License for more details.
>>>> + *
>>>> + * You should have received a copy of the GNU General Public License along with
>>>> + * this program. If not, see <http://www.gnu.org/licenses/>.
>>>> + */
>>>> +
>>>> +#ifndef __STM32_ADC_H
>>>> +#define __STM32_ADC_H
>>>> +
>>>> +/*
>>>> + * STM32 - ADC global register map
>>>> + * ________________________________________________________
>>>> + * | Offset |                 Register                    |
>>>> + * --------------------------------------------------------
>>>> + * | 0x000  |                Master ADC1                  |
>>>> + * --------------------------------------------------------
>>>> + * | 0x100  |                Slave ADC2                   |
>>>> + * --------------------------------------------------------
>>>> + * | 0x200  |                Slave ADC3                   |
>>>> + * --------------------------------------------------------
>>>> + * | 0x300  |         Master & Slave common regs          |
>>>> + * --------------------------------------------------------
>>>> + */
>>>> +#define STM32_ADC_MAX_ADCS        3
>>>> +#define STM32_ADCX_COMN_OFFSET        0x300
>>>> +
>>>> +/**
>>>> + * struct stm32_adc_common - stm32 ADC driver common data (for all instances)
>>>> + * @base:        control registers base cpu addr
>>>> + * @vref_mv:        vref voltage (mv)
>>>> + */
>>>> +struct stm32_adc_common {
>>>> +    void __iomem            *base;
>>>> +    int                vref_mv;
>>>> +};
>>>> +
>>>> +#endif
>>>>
>> -- 
>> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
>> the body of a message to majordomo at vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* [PATCH] reset: hisilicon: add a polarity cell for reset line specifier
From: Jiancheng Xue @ 2016-11-25  8:05 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <2281ff0d-2883-a78c-6106-f913da24581f@hisilicon.com>



On 2016/11/25 11:45, Jiancheng Xue wrote:
> 
> On 2016/11/21 10:58, Jiancheng Xue wrote:
>> Hi Philipp,
>>
>>> On 2016/11/15 18:43, Philipp Zabel wrote:
>>>> Hi Jiancheng,
>>>>
>>>> Am Dienstag, den 15.11.2016, 15:09 +0800 schrieb Jiancheng Xue:
>>>>> Add a polarity cell for reset line specifier. If the reset line
>>>>> is asserted when the register bit is 1, the polarity is
>>>>> normal. Otherwise, it is inverted.
>>>>>
>>>>> Signed-off-by: Jiancheng Xue <xuejiancheng@hisilicon.com>
>>>>> ---
>>> Thank you very much for replying so soon.
>>>
>>> Please allow me to decribe the reason why this patch exists first.
>>> All bits in the reset controller were designed to be active-high.
>>> But in a recent chip only one bit was implemented to be active-low :(
>>>
>>>>>  .../devicetree/bindings/clock/hisi-crg.txt         | 11 ++++---
>>>>>  arch/arm/boot/dts/hi3519.dtsi                      |  2 +-
>>>>>  drivers/clk/hisilicon/reset.c                      | 36 ++++++++++++++++------
>>>>>  3 files changed, 33 insertions(+), 16 deletions(-)
>>>>>
>>>>> diff --git a/Documentation/devicetree/bindings/clock/hisi-crg.txt b/Documentation/devicetree/bindings/clock/hisi-crg.txt
>>>>> index e3919b6..fcbb4f3 100644
>>>>> --- a/Documentation/devicetree/bindings/clock/hisi-crg.txt
>>>>> +++ b/Documentation/devicetree/bindings/clock/hisi-crg.txt
>>>>> @@ -25,19 +25,20 @@ to specify the clock which they consume.
>>>>>  
>>>>>  All these identifier could be found in <dt-bindings/clock/hi3519-clock.h>.
>>>>>  
>>>>> -- #reset-cells: should be 2.
>>>>> +- #reset-cells: should be 3.
>>>>>  
>>>>>  A reset signal can be controlled by writing a bit register in the CRG module.
>>>>> -The reset specifier consists of two cells. The first cell represents the
>>>>> +The reset specifier consists of three cells. The first cell represents the
>>>>>  register offset relative to the base address. The second cell represents the
>>>>> -bit index in the register.
>>>>> +bit index in the register. The third cell represents the polarity of the reset
>>>>> +line (0 for normal, 1 for inverted).
>>>>
>> #reset-cells: Should be 2 if compatilbe string is "hisilicon,hi3519-crg". Should be 3 otherwise.
>> 	      A reset signal can be controlled by writing a bit register in the CRG module.
>> 	      The reset specifier consists of two or three cells. The first cell represents the
>> 	      register offset relative to the base address. The second cell represents the
>> 	      bit index in the register.The third cell represents the polarity of the reset
>> 	      line (0 for active-high, 1 for active-low).
>>
>> If I change the binding like this, can it be accepted?
>>
> Hi Philipp,
> 
> Could you give me more suggestions about this?  If you really don't like changing the
> reset-cells like this, I can modify the patch according to your suggestions.
> Thank you.
> 

I'll drop this patch and use "ti,syscon-reset" instead to resolve the polarity issue. Thanks.

Regards,
Jiancheng

^ permalink raw reply

* [PATCH v2 0/3] arm64: dts: r8a7796: Add CAN/CAN FD support
From: Simon Horman @ 2016-11-25  7:48 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1480004021-4037-1-git-send-email-chris.paterson2@renesas.com>

On Thu, Nov 24, 2016 at 04:13:38PM +0000, Chris Paterson wrote:
> This series adds CAN and CAN FD support to the r8a7796.
> 
> Changes since v1:
> - Split device tree changes from bindings documentation.
> - Rebased on renesas-devel-20161123v2-v4.9-rc6.
> 
> 
> Chris Paterson (3):
>   arm64: dts: r8a7796: Add CAN external clock support
>   arm64: dts: r8a7796: Add CAN support
>   arm64: dts: r8a7796: Add CAN FD support

Thanks Chris,

I have queued these up for v4.11 in the devel branch of the renesas tree.

^ permalink raw reply

* [linux-sunxi] [PATCH v6 3/5] ARM: dts: sun8i-h3: add HDMI video nodes
From: Jean-François Moine @ 2016-11-25  6:57 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <6896101480051640@web28h.yandex.ru>

On Fri, 25 Nov 2016 13:27:20 +0800
Icenowy Zheng <icenowy@aosc.xyz> wrote:

> > + assigned-clocks = <&ccu CLK_PLL_DE>,
> 
> Cannot get the patch built on 4.9-rc, as CLK_PLL_DE is not an exported clock.

Hi,

Sorry, I forgot it. The symbol goes to
include/dt-bindings/clock/sun8i-h3-ccu.h
as:

#define CLK_PLL_DE		13

-- 
Ken ar c'henta?	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

^ permalink raw reply

* [PATCH] clk: sunxi-ng: fix PLL_CPUX adjusting on H3
From: Maxime Ripard @ 2016-11-25  6:35 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161125002852.18097-1-megous@megous.com>

On Fri, Nov 25, 2016 at 01:28:47AM +0100, megous at megous.com wrote:
> From: Ondrej Jirman <megous@megous.com>
> 
> When adjusting PLL_CPUX on H3, the PLL is temporarily driven
> too high, and the system becomes unstable (oopses or hangs).
> 
> Add a notifier to avoid this situation by temporarily switching
> to a known stable 24 MHz oscillator.
> 
> Signed-off-by: Ondrej Jirman <megous@megous.com>
> Tested-by: Lutz Sammer <johns98@gmx.net>

Applied, thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20161125/8ebd4b9a/attachment.sig>

^ permalink raw reply

* [PATCH 02/10] ASoC: sunxi: Add support for A23/A33/H3 codec's analog path controls
From: Icenowy Zheng @ 2016-11-25  5:51 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <CAGb2v66Zb9-UdimKDn5tq9UPRY7wBZNNCCn-52bf=evP914sPg@mail.gmail.com>



25.11.2016, 13:46, "Chen-Yu Tsai" <wens@csie.org>:
> On Fri, Nov 25, 2016 at 1:43 PM, Icenowy Zheng <icenowy@aosc.xyz> wrote:
>> ?12.11.2016, 14:57, "Chen-Yu Tsai" <wens@csie.org>:
>>> ?The internal codec on A23/A33/H3 is split into 2 parts. The
>>> ?analog path controls are routed through an embedded custom register
>>> ?bus accessed through the PRCM block.
>>>
>>> ?The SoCs share a common set of inputs, outputs, and audio paths.
>>> ?The following table lists the differences.
>>>
>>> ?????----------------------------------------
>>> ?????| Feature \ SoC | A23 | A33 | H3 |
>>> ?????----------------------------------------
>>> ?????| Headphone | v | v | |
>>> ?????----------------------------------------
>>> ?????| Line Out | | | v |
>>> ?????----------------------------------------
>>> ?????| Phone In/Out | v | v | |
>>> ?????----------------------------------------
>>>
>>> ?Add an ASoC component driver for it. This should be tied to the codec
>>> ?audio card as an auxiliary device. This patch adds the commont paths
>>> ?and controls, and variant specific headphone out and line out.
>>>
>>> ?Signed-off-by: Chen-Yu Tsai <wens@csie.org>
>>> ?---
>>> ??sound/soc/sunxi/Kconfig | 8 +
>>> ??sound/soc/sunxi/Makefile | 1 +
>>> ??sound/soc/sunxi/sun8i-codec-analog.c | 665 +++++++++++++++++++++++++++++++++++
>>> ??3 files changed, 674 insertions(+)
>>> ??create mode 100644 sound/soc/sunxi/sun8i-codec-analog.c
>>>
>>> ?diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
>>> ?index dd2368297fd3..6c344e16aca4 100644
>>> ?--- a/sound/soc/sunxi/Kconfig
>>> ?+++ b/sound/soc/sunxi/Kconfig
>>> ?@@ -9,6 +9,14 @@ config SND_SUN4I_CODEC
>>> ????????????Select Y or M to add support for the Codec embedded in the Allwinner
>>> ????????????A10 and affiliated SoCs.
>>>
>>> ?+config SND_SUN8I_CODEC_ANALOG
>>> ?+ tristate "Allwinner sun8i Codec Analog Controls Support"
>>> ?+ depends on MACH_SUN8I || COMPILE_TEST
>>
>> ?sun50i-a64 has a similar (or the same?) codec to A33.
>
> I think the register offsets/fields were moved around again.
> Why does Allwinner always do that... :/

Yes, moved around :-(

Or maybe I should say "there's more registers on A64".

>
> ChenYu
>
>>> ?+ select REGMAP
>>> ?+ help
>>> ?+ Say Y or M if you want to add support for the analog controls for
>>> ?+ the codec embedded in newer Allwinner SoCs.
>>> ?+
>>> ??config SND_SUN4I_I2S
>>> ??????????tristate "Allwinner A10 I2S Support"
>>> ??????????select SND_SOC_GENERIC_DMAENGINE_PCM
>>> ?diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile
>>> ?index 604c7b842837..241c0df9ca0c 100644
>>> ?--- a/sound/soc/sunxi/Makefile
>>> ?+++ b/sound/soc/sunxi/Makefile
>>> ?@@ -1,3 +1,4 @@
>>> ??obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o
>>> ??obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o
>>> ??obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o
>>> ?+obj-$(CONFIG_SND_SUN8I_CODEC_ANALOG) += sun8i-codec-analog.o
>>> ?diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c
>>> ?new file mode 100644
>>> ?index 000000000000..222bbd440b1e
>>> ?--- /dev/null
>>> ?+++ b/sound/soc/sunxi/sun8i-codec-analog.c
>>> ?@@ -0,0 +1,665 @@
>>> ?+/*
>>> ?+ * This driver supports the analog controls for the internal codec
>>> ?+ * found in Allwinner's A31s, A23, A33 and H3 SoCs.
>>> ?+ *
>>> ?+ * Copyright 2016 Chen-Yu Tsai <wens@csie.org>
>>> ?+ *
>>> ?+ * This program is free software; you can redistribute it and/or modify
>>> ?+ * it under the terms of the GNU General Public License as published by
>>> ?+ * the Free Software Foundation; either version 2 of the License, or
>>> ?+ * (at your option) any later version.
>>> ?+ *
>>> ?+ * This program is distributed in the hope that it will be useful,
>>> ?+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> ?+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>>> ?+ * GNU General Public License for more details.
>>> ?+ */
>>> ?+
>>> ?+#include <linux/io.h>
>>> ?+#include <linux/kernel.h>
>>> ?+#include <linux/module.h>
>>> ?+#include <linux/of.h>
>>> ?+#include <linux/of_device.h>
>>> ?+#include <linux/platform_device.h>
>>> ?+#include <linux/regmap.h>
>>> ?+
>>> ?+#include <sound/soc.h>
>>> ?+#include <sound/soc-dapm.h>
>>> ?+#include <sound/tlv.h>
>>> ?+
>>> ?+/* Codec analog control register offsets and bit fields */
>>> ?+#define SUN8I_ADDA_HP_VOLC 0x00
>>> ?+#define SUN8I_ADDA_HP_VOLC_PA_CLK_GATE 7
>>> ?+#define SUN8I_ADDA_HP_VOLC_HP_VOL 0
>>> ?+#define SUN8I_ADDA_LOMIXSC 0x01
>>> ?+#define SUN8I_ADDA_LOMIXSC_MIC1 6
>>> ?+#define SUN8I_ADDA_LOMIXSC_MIC2 5
>>> ?+#define SUN8I_ADDA_LOMIXSC_PHONE 4
>>> ?+#define SUN8I_ADDA_LOMIXSC_PHONEN 3
>>> ?+#define SUN8I_ADDA_LOMIXSC_LINEINL 2
>>> ?+#define SUN8I_ADDA_LOMIXSC_DACL 1
>>> ?+#define SUN8I_ADDA_LOMIXSC_DACR 0
>>> ?+#define SUN8I_ADDA_ROMIXSC 0x02
>>> ?+#define SUN8I_ADDA_ROMIXSC_MIC1 6
>>> ?+#define SUN8I_ADDA_ROMIXSC_MIC2 5
>>> ?+#define SUN8I_ADDA_ROMIXSC_PHONE 4
>>> ?+#define SUN8I_ADDA_ROMIXSC_PHONEP 3
>>> ?+#define SUN8I_ADDA_ROMIXSC_LINEINR 2
>>> ?+#define SUN8I_ADDA_ROMIXSC_DACR 1
>>> ?+#define SUN8I_ADDA_ROMIXSC_DACL 0
>>> ?+#define SUN8I_ADDA_DAC_PA_SRC 0x03
>>> ?+#define SUN8I_ADDA_DAC_PA_SRC_DACAREN 7
>>> ?+#define SUN8I_ADDA_DAC_PA_SRC_DACALEN 6
>>> ?+#define SUN8I_ADDA_DAC_PA_SRC_RMIXEN 5
>>> ?+#define SUN8I_ADDA_DAC_PA_SRC_LMIXEN 4
>>> ?+#define SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE 3
>>> ?+#define SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE 2
>>> ?+#define SUN8I_ADDA_DAC_PA_SRC_RHPIS 1
>>> ?+#define SUN8I_ADDA_DAC_PA_SRC_LHPIS 0
>>> ?+#define SUN8I_ADDA_PHONEIN_GCTRL 0x04
>>> ?+#define SUN8I_ADDA_PHONEIN_GCTRL_PHONEPG 4
>>> ?+#define SUN8I_ADDA_PHONEIN_GCTRL_PHONENG 0
>>> ?+#define SUN8I_ADDA_LINEIN_GCTRL 0x05
>>> ?+#define SUN8I_ADDA_LINEIN_GCTRL_LINEING 4
>>> ?+#define SUN8I_ADDA_LINEIN_GCTRL_PHONEG 0
>>> ?+#define SUN8I_ADDA_MICIN_GCTRL 0x06
>>> ?+#define SUN8I_ADDA_MICIN_GCTRL_MIC1G 4
>>> ?+#define SUN8I_ADDA_MICIN_GCTRL_MIC2G 0
>>> ?+#define SUN8I_ADDA_PAEN_HP_CTRL 0x07
>>> ?+#define SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN 7
>>> ?+#define SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN 7 /* H3 specific */
>>> ?+#define SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC 5
>>> ?+#define SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN 4
>>> ?+#define SUN8I_ADDA_PAEN_HP_CTRL_PA_ANTI_POP_CTRL 2
>>> ?+#define SUN8I_ADDA_PAEN_HP_CTRL_LTRNMUTE 1
>>> ?+#define SUN8I_ADDA_PAEN_HP_CTRL_RTLNMUTE 0
>>> ?+#define SUN8I_ADDA_PHONEOUT_CTRL 0x08
>>> ?+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTG 5
>>> ?+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTEN 4
>>> ?+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC1 3
>>> ?+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC2 2
>>> ?+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_RMIX 1
>>> ?+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_LMIX 0
>>> ?+#define SUN8I_ADDA_PHONE_GAIN_CTRL 0x09
>>> ?+#define SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL 3
>>> ?+#define SUN8I_ADDA_PHONE_GAIN_CTRL_PHONEPREG 0
>>> ?+#define SUN8I_ADDA_MIC2G_CTRL 0x0a
>>> ?+#define SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN 7
>>> ?+#define SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST 4
>>> ?+#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN 3
>>> ?+#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN 2
>>> ?+#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC 1
>>> ?+#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC 0
>>> ?+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL 0x0b
>>> ?+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN 7
>>> ?+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN 6
>>> ?+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIAS_MODE 5
>>> ?+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN 3
>>> ?+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST 0
>>> ?+#define SUN8I_ADDA_LADCMIXSC 0x0c
>>> ?+#define SUN8I_ADDA_LADCMIXSC_MIC1 6
>>> ?+#define SUN8I_ADDA_LADCMIXSC_MIC2 5
>>> ?+#define SUN8I_ADDA_LADCMIXSC_PHONE 4
>>> ?+#define SUN8I_ADDA_LADCMIXSC_PHONEN 3
>>> ?+#define SUN8I_ADDA_LADCMIXSC_LINEINL 2
>>> ?+#define SUN8I_ADDA_LADCMIXSC_OMIXRL 1
>>> ?+#define SUN8I_ADDA_LADCMIXSC_OMIXRR 0
>>> ?+#define SUN8I_ADDA_RADCMIXSC 0x0d
>>> ?+#define SUN8I_ADDA_RADCMIXSC_MIC1 6
>>> ?+#define SUN8I_ADDA_RADCMIXSC_MIC2 5
>>> ?+#define SUN8I_ADDA_RADCMIXSC_PHONE 4
>>> ?+#define SUN8I_ADDA_RADCMIXSC_PHONEP 3
>>> ?+#define SUN8I_ADDA_RADCMIXSC_LINEINR 2
>>> ?+#define SUN8I_ADDA_RADCMIXSC_OMIXR 1
>>> ?+#define SUN8I_ADDA_RADCMIXSC_OMIXL 0
>>> ?+#define SUN8I_ADDA_RES 0x0e
>>> ?+#define SUN8I_ADDA_RES_MMICBIAS_SEL 4
>>> ?+#define SUN8I_ADDA_RES_PA_ANTI_POP_CTRL 0
>>> ?+#define SUN8I_ADDA_ADC_AP_EN 0x0f
>>> ?+#define SUN8I_ADDA_ADC_AP_EN_ADCREN 7
>>> ?+#define SUN8I_ADDA_ADC_AP_EN_ADCLEN 6
>>> ?+#define SUN8I_ADDA_ADC_AP_EN_ADCG 0
>>> ?+
>>> ?+/* Analog control register access bits */
>>> ?+#define ADDA_PR 0x0 /* PRCM base + 0x1c0 */
>>> ?+#define ADDA_PR_RESET BIT(28)
>>> ?+#define ADDA_PR_WRITE BIT(24)
>>> ?+#define ADDA_PR_ADDR_SHIFT 16
>>> ?+#define ADDA_PR_ADDR_MASK GENMASK(4, 0)
>>> ?+#define ADDA_PR_DATA_IN_SHIFT 8
>>> ?+#define ADDA_PR_DATA_IN_MASK GENMASK(7, 0)
>>> ?+#define ADDA_PR_DATA_OUT_SHIFT 0
>>> ?+#define ADDA_PR_DATA_OUT_MASK GENMASK(7, 0)
>>> ?+
>>> ?+/* regmap access bits */
>>> ?+static int adda_reg_read(void *context, unsigned int reg, unsigned int *val)
>>> ?+{
>>> ?+ void __iomem *base = (void __iomem *)context;
>>> ?+ u32 tmp;
>>> ?+
>>> ?+ /* De-assert reset */
>>> ?+ writel(readl(base) | ADDA_PR_RESET, base);
>>> ?+
>>> ?+ /* Clear write bit */
>>> ?+ writel(readl(base) & ~ADDA_PR_WRITE, base);
>>> ?+
>>> ?+ /* Set register address */
>>> ?+ tmp = readl(base);
>>> ?+ tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
>>> ?+ tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT;
>>> ?+ writel(tmp, base);
>>> ?+
>>> ?+ /* Read back value */
>>> ?+ *val = readl(base) & ADDA_PR_DATA_OUT_MASK;
>>> ?+
>>> ?+ return 0;
>>> ?+}
>>> ?+
>>> ?+static int adda_reg_write(void *context, unsigned int reg, unsigned int val)
>>> ?+{
>>> ?+ void __iomem *base = (void __iomem *)context;
>>> ?+ u32 tmp;
>>> ?+
>>> ?+ /* De-assert reset */
>>> ?+ writel(readl(base) | ADDA_PR_RESET, base);
>>> ?+
>>> ?+ /* Set register address */
>>> ?+ tmp = readl(base);
>>> ?+ tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
>>> ?+ tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT;
>>> ?+ writel(tmp, base);
>>> ?+
>>> ?+ /* Set data to write */
>>> ?+ tmp = readl(base);
>>> ?+ tmp &= ~(ADDA_PR_DATA_IN_MASK << ADDA_PR_DATA_IN_SHIFT);
>>> ?+ tmp |= (val & ADDA_PR_DATA_IN_MASK) << ADDA_PR_DATA_IN_SHIFT;
>>> ?+ writel(tmp, base);
>>> ?+
>>> ?+ /* Set write bit to signal a write */
>>> ?+ writel(readl(base) | ADDA_PR_WRITE, base);
>>> ?+
>>> ?+ /* Clear write bit */
>>> ?+ writel(readl(base) & ~ADDA_PR_WRITE, base);
>>> ?+
>>> ?+ return 0;
>>> ?+}
>>> ?+
>>> ?+static const struct regmap_config adda_pr_regmap_cfg = {
>>> ?+ .name = "adda-pr",
>>> ?+ .reg_bits = 5,
>>> ?+ .reg_stride = 1,
>>> ?+ .val_bits = 8,
>>> ?+ .reg_read = adda_reg_read,
>>> ?+ .reg_write = adda_reg_write,
>>> ?+ .fast_io = true,
>>> ?+ .max_register = 24,
>>> ?+};
>>> ?+
>>> ?+/* mixer controls */
>>> ?+static const struct snd_kcontrol_new sun8i_codec_mixer_controls[] = {
>>> ?+ SOC_DAPM_DOUBLE_R("DAC Playback Switch",
>>> ?+ SUN8I_ADDA_LOMIXSC,
>>> ?+ SUN8I_ADDA_ROMIXSC,
>>> ?+ SUN8I_ADDA_LOMIXSC_DACL, 1, 0),
>>> ?+ SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch",
>>> ?+ SUN8I_ADDA_LOMIXSC,
>>> ?+ SUN8I_ADDA_ROMIXSC,
>>> ?+ SUN8I_ADDA_LOMIXSC_DACR, 1, 0),
>>> ?+ SOC_DAPM_DOUBLE_R("Line In Playback Switch",
>>> ?+ SUN8I_ADDA_LOMIXSC,
>>> ?+ SUN8I_ADDA_ROMIXSC,
>>> ?+ SUN8I_ADDA_LOMIXSC_LINEINL, 1, 0),
>>> ?+ SOC_DAPM_DOUBLE_R("Mic1 Playback Switch",
>>> ?+ SUN8I_ADDA_LOMIXSC,
>>> ?+ SUN8I_ADDA_ROMIXSC,
>>> ?+ SUN8I_ADDA_LOMIXSC_MIC1, 1, 0),
>>> ?+ SOC_DAPM_DOUBLE_R("Mic2 Playback Switch",
>>> ?+ SUN8I_ADDA_LOMIXSC,
>>> ?+ SUN8I_ADDA_ROMIXSC,
>>> ?+ SUN8I_ADDA_LOMIXSC_MIC2, 1, 0),
>>> ?+};
>>> ?+
>>> ?+/* ADC mixer controls */
>>> ?+static const struct snd_kcontrol_new sun8i_codec_adc_mixer_controls[] = {
>>> ?+ SOC_DAPM_DOUBLE_R("Mixer Capture Switch",
>>> ?+ SUN8I_ADDA_LADCMIXSC,
>>> ?+ SUN8I_ADDA_RADCMIXSC,
>>> ?+ SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0),
>>> ?+ SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch",
>>> ?+ SUN8I_ADDA_LADCMIXSC,
>>> ?+ SUN8I_ADDA_RADCMIXSC,
>>> ?+ SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0),
>>> ?+ SOC_DAPM_DOUBLE_R("Line In Capture Switch",
>>> ?+ SUN8I_ADDA_LADCMIXSC,
>>> ?+ SUN8I_ADDA_RADCMIXSC,
>>> ?+ SUN8I_ADDA_LADCMIXSC_LINEINL, 1, 0),
>>> ?+ SOC_DAPM_DOUBLE_R("Mic1 Capture Switch",
>>> ?+ SUN8I_ADDA_LADCMIXSC,
>>> ?+ SUN8I_ADDA_RADCMIXSC,
>>> ?+ SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0),
>>> ?+ SOC_DAPM_DOUBLE_R("Mic2 Capture Switch",
>>> ?+ SUN8I_ADDA_LADCMIXSC,
>>> ?+ SUN8I_ADDA_RADCMIXSC,
>>> ?+ SUN8I_ADDA_LADCMIXSC_MIC2, 1, 0),
>>> ?+};
>>> ?+
>>> ?+/* volume / mute controls */
>>> ?+static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale,
>>> ?+ -450, 150, 0);
>>> ?+static const DECLARE_TLV_DB_RANGE(sun8i_codec_mic_gain_scale,
>>> ?+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
>>> ?+ 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0),
>>> ?+);
>>> ?+
>>> ?+static const struct snd_kcontrol_new sun8i_codec_common_controls[] = {
>>> ?+ /* Mixer pre-gains */
>>> ?+ SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL,
>>> ?+ SUN8I_ADDA_LINEIN_GCTRL_LINEING,
>>> ?+ 0x7, 0, sun8i_codec_out_mixer_pregain_scale),
>>> ?+ SOC_SINGLE_TLV("Mic1 Playback Volume", SUN8I_ADDA_MICIN_GCTRL,
>>> ?+ SUN8I_ADDA_MICIN_GCTRL_MIC1G,
>>> ?+ 0x7, 0, sun8i_codec_out_mixer_pregain_scale),
>>> ?+ SOC_SINGLE_TLV("Mic2 Playback Volume",
>>> ?+ SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G,
>>> ?+ 0x7, 0, sun8i_codec_out_mixer_pregain_scale),
>>> ?+
>>> ?+ /* Microphone Amp boost gains */
>>> ?+ SOC_SINGLE_TLV("Mic1 Boost Volume", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
>>> ?+ SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0,
>>> ?+ sun8i_codec_mic_gain_scale),
>>> ?+ SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL,
>>> ?+ SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0,
>>> ?+ sun8i_codec_mic_gain_scale),
>>> ?+
>>> ?+ /* ADC */
>>> ?+ SOC_SINGLE_TLV("ADC Gain Capture Volume", SUN8I_ADDA_ADC_AP_EN,
>>> ?+ SUN8I_ADDA_ADC_AP_EN_ADCG, 0x7, 0,
>>> ?+ sun8i_codec_out_mixer_pregain_scale),
>>> ?+};
>>> ?+
>>> ?+static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = {
>>> ?+ /* ADC */
>>> ?+ SND_SOC_DAPM_ADC("Left ADC", NULL, SUN8I_ADDA_ADC_AP_EN,
>>> ?+ SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0),
>>> ?+ SND_SOC_DAPM_ADC("Right ADC", NULL, SUN8I_ADDA_ADC_AP_EN,
>>> ?+ SUN8I_ADDA_ADC_AP_EN_ADCREN, 0),
>>> ?+
>>> ?+ /* DAC */
>>> ?+ SND_SOC_DAPM_DAC("Left DAC", NULL, SUN8I_ADDA_DAC_PA_SRC,
>>> ?+ SUN8I_ADDA_DAC_PA_SRC_DACALEN, 0),
>>> ?+ SND_SOC_DAPM_DAC("Right DAC", NULL, SUN8I_ADDA_DAC_PA_SRC,
>>> ?+ SUN8I_ADDA_DAC_PA_SRC_DACAREN, 0),
>>> ?+ /*
>>> ?+ * Due to this component and the codec belonging to separate DAPM
>>> ?+ * contexts, we need to manually link the above widgets to their
>>> ?+ * stream widgets at the card level.
>>> ?+ */
>>> ?+
>>> ?+ /* Line In */
>>> ?+ SND_SOC_DAPM_INPUT("LINEIN"),
>>> ?+
>>> ?+ /* Microphone inputs */
>>> ?+ SND_SOC_DAPM_INPUT("MIC1"),
>>> ?+ SND_SOC_DAPM_INPUT("MIC2"),
>>> ?+
>>> ?+ /* Microphone Bias */
>>> ?+ SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
>>> ?+ SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN,
>>> ?+ 0, NULL, 0),
>>> ?+
>>> ?+ /* Mic input path */
>>> ?+ SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
>>> ?+ SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0),
>>> ?+ SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL,
>>> ?+ SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0),
>>> ?+
>>> ?+ /* Mixers */
>>> ?+ SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC,
>>> ?+ SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0,
>>> ?+ sun8i_codec_mixer_controls,
>>> ?+ ARRAY_SIZE(sun8i_codec_mixer_controls)),
>>> ?+ SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC,
>>> ?+ SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0,
>>> ?+ sun8i_codec_mixer_controls,
>>> ?+ ARRAY_SIZE(sun8i_codec_mixer_controls)),
>>> ?+ SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN,
>>> ?+ SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0,
>>> ?+ sun8i_codec_adc_mixer_controls,
>>> ?+ ARRAY_SIZE(sun8i_codec_adc_mixer_controls)),
>>> ?+ SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN,
>>> ?+ SUN8I_ADDA_ADC_AP_EN_ADCREN, 0,
>>> ?+ sun8i_codec_adc_mixer_controls,
>>> ?+ ARRAY_SIZE(sun8i_codec_adc_mixer_controls)),
>>> ?+};
>>> ?+
>>> ?+static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = {
>>> ?+ /* Microphone Routes */
>>> ?+ { "Mic1 Amplifier", NULL, "MIC1"},
>>> ?+ { "Mic2 Amplifier", NULL, "MIC2"},
>>> ?+
>>> ?+ /* Left Mixer Routes */
>>> ?+ { "Left Mixer", "DAC Playback Switch", "Left DAC" },
>>> ?+ { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" },
>>> ?+ { "Left Mixer", "Line In Playback Switch", "LINEIN" },
>>> ?+ { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
>>> ?+ { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
>>> ?+
>>> ?+ /* Right Mixer Routes */
>>> ?+ { "Right Mixer", "DAC Playback Switch", "Right DAC" },
>>> ?+ { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" },
>>> ?+ { "Right Mixer", "Line In Playback Switch", "LINEIN" },
>>> ?+ { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
>>> ?+ { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
>>> ?+
>>> ?+ /* Left ADC Mixer Routes */
>>> ?+ { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" },
>>> ?+ { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" },
>>> ?+ { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" },
>>> ?+ { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
>>> ?+ { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
>>> ?+
>>> ?+ /* Right ADC Mixer Routes */
>>> ?+ { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" },
>>> ?+ { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" },
>>> ?+ { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" },
>>> ?+ { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
>>> ?+ { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
>>> ?+
>>> ?+ /* ADC Routes */
>>> ?+ { "Left ADC", NULL, "Left ADC Mixer" },
>>> ?+ { "Right ADC", NULL, "Right ADC Mixer" },
>>> ?+};
>>> ?+
>>> ?+/* headphone specific controls, widgets, and routes */
>>> ?+static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1);
>>> ?+static const struct snd_kcontrol_new sun8i_codec_headphone_controls[] = {
>>> ?+ SOC_SINGLE_TLV("Headphone Playback Volume",
>>> ?+ SUN8I_ADDA_HP_VOLC,
>>> ?+ SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3f, 0,
>>> ?+ sun8i_codec_hp_vol_scale),
>>> ?+ SOC_DOUBLE("Headphone Playback Switch",
>>> ?+ SUN8I_ADDA_DAC_PA_SRC,
>>> ?+ SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE,
>>> ?+ SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0),
>>> ?+};
>>> ?+
>>> ?+static const char * const sun8i_codec_hp_src_enum_text[] = {
>>> ?+ "DAC", "Mixer",
>>> ?+};
>>> ?+
>>> ?+static SOC_ENUM_DOUBLE_DECL(sun8i_codec_hp_src_enum,
>>> ?+ SUN8I_ADDA_DAC_PA_SRC,
>>> ?+ SUN8I_ADDA_DAC_PA_SRC_LHPIS,
>>> ?+ SUN8I_ADDA_DAC_PA_SRC_RHPIS,
>>> ?+ sun8i_codec_hp_src_enum_text);
>>> ?+
>>> ?+static const struct snd_kcontrol_new sun8i_codec_hp_src[] = {
>>> ?+ SOC_DAPM_ENUM("Headphone Source Playback Route",
>>> ?+ sun8i_codec_hp_src_enum),
>>> ?+};
>>> ?+
>>> ?+static const struct snd_soc_dapm_widget sun8i_codec_headphone_widgets[] = {
>>> ?+ SND_SOC_DAPM_MUX("Headphone Source Playback Route",
>>> ?+ SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src),
>>> ?+ SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL,
>>> ?+ SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0),
>>> ?+ SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN8I_ADDA_PAEN_HP_CTRL,
>>> ?+ SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN, 0, NULL, 0),
>>> ?+ SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN8I_ADDA_PAEN_HP_CTRL,
>>> ?+ SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC, 0x3, 0x3, 0),
>>> ?+ SND_SOC_DAPM_OUTPUT("HP"),
>>> ?+};
>>> ?+
>>> ?+static const struct snd_soc_dapm_route sun8i_codec_headphone_routes[] = {
>>> ?+ { "Headphone Source Playback Route", "DAC", "Left DAC" },
>>> ?+ { "Headphone Source Playback Route", "DAC", "Right DAC" },
>>> ?+ { "Headphone Source Playback Route", "Mixer", "Left Mixer" },
>>> ?+ { "Headphone Source Playback Route", "Mixer", "Right Mixer" },
>>> ?+ { "Headphone Amp", NULL, "Headphone Source Playback Route" },
>>> ?+ { "HPCOM", NULL, "HPCOM Protection" },
>>> ?+ { "HP", NULL, "Headphone Amp" },
>>> ?+};
>>> ?+
>>> ?+static int sun8i_codec_add_headphone(struct snd_soc_component *cmpnt)
>>> ?+{
>>> ?+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
>>> ?+ struct device *dev = cmpnt->dev;
>>> ?+ int ret;
>>> ?+
>>> ?+ ret = snd_soc_add_component_controls(cmpnt,
>>> ?+ sun8i_codec_headphone_controls,
>>> ?+ ARRAY_SIZE(sun8i_codec_headphone_controls));
>>> ?+ if (ret) {
>>> ?+ dev_err(dev, "Failed to add Headphone controls: %d\n", ret);
>>> ?+ return ret;
>>> ?+ }
>>> ?+
>>> ?+ ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_headphone_widgets,
>>> ?+ ARRAY_SIZE(sun8i_codec_headphone_widgets));
>>> ?+ if (ret) {
>>> ?+ dev_err(dev, "Failed to add Headphone DAPM widgets: %d\n", ret);
>>> ?+ return ret;
>>> ?+ }
>>> ?+
>>> ?+ ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_headphone_routes,
>>> ?+ ARRAY_SIZE(sun8i_codec_headphone_routes));
>>> ?+ if (ret) {
>>> ?+ dev_err(dev, "Failed to add Headphone DAPM routes: %d\n", ret);
>>> ?+ return ret;
>>> ?+ }
>>> ?+
>>> ?+ return 0;
>>> ?+}
>>> ?+
>>> ?+/* hmic specific widget */
>>> ?+static const struct snd_soc_dapm_widget sun8i_codec_hmic_widgets[] = {
>>> ?+ SND_SOC_DAPM_SUPPLY("HBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
>>> ?+ SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN,
>>> ?+ 0, NULL, 0),
>>> ?+};
>>> ?+
>>> ?+static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt)
>>> ?+{
>>> ?+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
>>> ?+ struct device *dev = cmpnt->dev;
>>> ?+ int ret;
>>> ?+
>>> ?+ ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_hmic_widgets,
>>> ?+ ARRAY_SIZE(sun8i_codec_hmic_widgets));
>>> ?+ if (ret)
>>> ?+ dev_err(dev, "Failed to add Mic3 DAPM widgets: %d\n", ret);
>>> ?+
>>> ?+ return ret;
>>> ?+}
>>> ?+
>>> ?+/* line out specific controls, widgets and routes */
>>> ?+static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale,
>>> ?+ 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1),
>>> ?+ 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0),
>>> ?+);
>>> ?+static const struct snd_kcontrol_new sun8i_codec_lineout_controls[] = {
>>> ?+ SOC_SINGLE_TLV("Line Out Playback Volume",
>>> ?+ SUN8I_ADDA_PHONE_GAIN_CTRL,
>>> ?+ SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL, 0x1f, 0,
>>> ?+ sun8i_codec_lineout_vol_scale),
>>> ?+ SOC_DOUBLE("Line Out Playback Switch",
>>> ?+ SUN8I_ADDA_MIC2G_CTRL,
>>> ?+ SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN,
>>> ?+ SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN, 1, 0),
>>> ?+};
>>> ?+
>>> ?+static const char * const sun8i_codec_lineout_src_enum_text[] = {
>>> ?+ "Stereo", "Mono Differential",
>>> ?+};
>>> ?+
>>> ?+static SOC_ENUM_DOUBLE_DECL(sun8i_codec_lineout_src_enum,
>>> ?+ SUN8I_ADDA_MIC2G_CTRL,
>>> ?+ SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC,
>>> ?+ SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC,
>>> ?+ sun8i_codec_lineout_src_enum_text);
>>> ?+
>>> ?+static const struct snd_kcontrol_new sun8i_codec_lineout_src[] = {
>>> ?+ SOC_DAPM_ENUM("Line Out Source Playback Route",
>>> ?+ sun8i_codec_lineout_src_enum),
>>> ?+};
>>> ?+
>>> ?+static const struct snd_soc_dapm_widget sun8i_codec_lineout_widgets[] = {
>>> ?+ SND_SOC_DAPM_MUX("Line Out Source Playback Route",
>>> ?+ SND_SOC_NOPM, 0, 0, sun8i_codec_lineout_src),
>>> ?+ /* It is unclear if this is a buffer or gate, model it as a supply */
>>> ?+ SND_SOC_DAPM_SUPPLY("Line Out Enable", SUN8I_ADDA_PAEN_HP_CTRL,
>>> ?+ SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN, 0, NULL, 0),
>>> ?+ SND_SOC_DAPM_OUTPUT("LINEOUT"),
>>> ?+};
>>> ?+
>>> ?+static const struct snd_soc_dapm_route sun8i_codec_lineout_routes[] = {
>>> ?+ { "Line Out Source Playback Route", "Stereo", "Left Mixer" },
>>> ?+ { "Line Out Source Playback Route", "Stereo", "Right Mixer" },
>>> ?+ { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" },
>>> ?+ { "Line Out Source Playback Route", "Mono Differential", "Right Mixer" },
>>> ?+ { "LINEOUT", NULL, "Line Out Source Playback Route" },
>>> ?+ { "LINEOUT", NULL, "Line Out Enable", },
>>> ?+};
>>> ?+
>>> ?+static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt)
>>> ?+{
>>> ?+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
>>> ?+ struct device *dev = cmpnt->dev;
>>> ?+ int ret;
>>> ?+
>>> ?+ ret = snd_soc_add_component_controls(cmpnt,
>>> ?+ sun8i_codec_lineout_controls,
>>> ?+ ARRAY_SIZE(sun8i_codec_lineout_controls));
>>> ?+ if (ret) {
>>> ?+ dev_err(dev, "Failed to add Line Out controls: %d\n", ret);
>>> ?+ return ret;
>>> ?+ }
>>> ?+
>>> ?+ ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_lineout_widgets,
>>> ?+ ARRAY_SIZE(sun8i_codec_lineout_widgets));
>>> ?+ if (ret) {
>>> ?+ dev_err(dev, "Failed to add Line Out DAPM widgets: %d\n", ret);
>>> ?+ return ret;
>>> ?+ }
>>> ?+
>>> ?+ ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_lineout_routes,
>>> ?+ ARRAY_SIZE(sun8i_codec_lineout_routes));
>>> ?+ if (ret) {
>>> ?+ dev_err(dev, "Failed to add Line Out DAPM routes: %d\n", ret);
>>> ?+ return ret;
>>> ?+ }
>>> ?+
>>> ?+ return 0;
>>> ?+}
>>> ?+
>>> ?+struct sun8i_codec_analog_quirks {
>>> ?+ bool has_headphone;
>>> ?+ bool has_hmic;
>>> ?+ bool has_lineout;
>>> ?+};
>>> ?+
>>> ?+static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = {
>>> ?+ .has_headphone = true,
>>> ?+ .has_hmic = true,
>>> ?+};
>>> ?+
>>> ?+static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = {
>>> ?+ .has_lineout = true,
>>> ?+};
>>> ?+
>>> ?+static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt)
>>> ?+{
>>> ?+ struct device *dev = cmpnt->dev;
>>> ?+ const struct sun8i_codec_analog_quirks *quirks;
>>> ?+ int ret;
>>> ?+
>>> ?+ /*
>>> ?+ * This would never return NULL unless someone directly registers a
>>> ?+ * platform device matching this driver's name, without specifying a
>>> ?+ * device tree node.
>>> ?+ */
>>> ?+ quirks = of_device_get_match_data(dev);
>>> ?+
>>> ?+ /* Add controls, widgets, and routes for individual features */
>>> ?+
>>> ?+ if (quirks->has_headphone) {
>>> ?+ ret = sun8i_codec_add_headphone(cmpnt);
>>> ?+ if (ret)
>>> ?+ return ret;
>>> ?+ }
>>> ?+
>>> ?+ if (quirks->has_hmic) {
>>> ?+ sun8i_codec_add_hmic(cmpnt);
>>> ?+ if (ret)
>>> ?+ return ret;
>>> ?+ }
>>> ?+
>>> ?+ if (quirks->has_lineout) {
>>> ?+ ret = sun8i_codec_add_lineout(cmpnt);
>>> ?+ if (ret)
>>> ?+ return ret;
>>> ?+ }
>>> ?+
>>> ?+ return 0;
>>> ?+}
>>> ?+
>>> ?+static const struct snd_soc_component_driver sun8i_codec_analog_cmpnt_drv = {
>>> ?+ .controls = sun8i_codec_common_controls,
>>> ?+ .num_controls = ARRAY_SIZE(sun8i_codec_common_controls),
>>> ?+ .dapm_widgets = sun8i_codec_common_widgets,
>>> ?+ .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_common_widgets),
>>> ?+ .dapm_routes = sun8i_codec_common_routes,
>>> ?+ .num_dapm_routes = ARRAY_SIZE(sun8i_codec_common_routes),
>>> ?+ .probe = sun8i_codec_analog_cmpnt_probe,
>>> ?+};
>>> ?+
>>> ?+static const struct of_device_id sun8i_codec_analog_of_match[] = {
>>> ?+ {
>>> ?+ .compatible = "allwinner,sun8i-a23-codec-analog",
>>> ?+ .data = &sun8i_a23_quirks,
>>> ?+ },
>>> ?+ {
>>> ?+ .compatible = "allwinner,sun8i-h3-codec-analog",
>>> ?+ .data = &sun8i_h3_quirks,
>>> ?+ },
>>> ?+ {}
>>> ?+};
>>> ?+MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match);
>>> ?+
>>> ?+static int sun8i_codec_analog_probe(struct platform_device *pdev)
>>> ?+{
>>> ?+ struct resource *res;
>>> ?+ struct regmap *regmap;
>>> ?+ void __iomem *base;
>>> ?+
>>> ?+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>> ?+ base = devm_ioremap_resource(&pdev->dev, res);
>>> ?+ if (IS_ERR(base)) {
>>> ?+ dev_err(&pdev->dev, "Failed to map the registers\n");
>>> ?+ return PTR_ERR(base);
>>> ?+ }
>>> ?+
>>> ?+ regmap = devm_regmap_init(&pdev->dev, NULL, base, &adda_pr_regmap_cfg);
>>> ?+ if (IS_ERR(regmap)) {
>>> ?+ dev_err(&pdev->dev, "Failed to create regmap\n");
>>> ?+ return PTR_ERR(regmap);
>>> ?+ }
>>> ?+
>>> ?+ return devm_snd_soc_register_component(&pdev->dev,
>>> ?+ &sun8i_codec_analog_cmpnt_drv,
>>> ?+ NULL, 0);
>>> ?+}
>>> ?+
>>> ?+static struct platform_driver sun8i_codec_analog_driver = {
>>> ?+ .driver = {
>>> ?+ .name = "sun8i-codec-analog",
>>> ?+ .of_match_table = sun8i_codec_analog_of_match,
>>> ?+ },
>>> ?+ .probe = sun8i_codec_analog_probe,
>>> ?+};
>>> ?+module_platform_driver(sun8i_codec_analog_driver);
>>> ?+
>>> ?+MODULE_DESCRIPTION("Allwinner internal codec analog controls driver");
>>> ?+MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
>>> ?+MODULE_LICENSE("GPL");
>>> ?+MODULE_ALIAS("platform:sun8i-codec-analog");
>>> ?--
>>> ?2.10.2
>>>
>>> ?_______________________________________________
>>> ?linux-arm-kernel mailing list
>>> ?linux-arm-kernel at lists.infradead.org
>>> ?http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* [PATCH v2] dmaengine: mv_xor: Add support for scatter-gather DMA mode
From: Vinod Koul @ 2016-11-25  5:46 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161026081025.28322-1-sr@denx.de>

On Wed, Oct 26, 2016 at 10:10:25AM +0200, Stefan Roese wrote:
> This patch adds memory to memory scatter-gather support to the Marvell
> mv_or DMA driver.

Applied, thanks

-- 
~Vinod

^ permalink raw reply

* [PATCH 02/10] ASoC: sunxi: Add support for A23/A33/H3 codec's analog path controls
From: Chen-Yu Tsai @ 2016-11-25  5:45 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <901011480052591@web1j.yandex.ru>

On Fri, Nov 25, 2016 at 1:43 PM, Icenowy Zheng <icenowy@aosc.xyz> wrote:
>
>
> 12.11.2016, 14:57, "Chen-Yu Tsai" <wens@csie.org>:
>> The internal codec on A23/A33/H3 is split into 2 parts. The
>> analog path controls are routed through an embedded custom register
>> bus accessed through the PRCM block.
>>
>> The SoCs share a common set of inputs, outputs, and audio paths.
>> The following table lists the differences.
>>
>>     ----------------------------------------
>>     | Feature \ SoC | A23 | A33 | H3 |
>>     ----------------------------------------
>>     | Headphone | v | v | |
>>     ----------------------------------------
>>     | Line Out | | | v |
>>     ----------------------------------------
>>     | Phone In/Out | v | v | |
>>     ----------------------------------------
>>
>> Add an ASoC component driver for it. This should be tied to the codec
>> audio card as an auxiliary device. This patch adds the commont paths
>> and controls, and variant specific headphone out and line out.
>>
>> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
>> ---
>>  sound/soc/sunxi/Kconfig | 8 +
>>  sound/soc/sunxi/Makefile | 1 +
>>  sound/soc/sunxi/sun8i-codec-analog.c | 665 +++++++++++++++++++++++++++++++++++
>>  3 files changed, 674 insertions(+)
>>  create mode 100644 sound/soc/sunxi/sun8i-codec-analog.c
>>
>> diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
>> index dd2368297fd3..6c344e16aca4 100644
>> --- a/sound/soc/sunxi/Kconfig
>> +++ b/sound/soc/sunxi/Kconfig
>> @@ -9,6 +9,14 @@ config SND_SUN4I_CODEC
>>            Select Y or M to add support for the Codec embedded in the Allwinner
>>            A10 and affiliated SoCs.
>>
>> +config SND_SUN8I_CODEC_ANALOG
>> + tristate "Allwinner sun8i Codec Analog Controls Support"
>> + depends on MACH_SUN8I || COMPILE_TEST
>
> sun50i-a64 has a similar (or the same?) codec to A33.
>

I think the register offsets/fields were moved around again.
Why does Allwinner always do that... :/

ChenYu

>> + select REGMAP
>> + help
>> + Say Y or M if you want to add support for the analog controls for
>> + the codec embedded in newer Allwinner SoCs.
>> +
>>  config SND_SUN4I_I2S
>>          tristate "Allwinner A10 I2S Support"
>>          select SND_SOC_GENERIC_DMAENGINE_PCM
>> diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile
>> index 604c7b842837..241c0df9ca0c 100644
>> --- a/sound/soc/sunxi/Makefile
>> +++ b/sound/soc/sunxi/Makefile
>> @@ -1,3 +1,4 @@
>>  obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o
>>  obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o
>>  obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o
>> +obj-$(CONFIG_SND_SUN8I_CODEC_ANALOG) += sun8i-codec-analog.o
>> diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c
>> new file mode 100644
>> index 000000000000..222bbd440b1e
>> --- /dev/null
>> +++ b/sound/soc/sunxi/sun8i-codec-analog.c
>> @@ -0,0 +1,665 @@
>> +/*
>> + * This driver supports the analog controls for the internal codec
>> + * found in Allwinner's A31s, A23, A33 and H3 SoCs.
>> + *
>> + * Copyright 2016 Chen-Yu Tsai <wens@csie.org>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + * GNU General Public License for more details.
>> + */
>> +
>> +#include <linux/io.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regmap.h>
>> +
>> +#include <sound/soc.h>
>> +#include <sound/soc-dapm.h>
>> +#include <sound/tlv.h>
>> +
>> +/* Codec analog control register offsets and bit fields */
>> +#define SUN8I_ADDA_HP_VOLC 0x00
>> +#define SUN8I_ADDA_HP_VOLC_PA_CLK_GATE 7
>> +#define SUN8I_ADDA_HP_VOLC_HP_VOL 0
>> +#define SUN8I_ADDA_LOMIXSC 0x01
>> +#define SUN8I_ADDA_LOMIXSC_MIC1 6
>> +#define SUN8I_ADDA_LOMIXSC_MIC2 5
>> +#define SUN8I_ADDA_LOMIXSC_PHONE 4
>> +#define SUN8I_ADDA_LOMIXSC_PHONEN 3
>> +#define SUN8I_ADDA_LOMIXSC_LINEINL 2
>> +#define SUN8I_ADDA_LOMIXSC_DACL 1
>> +#define SUN8I_ADDA_LOMIXSC_DACR 0
>> +#define SUN8I_ADDA_ROMIXSC 0x02
>> +#define SUN8I_ADDA_ROMIXSC_MIC1 6
>> +#define SUN8I_ADDA_ROMIXSC_MIC2 5
>> +#define SUN8I_ADDA_ROMIXSC_PHONE 4
>> +#define SUN8I_ADDA_ROMIXSC_PHONEP 3
>> +#define SUN8I_ADDA_ROMIXSC_LINEINR 2
>> +#define SUN8I_ADDA_ROMIXSC_DACR 1
>> +#define SUN8I_ADDA_ROMIXSC_DACL 0
>> +#define SUN8I_ADDA_DAC_PA_SRC 0x03
>> +#define SUN8I_ADDA_DAC_PA_SRC_DACAREN 7
>> +#define SUN8I_ADDA_DAC_PA_SRC_DACALEN 6
>> +#define SUN8I_ADDA_DAC_PA_SRC_RMIXEN 5
>> +#define SUN8I_ADDA_DAC_PA_SRC_LMIXEN 4
>> +#define SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE 3
>> +#define SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE 2
>> +#define SUN8I_ADDA_DAC_PA_SRC_RHPIS 1
>> +#define SUN8I_ADDA_DAC_PA_SRC_LHPIS 0
>> +#define SUN8I_ADDA_PHONEIN_GCTRL 0x04
>> +#define SUN8I_ADDA_PHONEIN_GCTRL_PHONEPG 4
>> +#define SUN8I_ADDA_PHONEIN_GCTRL_PHONENG 0
>> +#define SUN8I_ADDA_LINEIN_GCTRL 0x05
>> +#define SUN8I_ADDA_LINEIN_GCTRL_LINEING 4
>> +#define SUN8I_ADDA_LINEIN_GCTRL_PHONEG 0
>> +#define SUN8I_ADDA_MICIN_GCTRL 0x06
>> +#define SUN8I_ADDA_MICIN_GCTRL_MIC1G 4
>> +#define SUN8I_ADDA_MICIN_GCTRL_MIC2G 0
>> +#define SUN8I_ADDA_PAEN_HP_CTRL 0x07
>> +#define SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN 7
>> +#define SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN 7 /* H3 specific */
>> +#define SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC 5
>> +#define SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN 4
>> +#define SUN8I_ADDA_PAEN_HP_CTRL_PA_ANTI_POP_CTRL 2
>> +#define SUN8I_ADDA_PAEN_HP_CTRL_LTRNMUTE 1
>> +#define SUN8I_ADDA_PAEN_HP_CTRL_RTLNMUTE 0
>> +#define SUN8I_ADDA_PHONEOUT_CTRL 0x08
>> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTG 5
>> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTEN 4
>> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC1 3
>> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC2 2
>> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_RMIX 1
>> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_LMIX 0
>> +#define SUN8I_ADDA_PHONE_GAIN_CTRL 0x09
>> +#define SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL 3
>> +#define SUN8I_ADDA_PHONE_GAIN_CTRL_PHONEPREG 0
>> +#define SUN8I_ADDA_MIC2G_CTRL 0x0a
>> +#define SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN 7
>> +#define SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST 4
>> +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN 3
>> +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN 2
>> +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC 1
>> +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC 0
>> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL 0x0b
>> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN 7
>> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN 6
>> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIAS_MODE 5
>> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN 3
>> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST 0
>> +#define SUN8I_ADDA_LADCMIXSC 0x0c
>> +#define SUN8I_ADDA_LADCMIXSC_MIC1 6
>> +#define SUN8I_ADDA_LADCMIXSC_MIC2 5
>> +#define SUN8I_ADDA_LADCMIXSC_PHONE 4
>> +#define SUN8I_ADDA_LADCMIXSC_PHONEN 3
>> +#define SUN8I_ADDA_LADCMIXSC_LINEINL 2
>> +#define SUN8I_ADDA_LADCMIXSC_OMIXRL 1
>> +#define SUN8I_ADDA_LADCMIXSC_OMIXRR 0
>> +#define SUN8I_ADDA_RADCMIXSC 0x0d
>> +#define SUN8I_ADDA_RADCMIXSC_MIC1 6
>> +#define SUN8I_ADDA_RADCMIXSC_MIC2 5
>> +#define SUN8I_ADDA_RADCMIXSC_PHONE 4
>> +#define SUN8I_ADDA_RADCMIXSC_PHONEP 3
>> +#define SUN8I_ADDA_RADCMIXSC_LINEINR 2
>> +#define SUN8I_ADDA_RADCMIXSC_OMIXR 1
>> +#define SUN8I_ADDA_RADCMIXSC_OMIXL 0
>> +#define SUN8I_ADDA_RES 0x0e
>> +#define SUN8I_ADDA_RES_MMICBIAS_SEL 4
>> +#define SUN8I_ADDA_RES_PA_ANTI_POP_CTRL 0
>> +#define SUN8I_ADDA_ADC_AP_EN 0x0f
>> +#define SUN8I_ADDA_ADC_AP_EN_ADCREN 7
>> +#define SUN8I_ADDA_ADC_AP_EN_ADCLEN 6
>> +#define SUN8I_ADDA_ADC_AP_EN_ADCG 0
>> +
>> +/* Analog control register access bits */
>> +#define ADDA_PR 0x0 /* PRCM base + 0x1c0 */
>> +#define ADDA_PR_RESET BIT(28)
>> +#define ADDA_PR_WRITE BIT(24)
>> +#define ADDA_PR_ADDR_SHIFT 16
>> +#define ADDA_PR_ADDR_MASK GENMASK(4, 0)
>> +#define ADDA_PR_DATA_IN_SHIFT 8
>> +#define ADDA_PR_DATA_IN_MASK GENMASK(7, 0)
>> +#define ADDA_PR_DATA_OUT_SHIFT 0
>> +#define ADDA_PR_DATA_OUT_MASK GENMASK(7, 0)
>> +
>> +/* regmap access bits */
>> +static int adda_reg_read(void *context, unsigned int reg, unsigned int *val)
>> +{
>> + void __iomem *base = (void __iomem *)context;
>> + u32 tmp;
>> +
>> + /* De-assert reset */
>> + writel(readl(base) | ADDA_PR_RESET, base);
>> +
>> + /* Clear write bit */
>> + writel(readl(base) & ~ADDA_PR_WRITE, base);
>> +
>> + /* Set register address */
>> + tmp = readl(base);
>> + tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
>> + tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT;
>> + writel(tmp, base);
>> +
>> + /* Read back value */
>> + *val = readl(base) & ADDA_PR_DATA_OUT_MASK;
>> +
>> + return 0;
>> +}
>> +
>> +static int adda_reg_write(void *context, unsigned int reg, unsigned int val)
>> +{
>> + void __iomem *base = (void __iomem *)context;
>> + u32 tmp;
>> +
>> + /* De-assert reset */
>> + writel(readl(base) | ADDA_PR_RESET, base);
>> +
>> + /* Set register address */
>> + tmp = readl(base);
>> + tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
>> + tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT;
>> + writel(tmp, base);
>> +
>> + /* Set data to write */
>> + tmp = readl(base);
>> + tmp &= ~(ADDA_PR_DATA_IN_MASK << ADDA_PR_DATA_IN_SHIFT);
>> + tmp |= (val & ADDA_PR_DATA_IN_MASK) << ADDA_PR_DATA_IN_SHIFT;
>> + writel(tmp, base);
>> +
>> + /* Set write bit to signal a write */
>> + writel(readl(base) | ADDA_PR_WRITE, base);
>> +
>> + /* Clear write bit */
>> + writel(readl(base) & ~ADDA_PR_WRITE, base);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct regmap_config adda_pr_regmap_cfg = {
>> + .name = "adda-pr",
>> + .reg_bits = 5,
>> + .reg_stride = 1,
>> + .val_bits = 8,
>> + .reg_read = adda_reg_read,
>> + .reg_write = adda_reg_write,
>> + .fast_io = true,
>> + .max_register = 24,
>> +};
>> +
>> +/* mixer controls */
>> +static const struct snd_kcontrol_new sun8i_codec_mixer_controls[] = {
>> + SOC_DAPM_DOUBLE_R("DAC Playback Switch",
>> + SUN8I_ADDA_LOMIXSC,
>> + SUN8I_ADDA_ROMIXSC,
>> + SUN8I_ADDA_LOMIXSC_DACL, 1, 0),
>> + SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch",
>> + SUN8I_ADDA_LOMIXSC,
>> + SUN8I_ADDA_ROMIXSC,
>> + SUN8I_ADDA_LOMIXSC_DACR, 1, 0),
>> + SOC_DAPM_DOUBLE_R("Line In Playback Switch",
>> + SUN8I_ADDA_LOMIXSC,
>> + SUN8I_ADDA_ROMIXSC,
>> + SUN8I_ADDA_LOMIXSC_LINEINL, 1, 0),
>> + SOC_DAPM_DOUBLE_R("Mic1 Playback Switch",
>> + SUN8I_ADDA_LOMIXSC,
>> + SUN8I_ADDA_ROMIXSC,
>> + SUN8I_ADDA_LOMIXSC_MIC1, 1, 0),
>> + SOC_DAPM_DOUBLE_R("Mic2 Playback Switch",
>> + SUN8I_ADDA_LOMIXSC,
>> + SUN8I_ADDA_ROMIXSC,
>> + SUN8I_ADDA_LOMIXSC_MIC2, 1, 0),
>> +};
>> +
>> +/* ADC mixer controls */
>> +static const struct snd_kcontrol_new sun8i_codec_adc_mixer_controls[] = {
>> + SOC_DAPM_DOUBLE_R("Mixer Capture Switch",
>> + SUN8I_ADDA_LADCMIXSC,
>> + SUN8I_ADDA_RADCMIXSC,
>> + SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0),
>> + SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch",
>> + SUN8I_ADDA_LADCMIXSC,
>> + SUN8I_ADDA_RADCMIXSC,
>> + SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0),
>> + SOC_DAPM_DOUBLE_R("Line In Capture Switch",
>> + SUN8I_ADDA_LADCMIXSC,
>> + SUN8I_ADDA_RADCMIXSC,
>> + SUN8I_ADDA_LADCMIXSC_LINEINL, 1, 0),
>> + SOC_DAPM_DOUBLE_R("Mic1 Capture Switch",
>> + SUN8I_ADDA_LADCMIXSC,
>> + SUN8I_ADDA_RADCMIXSC,
>> + SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0),
>> + SOC_DAPM_DOUBLE_R("Mic2 Capture Switch",
>> + SUN8I_ADDA_LADCMIXSC,
>> + SUN8I_ADDA_RADCMIXSC,
>> + SUN8I_ADDA_LADCMIXSC_MIC2, 1, 0),
>> +};
>> +
>> +/* volume / mute controls */
>> +static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale,
>> + -450, 150, 0);
>> +static const DECLARE_TLV_DB_RANGE(sun8i_codec_mic_gain_scale,
>> + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
>> + 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0),
>> +);
>> +
>> +static const struct snd_kcontrol_new sun8i_codec_common_controls[] = {
>> + /* Mixer pre-gains */
>> + SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL,
>> + SUN8I_ADDA_LINEIN_GCTRL_LINEING,
>> + 0x7, 0, sun8i_codec_out_mixer_pregain_scale),
>> + SOC_SINGLE_TLV("Mic1 Playback Volume", SUN8I_ADDA_MICIN_GCTRL,
>> + SUN8I_ADDA_MICIN_GCTRL_MIC1G,
>> + 0x7, 0, sun8i_codec_out_mixer_pregain_scale),
>> + SOC_SINGLE_TLV("Mic2 Playback Volume",
>> + SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G,
>> + 0x7, 0, sun8i_codec_out_mixer_pregain_scale),
>> +
>> + /* Microphone Amp boost gains */
>> + SOC_SINGLE_TLV("Mic1 Boost Volume", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
>> + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0,
>> + sun8i_codec_mic_gain_scale),
>> + SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL,
>> + SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0,
>> + sun8i_codec_mic_gain_scale),
>> +
>> + /* ADC */
>> + SOC_SINGLE_TLV("ADC Gain Capture Volume", SUN8I_ADDA_ADC_AP_EN,
>> + SUN8I_ADDA_ADC_AP_EN_ADCG, 0x7, 0,
>> + sun8i_codec_out_mixer_pregain_scale),
>> +};
>> +
>> +static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = {
>> + /* ADC */
>> + SND_SOC_DAPM_ADC("Left ADC", NULL, SUN8I_ADDA_ADC_AP_EN,
>> + SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0),
>> + SND_SOC_DAPM_ADC("Right ADC", NULL, SUN8I_ADDA_ADC_AP_EN,
>> + SUN8I_ADDA_ADC_AP_EN_ADCREN, 0),
>> +
>> + /* DAC */
>> + SND_SOC_DAPM_DAC("Left DAC", NULL, SUN8I_ADDA_DAC_PA_SRC,
>> + SUN8I_ADDA_DAC_PA_SRC_DACALEN, 0),
>> + SND_SOC_DAPM_DAC("Right DAC", NULL, SUN8I_ADDA_DAC_PA_SRC,
>> + SUN8I_ADDA_DAC_PA_SRC_DACAREN, 0),
>> + /*
>> + * Due to this component and the codec belonging to separate DAPM
>> + * contexts, we need to manually link the above widgets to their
>> + * stream widgets at the card level.
>> + */
>> +
>> + /* Line In */
>> + SND_SOC_DAPM_INPUT("LINEIN"),
>> +
>> + /* Microphone inputs */
>> + SND_SOC_DAPM_INPUT("MIC1"),
>> + SND_SOC_DAPM_INPUT("MIC2"),
>> +
>> + /* Microphone Bias */
>> + SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
>> + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN,
>> + 0, NULL, 0),
>> +
>> + /* Mic input path */
>> + SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
>> + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0),
>> + SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL,
>> + SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0),
>> +
>> + /* Mixers */
>> + SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC,
>> + SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0,
>> + sun8i_codec_mixer_controls,
>> + ARRAY_SIZE(sun8i_codec_mixer_controls)),
>> + SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC,
>> + SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0,
>> + sun8i_codec_mixer_controls,
>> + ARRAY_SIZE(sun8i_codec_mixer_controls)),
>> + SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN,
>> + SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0,
>> + sun8i_codec_adc_mixer_controls,
>> + ARRAY_SIZE(sun8i_codec_adc_mixer_controls)),
>> + SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN,
>> + SUN8I_ADDA_ADC_AP_EN_ADCREN, 0,
>> + sun8i_codec_adc_mixer_controls,
>> + ARRAY_SIZE(sun8i_codec_adc_mixer_controls)),
>> +};
>> +
>> +static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = {
>> + /* Microphone Routes */
>> + { "Mic1 Amplifier", NULL, "MIC1"},
>> + { "Mic2 Amplifier", NULL, "MIC2"},
>> +
>> + /* Left Mixer Routes */
>> + { "Left Mixer", "DAC Playback Switch", "Left DAC" },
>> + { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" },
>> + { "Left Mixer", "Line In Playback Switch", "LINEIN" },
>> + { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
>> + { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
>> +
>> + /* Right Mixer Routes */
>> + { "Right Mixer", "DAC Playback Switch", "Right DAC" },
>> + { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" },
>> + { "Right Mixer", "Line In Playback Switch", "LINEIN" },
>> + { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
>> + { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
>> +
>> + /* Left ADC Mixer Routes */
>> + { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" },
>> + { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" },
>> + { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" },
>> + { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
>> + { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
>> +
>> + /* Right ADC Mixer Routes */
>> + { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" },
>> + { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" },
>> + { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" },
>> + { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
>> + { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
>> +
>> + /* ADC Routes */
>> + { "Left ADC", NULL, "Left ADC Mixer" },
>> + { "Right ADC", NULL, "Right ADC Mixer" },
>> +};
>> +
>> +/* headphone specific controls, widgets, and routes */
>> +static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1);
>> +static const struct snd_kcontrol_new sun8i_codec_headphone_controls[] = {
>> + SOC_SINGLE_TLV("Headphone Playback Volume",
>> + SUN8I_ADDA_HP_VOLC,
>> + SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3f, 0,
>> + sun8i_codec_hp_vol_scale),
>> + SOC_DOUBLE("Headphone Playback Switch",
>> + SUN8I_ADDA_DAC_PA_SRC,
>> + SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE,
>> + SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0),
>> +};
>> +
>> +static const char * const sun8i_codec_hp_src_enum_text[] = {
>> + "DAC", "Mixer",
>> +};
>> +
>> +static SOC_ENUM_DOUBLE_DECL(sun8i_codec_hp_src_enum,
>> + SUN8I_ADDA_DAC_PA_SRC,
>> + SUN8I_ADDA_DAC_PA_SRC_LHPIS,
>> + SUN8I_ADDA_DAC_PA_SRC_RHPIS,
>> + sun8i_codec_hp_src_enum_text);
>> +
>> +static const struct snd_kcontrol_new sun8i_codec_hp_src[] = {
>> + SOC_DAPM_ENUM("Headphone Source Playback Route",
>> + sun8i_codec_hp_src_enum),
>> +};
>> +
>> +static const struct snd_soc_dapm_widget sun8i_codec_headphone_widgets[] = {
>> + SND_SOC_DAPM_MUX("Headphone Source Playback Route",
>> + SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src),
>> + SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL,
>> + SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0),
>> + SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN8I_ADDA_PAEN_HP_CTRL,
>> + SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN, 0, NULL, 0),
>> + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN8I_ADDA_PAEN_HP_CTRL,
>> + SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC, 0x3, 0x3, 0),
>> + SND_SOC_DAPM_OUTPUT("HP"),
>> +};
>> +
>> +static const struct snd_soc_dapm_route sun8i_codec_headphone_routes[] = {
>> + { "Headphone Source Playback Route", "DAC", "Left DAC" },
>> + { "Headphone Source Playback Route", "DAC", "Right DAC" },
>> + { "Headphone Source Playback Route", "Mixer", "Left Mixer" },
>> + { "Headphone Source Playback Route", "Mixer", "Right Mixer" },
>> + { "Headphone Amp", NULL, "Headphone Source Playback Route" },
>> + { "HPCOM", NULL, "HPCOM Protection" },
>> + { "HP", NULL, "Headphone Amp" },
>> +};
>> +
>> +static int sun8i_codec_add_headphone(struct snd_soc_component *cmpnt)
>> +{
>> + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
>> + struct device *dev = cmpnt->dev;
>> + int ret;
>> +
>> + ret = snd_soc_add_component_controls(cmpnt,
>> + sun8i_codec_headphone_controls,
>> + ARRAY_SIZE(sun8i_codec_headphone_controls));
>> + if (ret) {
>> + dev_err(dev, "Failed to add Headphone controls: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_headphone_widgets,
>> + ARRAY_SIZE(sun8i_codec_headphone_widgets));
>> + if (ret) {
>> + dev_err(dev, "Failed to add Headphone DAPM widgets: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_headphone_routes,
>> + ARRAY_SIZE(sun8i_codec_headphone_routes));
>> + if (ret) {
>> + dev_err(dev, "Failed to add Headphone DAPM routes: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +/* hmic specific widget */
>> +static const struct snd_soc_dapm_widget sun8i_codec_hmic_widgets[] = {
>> + SND_SOC_DAPM_SUPPLY("HBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
>> + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN,
>> + 0, NULL, 0),
>> +};
>> +
>> +static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt)
>> +{
>> + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
>> + struct device *dev = cmpnt->dev;
>> + int ret;
>> +
>> + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_hmic_widgets,
>> + ARRAY_SIZE(sun8i_codec_hmic_widgets));
>> + if (ret)
>> + dev_err(dev, "Failed to add Mic3 DAPM widgets: %d\n", ret);
>> +
>> + return ret;
>> +}
>> +
>> +/* line out specific controls, widgets and routes */
>> +static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale,
>> + 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1),
>> + 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0),
>> +);
>> +static const struct snd_kcontrol_new sun8i_codec_lineout_controls[] = {
>> + SOC_SINGLE_TLV("Line Out Playback Volume",
>> + SUN8I_ADDA_PHONE_GAIN_CTRL,
>> + SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL, 0x1f, 0,
>> + sun8i_codec_lineout_vol_scale),
>> + SOC_DOUBLE("Line Out Playback Switch",
>> + SUN8I_ADDA_MIC2G_CTRL,
>> + SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN,
>> + SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN, 1, 0),
>> +};
>> +
>> +static const char * const sun8i_codec_lineout_src_enum_text[] = {
>> + "Stereo", "Mono Differential",
>> +};
>> +
>> +static SOC_ENUM_DOUBLE_DECL(sun8i_codec_lineout_src_enum,
>> + SUN8I_ADDA_MIC2G_CTRL,
>> + SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC,
>> + SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC,
>> + sun8i_codec_lineout_src_enum_text);
>> +
>> +static const struct snd_kcontrol_new sun8i_codec_lineout_src[] = {
>> + SOC_DAPM_ENUM("Line Out Source Playback Route",
>> + sun8i_codec_lineout_src_enum),
>> +};
>> +
>> +static const struct snd_soc_dapm_widget sun8i_codec_lineout_widgets[] = {
>> + SND_SOC_DAPM_MUX("Line Out Source Playback Route",
>> + SND_SOC_NOPM, 0, 0, sun8i_codec_lineout_src),
>> + /* It is unclear if this is a buffer or gate, model it as a supply */
>> + SND_SOC_DAPM_SUPPLY("Line Out Enable", SUN8I_ADDA_PAEN_HP_CTRL,
>> + SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN, 0, NULL, 0),
>> + SND_SOC_DAPM_OUTPUT("LINEOUT"),
>> +};
>> +
>> +static const struct snd_soc_dapm_route sun8i_codec_lineout_routes[] = {
>> + { "Line Out Source Playback Route", "Stereo", "Left Mixer" },
>> + { "Line Out Source Playback Route", "Stereo", "Right Mixer" },
>> + { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" },
>> + { "Line Out Source Playback Route", "Mono Differential", "Right Mixer" },
>> + { "LINEOUT", NULL, "Line Out Source Playback Route" },
>> + { "LINEOUT", NULL, "Line Out Enable", },
>> +};
>> +
>> +static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt)
>> +{
>> + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
>> + struct device *dev = cmpnt->dev;
>> + int ret;
>> +
>> + ret = snd_soc_add_component_controls(cmpnt,
>> + sun8i_codec_lineout_controls,
>> + ARRAY_SIZE(sun8i_codec_lineout_controls));
>> + if (ret) {
>> + dev_err(dev, "Failed to add Line Out controls: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_lineout_widgets,
>> + ARRAY_SIZE(sun8i_codec_lineout_widgets));
>> + if (ret) {
>> + dev_err(dev, "Failed to add Line Out DAPM widgets: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_lineout_routes,
>> + ARRAY_SIZE(sun8i_codec_lineout_routes));
>> + if (ret) {
>> + dev_err(dev, "Failed to add Line Out DAPM routes: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +struct sun8i_codec_analog_quirks {
>> + bool has_headphone;
>> + bool has_hmic;
>> + bool has_lineout;
>> +};
>> +
>> +static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = {
>> + .has_headphone = true,
>> + .has_hmic = true,
>> +};
>> +
>> +static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = {
>> + .has_lineout = true,
>> +};
>> +
>> +static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt)
>> +{
>> + struct device *dev = cmpnt->dev;
>> + const struct sun8i_codec_analog_quirks *quirks;
>> + int ret;
>> +
>> + /*
>> + * This would never return NULL unless someone directly registers a
>> + * platform device matching this driver's name, without specifying a
>> + * device tree node.
>> + */
>> + quirks = of_device_get_match_data(dev);
>> +
>> + /* Add controls, widgets, and routes for individual features */
>> +
>> + if (quirks->has_headphone) {
>> + ret = sun8i_codec_add_headphone(cmpnt);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + if (quirks->has_hmic) {
>> + sun8i_codec_add_hmic(cmpnt);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + if (quirks->has_lineout) {
>> + ret = sun8i_codec_add_lineout(cmpnt);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static const struct snd_soc_component_driver sun8i_codec_analog_cmpnt_drv = {
>> + .controls = sun8i_codec_common_controls,
>> + .num_controls = ARRAY_SIZE(sun8i_codec_common_controls),
>> + .dapm_widgets = sun8i_codec_common_widgets,
>> + .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_common_widgets),
>> + .dapm_routes = sun8i_codec_common_routes,
>> + .num_dapm_routes = ARRAY_SIZE(sun8i_codec_common_routes),
>> + .probe = sun8i_codec_analog_cmpnt_probe,
>> +};
>> +
>> +static const struct of_device_id sun8i_codec_analog_of_match[] = {
>> + {
>> + .compatible = "allwinner,sun8i-a23-codec-analog",
>> + .data = &sun8i_a23_quirks,
>> + },
>> + {
>> + .compatible = "allwinner,sun8i-h3-codec-analog",
>> + .data = &sun8i_h3_quirks,
>> + },
>> + {}
>> +};
>> +MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match);
>> +
>> +static int sun8i_codec_analog_probe(struct platform_device *pdev)
>> +{
>> + struct resource *res;
>> + struct regmap *regmap;
>> + void __iomem *base;
>> +
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + base = devm_ioremap_resource(&pdev->dev, res);
>> + if (IS_ERR(base)) {
>> + dev_err(&pdev->dev, "Failed to map the registers\n");
>> + return PTR_ERR(base);
>> + }
>> +
>> + regmap = devm_regmap_init(&pdev->dev, NULL, base, &adda_pr_regmap_cfg);
>> + if (IS_ERR(regmap)) {
>> + dev_err(&pdev->dev, "Failed to create regmap\n");
>> + return PTR_ERR(regmap);
>> + }
>> +
>> + return devm_snd_soc_register_component(&pdev->dev,
>> + &sun8i_codec_analog_cmpnt_drv,
>> + NULL, 0);
>> +}
>> +
>> +static struct platform_driver sun8i_codec_analog_driver = {
>> + .driver = {
>> + .name = "sun8i-codec-analog",
>> + .of_match_table = sun8i_codec_analog_of_match,
>> + },
>> + .probe = sun8i_codec_analog_probe,
>> +};
>> +module_platform_driver(sun8i_codec_analog_driver);
>> +
>> +MODULE_DESCRIPTION("Allwinner internal codec analog controls driver");
>> +MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
>> +MODULE_LICENSE("GPL");
>> +MODULE_ALIAS("platform:sun8i-codec-analog");
>> --
>> 2.10.2
>>
>> _______________________________________________
>> linux-arm-kernel mailing list
>> linux-arm-kernel at lists.infradead.org
>> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* [PATCH 02/10] ASoC: sunxi: Add support for A23/A33/H3 codec's analog path controls
From: Icenowy Zheng @ 2016-11-25  5:43 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161112064648.26779-3-wens@csie.org>



12.11.2016, 14:57, "Chen-Yu Tsai" <wens@csie.org>:
> The internal codec on A23/A33/H3 is split into 2 parts. The
> analog path controls are routed through an embedded custom register
> bus accessed through the PRCM block.
>
> The SoCs share a common set of inputs, outputs, and audio paths.
> The following table lists the differences.
>
> ????----------------------------------------
> ????| Feature \ SoC | A23 | A33 | H3 |
> ????----------------------------------------
> ????| Headphone | v | v | |
> ????----------------------------------------
> ????| Line Out | | | v |
> ????----------------------------------------
> ????| Phone In/Out | v | v | |
> ????----------------------------------------
>
> Add an ASoC component driver for it. This should be tied to the codec
> audio card as an auxiliary device. This patch adds the commont paths
> and controls, and variant specific headphone out and line out.
>
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
> ---
> ?sound/soc/sunxi/Kconfig | 8 +
> ?sound/soc/sunxi/Makefile | 1 +
> ?sound/soc/sunxi/sun8i-codec-analog.c | 665 +++++++++++++++++++++++++++++++++++
> ?3 files changed, 674 insertions(+)
> ?create mode 100644 sound/soc/sunxi/sun8i-codec-analog.c
>
> diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
> index dd2368297fd3..6c344e16aca4 100644
> --- a/sound/soc/sunxi/Kconfig
> +++ b/sound/soc/sunxi/Kconfig
> @@ -9,6 +9,14 @@ config SND_SUN4I_CODEC
> ???????????Select Y or M to add support for the Codec embedded in the Allwinner
> ???????????A10 and affiliated SoCs.
>
> +config SND_SUN8I_CODEC_ANALOG
> + tristate "Allwinner sun8i Codec Analog Controls Support"
> + depends on MACH_SUN8I || COMPILE_TEST

sun50i-a64 has a similar (or the same?) codec to A33.

> + select REGMAP
> + help
> + Say Y or M if you want to add support for the analog controls for
> + the codec embedded in newer Allwinner SoCs.
> +
> ?config SND_SUN4I_I2S
> ?????????tristate "Allwinner A10 I2S Support"
> ?????????select SND_SOC_GENERIC_DMAENGINE_PCM
> diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile
> index 604c7b842837..241c0df9ca0c 100644
> --- a/sound/soc/sunxi/Makefile
> +++ b/sound/soc/sunxi/Makefile
> @@ -1,3 +1,4 @@
> ?obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o
> ?obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o
> ?obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o
> +obj-$(CONFIG_SND_SUN8I_CODEC_ANALOG) += sun8i-codec-analog.o
> diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c
> new file mode 100644
> index 000000000000..222bbd440b1e
> --- /dev/null
> +++ b/sound/soc/sunxi/sun8i-codec-analog.c
> @@ -0,0 +1,665 @@
> +/*
> + * This driver supports the analog controls for the internal codec
> + * found in Allwinner's A31s, A23, A33 and H3 SoCs.
> + *
> + * Copyright 2016 Chen-Yu Tsai <wens@csie.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#include <sound/soc.h>
> +#include <sound/soc-dapm.h>
> +#include <sound/tlv.h>
> +
> +/* Codec analog control register offsets and bit fields */
> +#define SUN8I_ADDA_HP_VOLC 0x00
> +#define SUN8I_ADDA_HP_VOLC_PA_CLK_GATE 7
> +#define SUN8I_ADDA_HP_VOLC_HP_VOL 0
> +#define SUN8I_ADDA_LOMIXSC 0x01
> +#define SUN8I_ADDA_LOMIXSC_MIC1 6
> +#define SUN8I_ADDA_LOMIXSC_MIC2 5
> +#define SUN8I_ADDA_LOMIXSC_PHONE 4
> +#define SUN8I_ADDA_LOMIXSC_PHONEN 3
> +#define SUN8I_ADDA_LOMIXSC_LINEINL 2
> +#define SUN8I_ADDA_LOMIXSC_DACL 1
> +#define SUN8I_ADDA_LOMIXSC_DACR 0
> +#define SUN8I_ADDA_ROMIXSC 0x02
> +#define SUN8I_ADDA_ROMIXSC_MIC1 6
> +#define SUN8I_ADDA_ROMIXSC_MIC2 5
> +#define SUN8I_ADDA_ROMIXSC_PHONE 4
> +#define SUN8I_ADDA_ROMIXSC_PHONEP 3
> +#define SUN8I_ADDA_ROMIXSC_LINEINR 2
> +#define SUN8I_ADDA_ROMIXSC_DACR 1
> +#define SUN8I_ADDA_ROMIXSC_DACL 0
> +#define SUN8I_ADDA_DAC_PA_SRC 0x03
> +#define SUN8I_ADDA_DAC_PA_SRC_DACAREN 7
> +#define SUN8I_ADDA_DAC_PA_SRC_DACALEN 6
> +#define SUN8I_ADDA_DAC_PA_SRC_RMIXEN 5
> +#define SUN8I_ADDA_DAC_PA_SRC_LMIXEN 4
> +#define SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE 3
> +#define SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE 2
> +#define SUN8I_ADDA_DAC_PA_SRC_RHPIS 1
> +#define SUN8I_ADDA_DAC_PA_SRC_LHPIS 0
> +#define SUN8I_ADDA_PHONEIN_GCTRL 0x04
> +#define SUN8I_ADDA_PHONEIN_GCTRL_PHONEPG 4
> +#define SUN8I_ADDA_PHONEIN_GCTRL_PHONENG 0
> +#define SUN8I_ADDA_LINEIN_GCTRL 0x05
> +#define SUN8I_ADDA_LINEIN_GCTRL_LINEING 4
> +#define SUN8I_ADDA_LINEIN_GCTRL_PHONEG 0
> +#define SUN8I_ADDA_MICIN_GCTRL 0x06
> +#define SUN8I_ADDA_MICIN_GCTRL_MIC1G 4
> +#define SUN8I_ADDA_MICIN_GCTRL_MIC2G 0
> +#define SUN8I_ADDA_PAEN_HP_CTRL 0x07
> +#define SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN 7
> +#define SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN 7 /* H3 specific */
> +#define SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC 5
> +#define SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN 4
> +#define SUN8I_ADDA_PAEN_HP_CTRL_PA_ANTI_POP_CTRL 2
> +#define SUN8I_ADDA_PAEN_HP_CTRL_LTRNMUTE 1
> +#define SUN8I_ADDA_PAEN_HP_CTRL_RTLNMUTE 0
> +#define SUN8I_ADDA_PHONEOUT_CTRL 0x08
> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTG 5
> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTEN 4
> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC1 3
> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC2 2
> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_RMIX 1
> +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_LMIX 0
> +#define SUN8I_ADDA_PHONE_GAIN_CTRL 0x09
> +#define SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL 3
> +#define SUN8I_ADDA_PHONE_GAIN_CTRL_PHONEPREG 0
> +#define SUN8I_ADDA_MIC2G_CTRL 0x0a
> +#define SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN 7
> +#define SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST 4
> +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN 3
> +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN 2
> +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC 1
> +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC 0
> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL 0x0b
> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN 7
> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN 6
> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIAS_MODE 5
> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN 3
> +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST 0
> +#define SUN8I_ADDA_LADCMIXSC 0x0c
> +#define SUN8I_ADDA_LADCMIXSC_MIC1 6
> +#define SUN8I_ADDA_LADCMIXSC_MIC2 5
> +#define SUN8I_ADDA_LADCMIXSC_PHONE 4
> +#define SUN8I_ADDA_LADCMIXSC_PHONEN 3
> +#define SUN8I_ADDA_LADCMIXSC_LINEINL 2
> +#define SUN8I_ADDA_LADCMIXSC_OMIXRL 1
> +#define SUN8I_ADDA_LADCMIXSC_OMIXRR 0
> +#define SUN8I_ADDA_RADCMIXSC 0x0d
> +#define SUN8I_ADDA_RADCMIXSC_MIC1 6
> +#define SUN8I_ADDA_RADCMIXSC_MIC2 5
> +#define SUN8I_ADDA_RADCMIXSC_PHONE 4
> +#define SUN8I_ADDA_RADCMIXSC_PHONEP 3
> +#define SUN8I_ADDA_RADCMIXSC_LINEINR 2
> +#define SUN8I_ADDA_RADCMIXSC_OMIXR 1
> +#define SUN8I_ADDA_RADCMIXSC_OMIXL 0
> +#define SUN8I_ADDA_RES 0x0e
> +#define SUN8I_ADDA_RES_MMICBIAS_SEL 4
> +#define SUN8I_ADDA_RES_PA_ANTI_POP_CTRL 0
> +#define SUN8I_ADDA_ADC_AP_EN 0x0f
> +#define SUN8I_ADDA_ADC_AP_EN_ADCREN 7
> +#define SUN8I_ADDA_ADC_AP_EN_ADCLEN 6
> +#define SUN8I_ADDA_ADC_AP_EN_ADCG 0
> +
> +/* Analog control register access bits */
> +#define ADDA_PR 0x0 /* PRCM base + 0x1c0 */
> +#define ADDA_PR_RESET BIT(28)
> +#define ADDA_PR_WRITE BIT(24)
> +#define ADDA_PR_ADDR_SHIFT 16
> +#define ADDA_PR_ADDR_MASK GENMASK(4, 0)
> +#define ADDA_PR_DATA_IN_SHIFT 8
> +#define ADDA_PR_DATA_IN_MASK GENMASK(7, 0)
> +#define ADDA_PR_DATA_OUT_SHIFT 0
> +#define ADDA_PR_DATA_OUT_MASK GENMASK(7, 0)
> +
> +/* regmap access bits */
> +static int adda_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> + void __iomem *base = (void __iomem *)context;
> + u32 tmp;
> +
> + /* De-assert reset */
> + writel(readl(base) | ADDA_PR_RESET, base);
> +
> + /* Clear write bit */
> + writel(readl(base) & ~ADDA_PR_WRITE, base);
> +
> + /* Set register address */
> + tmp = readl(base);
> + tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
> + tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT;
> + writel(tmp, base);
> +
> + /* Read back value */
> + *val = readl(base) & ADDA_PR_DATA_OUT_MASK;
> +
> + return 0;
> +}
> +
> +static int adda_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> + void __iomem *base = (void __iomem *)context;
> + u32 tmp;
> +
> + /* De-assert reset */
> + writel(readl(base) | ADDA_PR_RESET, base);
> +
> + /* Set register address */
> + tmp = readl(base);
> + tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
> + tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT;
> + writel(tmp, base);
> +
> + /* Set data to write */
> + tmp = readl(base);
> + tmp &= ~(ADDA_PR_DATA_IN_MASK << ADDA_PR_DATA_IN_SHIFT);
> + tmp |= (val & ADDA_PR_DATA_IN_MASK) << ADDA_PR_DATA_IN_SHIFT;
> + writel(tmp, base);
> +
> + /* Set write bit to signal a write */
> + writel(readl(base) | ADDA_PR_WRITE, base);
> +
> + /* Clear write bit */
> + writel(readl(base) & ~ADDA_PR_WRITE, base);
> +
> + return 0;
> +}
> +
> +static const struct regmap_config adda_pr_regmap_cfg = {
> + .name = "adda-pr",
> + .reg_bits = 5,
> + .reg_stride = 1,
> + .val_bits = 8,
> + .reg_read = adda_reg_read,
> + .reg_write = adda_reg_write,
> + .fast_io = true,
> + .max_register = 24,
> +};
> +
> +/* mixer controls */
> +static const struct snd_kcontrol_new sun8i_codec_mixer_controls[] = {
> + SOC_DAPM_DOUBLE_R("DAC Playback Switch",
> + SUN8I_ADDA_LOMIXSC,
> + SUN8I_ADDA_ROMIXSC,
> + SUN8I_ADDA_LOMIXSC_DACL, 1, 0),
> + SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch",
> + SUN8I_ADDA_LOMIXSC,
> + SUN8I_ADDA_ROMIXSC,
> + SUN8I_ADDA_LOMIXSC_DACR, 1, 0),
> + SOC_DAPM_DOUBLE_R("Line In Playback Switch",
> + SUN8I_ADDA_LOMIXSC,
> + SUN8I_ADDA_ROMIXSC,
> + SUN8I_ADDA_LOMIXSC_LINEINL, 1, 0),
> + SOC_DAPM_DOUBLE_R("Mic1 Playback Switch",
> + SUN8I_ADDA_LOMIXSC,
> + SUN8I_ADDA_ROMIXSC,
> + SUN8I_ADDA_LOMIXSC_MIC1, 1, 0),
> + SOC_DAPM_DOUBLE_R("Mic2 Playback Switch",
> + SUN8I_ADDA_LOMIXSC,
> + SUN8I_ADDA_ROMIXSC,
> + SUN8I_ADDA_LOMIXSC_MIC2, 1, 0),
> +};
> +
> +/* ADC mixer controls */
> +static const struct snd_kcontrol_new sun8i_codec_adc_mixer_controls[] = {
> + SOC_DAPM_DOUBLE_R("Mixer Capture Switch",
> + SUN8I_ADDA_LADCMIXSC,
> + SUN8I_ADDA_RADCMIXSC,
> + SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0),
> + SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch",
> + SUN8I_ADDA_LADCMIXSC,
> + SUN8I_ADDA_RADCMIXSC,
> + SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0),
> + SOC_DAPM_DOUBLE_R("Line In Capture Switch",
> + SUN8I_ADDA_LADCMIXSC,
> + SUN8I_ADDA_RADCMIXSC,
> + SUN8I_ADDA_LADCMIXSC_LINEINL, 1, 0),
> + SOC_DAPM_DOUBLE_R("Mic1 Capture Switch",
> + SUN8I_ADDA_LADCMIXSC,
> + SUN8I_ADDA_RADCMIXSC,
> + SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0),
> + SOC_DAPM_DOUBLE_R("Mic2 Capture Switch",
> + SUN8I_ADDA_LADCMIXSC,
> + SUN8I_ADDA_RADCMIXSC,
> + SUN8I_ADDA_LADCMIXSC_MIC2, 1, 0),
> +};
> +
> +/* volume / mute controls */
> +static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale,
> + -450, 150, 0);
> +static const DECLARE_TLV_DB_RANGE(sun8i_codec_mic_gain_scale,
> + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
> + 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0),
> +);
> +
> +static const struct snd_kcontrol_new sun8i_codec_common_controls[] = {
> + /* Mixer pre-gains */
> + SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL,
> + SUN8I_ADDA_LINEIN_GCTRL_LINEING,
> + 0x7, 0, sun8i_codec_out_mixer_pregain_scale),
> + SOC_SINGLE_TLV("Mic1 Playback Volume", SUN8I_ADDA_MICIN_GCTRL,
> + SUN8I_ADDA_MICIN_GCTRL_MIC1G,
> + 0x7, 0, sun8i_codec_out_mixer_pregain_scale),
> + SOC_SINGLE_TLV("Mic2 Playback Volume",
> + SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G,
> + 0x7, 0, sun8i_codec_out_mixer_pregain_scale),
> +
> + /* Microphone Amp boost gains */
> + SOC_SINGLE_TLV("Mic1 Boost Volume", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
> + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0,
> + sun8i_codec_mic_gain_scale),
> + SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL,
> + SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0,
> + sun8i_codec_mic_gain_scale),
> +
> + /* ADC */
> + SOC_SINGLE_TLV("ADC Gain Capture Volume", SUN8I_ADDA_ADC_AP_EN,
> + SUN8I_ADDA_ADC_AP_EN_ADCG, 0x7, 0,
> + sun8i_codec_out_mixer_pregain_scale),
> +};
> +
> +static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = {
> + /* ADC */
> + SND_SOC_DAPM_ADC("Left ADC", NULL, SUN8I_ADDA_ADC_AP_EN,
> + SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0),
> + SND_SOC_DAPM_ADC("Right ADC", NULL, SUN8I_ADDA_ADC_AP_EN,
> + SUN8I_ADDA_ADC_AP_EN_ADCREN, 0),
> +
> + /* DAC */
> + SND_SOC_DAPM_DAC("Left DAC", NULL, SUN8I_ADDA_DAC_PA_SRC,
> + SUN8I_ADDA_DAC_PA_SRC_DACALEN, 0),
> + SND_SOC_DAPM_DAC("Right DAC", NULL, SUN8I_ADDA_DAC_PA_SRC,
> + SUN8I_ADDA_DAC_PA_SRC_DACAREN, 0),
> + /*
> + * Due to this component and the codec belonging to separate DAPM
> + * contexts, we need to manually link the above widgets to their
> + * stream widgets at the card level.
> + */
> +
> + /* Line In */
> + SND_SOC_DAPM_INPUT("LINEIN"),
> +
> + /* Microphone inputs */
> + SND_SOC_DAPM_INPUT("MIC1"),
> + SND_SOC_DAPM_INPUT("MIC2"),
> +
> + /* Microphone Bias */
> + SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
> + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN,
> + 0, NULL, 0),
> +
> + /* Mic input path */
> + SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
> + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0),
> + SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL,
> + SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0),
> +
> + /* Mixers */
> + SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC,
> + SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0,
> + sun8i_codec_mixer_controls,
> + ARRAY_SIZE(sun8i_codec_mixer_controls)),
> + SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC,
> + SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0,
> + sun8i_codec_mixer_controls,
> + ARRAY_SIZE(sun8i_codec_mixer_controls)),
> + SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN,
> + SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0,
> + sun8i_codec_adc_mixer_controls,
> + ARRAY_SIZE(sun8i_codec_adc_mixer_controls)),
> + SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN,
> + SUN8I_ADDA_ADC_AP_EN_ADCREN, 0,
> + sun8i_codec_adc_mixer_controls,
> + ARRAY_SIZE(sun8i_codec_adc_mixer_controls)),
> +};
> +
> +static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = {
> + /* Microphone Routes */
> + { "Mic1 Amplifier", NULL, "MIC1"},
> + { "Mic2 Amplifier", NULL, "MIC2"},
> +
> + /* Left Mixer Routes */
> + { "Left Mixer", "DAC Playback Switch", "Left DAC" },
> + { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" },
> + { "Left Mixer", "Line In Playback Switch", "LINEIN" },
> + { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
> + { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
> +
> + /* Right Mixer Routes */
> + { "Right Mixer", "DAC Playback Switch", "Right DAC" },
> + { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" },
> + { "Right Mixer", "Line In Playback Switch", "LINEIN" },
> + { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
> + { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
> +
> + /* Left ADC Mixer Routes */
> + { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" },
> + { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" },
> + { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" },
> + { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
> + { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
> +
> + /* Right ADC Mixer Routes */
> + { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" },
> + { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" },
> + { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" },
> + { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
> + { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
> +
> + /* ADC Routes */
> + { "Left ADC", NULL, "Left ADC Mixer" },
> + { "Right ADC", NULL, "Right ADC Mixer" },
> +};
> +
> +/* headphone specific controls, widgets, and routes */
> +static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1);
> +static const struct snd_kcontrol_new sun8i_codec_headphone_controls[] = {
> + SOC_SINGLE_TLV("Headphone Playback Volume",
> + SUN8I_ADDA_HP_VOLC,
> + SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3f, 0,
> + sun8i_codec_hp_vol_scale),
> + SOC_DOUBLE("Headphone Playback Switch",
> + SUN8I_ADDA_DAC_PA_SRC,
> + SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE,
> + SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0),
> +};
> +
> +static const char * const sun8i_codec_hp_src_enum_text[] = {
> + "DAC", "Mixer",
> +};
> +
> +static SOC_ENUM_DOUBLE_DECL(sun8i_codec_hp_src_enum,
> + SUN8I_ADDA_DAC_PA_SRC,
> + SUN8I_ADDA_DAC_PA_SRC_LHPIS,
> + SUN8I_ADDA_DAC_PA_SRC_RHPIS,
> + sun8i_codec_hp_src_enum_text);
> +
> +static const struct snd_kcontrol_new sun8i_codec_hp_src[] = {
> + SOC_DAPM_ENUM("Headphone Source Playback Route",
> + sun8i_codec_hp_src_enum),
> +};
> +
> +static const struct snd_soc_dapm_widget sun8i_codec_headphone_widgets[] = {
> + SND_SOC_DAPM_MUX("Headphone Source Playback Route",
> + SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src),
> + SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL,
> + SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0),
> + SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN8I_ADDA_PAEN_HP_CTRL,
> + SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN, 0, NULL, 0),
> + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN8I_ADDA_PAEN_HP_CTRL,
> + SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC, 0x3, 0x3, 0),
> + SND_SOC_DAPM_OUTPUT("HP"),
> +};
> +
> +static const struct snd_soc_dapm_route sun8i_codec_headphone_routes[] = {
> + { "Headphone Source Playback Route", "DAC", "Left DAC" },
> + { "Headphone Source Playback Route", "DAC", "Right DAC" },
> + { "Headphone Source Playback Route", "Mixer", "Left Mixer" },
> + { "Headphone Source Playback Route", "Mixer", "Right Mixer" },
> + { "Headphone Amp", NULL, "Headphone Source Playback Route" },
> + { "HPCOM", NULL, "HPCOM Protection" },
> + { "HP", NULL, "Headphone Amp" },
> +};
> +
> +static int sun8i_codec_add_headphone(struct snd_soc_component *cmpnt)
> +{
> + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
> + struct device *dev = cmpnt->dev;
> + int ret;
> +
> + ret = snd_soc_add_component_controls(cmpnt,
> + sun8i_codec_headphone_controls,
> + ARRAY_SIZE(sun8i_codec_headphone_controls));
> + if (ret) {
> + dev_err(dev, "Failed to add Headphone controls: %d\n", ret);
> + return ret;
> + }
> +
> + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_headphone_widgets,
> + ARRAY_SIZE(sun8i_codec_headphone_widgets));
> + if (ret) {
> + dev_err(dev, "Failed to add Headphone DAPM widgets: %d\n", ret);
> + return ret;
> + }
> +
> + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_headphone_routes,
> + ARRAY_SIZE(sun8i_codec_headphone_routes));
> + if (ret) {
> + dev_err(dev, "Failed to add Headphone DAPM routes: %d\n", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/* hmic specific widget */
> +static const struct snd_soc_dapm_widget sun8i_codec_hmic_widgets[] = {
> + SND_SOC_DAPM_SUPPLY("HBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
> + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN,
> + 0, NULL, 0),
> +};
> +
> +static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt)
> +{
> + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
> + struct device *dev = cmpnt->dev;
> + int ret;
> +
> + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_hmic_widgets,
> + ARRAY_SIZE(sun8i_codec_hmic_widgets));
> + if (ret)
> + dev_err(dev, "Failed to add Mic3 DAPM widgets: %d\n", ret);
> +
> + return ret;
> +}
> +
> +/* line out specific controls, widgets and routes */
> +static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale,
> + 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1),
> + 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0),
> +);
> +static const struct snd_kcontrol_new sun8i_codec_lineout_controls[] = {
> + SOC_SINGLE_TLV("Line Out Playback Volume",
> + SUN8I_ADDA_PHONE_GAIN_CTRL,
> + SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL, 0x1f, 0,
> + sun8i_codec_lineout_vol_scale),
> + SOC_DOUBLE("Line Out Playback Switch",
> + SUN8I_ADDA_MIC2G_CTRL,
> + SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN,
> + SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN, 1, 0),
> +};
> +
> +static const char * const sun8i_codec_lineout_src_enum_text[] = {
> + "Stereo", "Mono Differential",
> +};
> +
> +static SOC_ENUM_DOUBLE_DECL(sun8i_codec_lineout_src_enum,
> + SUN8I_ADDA_MIC2G_CTRL,
> + SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC,
> + SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC,
> + sun8i_codec_lineout_src_enum_text);
> +
> +static const struct snd_kcontrol_new sun8i_codec_lineout_src[] = {
> + SOC_DAPM_ENUM("Line Out Source Playback Route",
> + sun8i_codec_lineout_src_enum),
> +};
> +
> +static const struct snd_soc_dapm_widget sun8i_codec_lineout_widgets[] = {
> + SND_SOC_DAPM_MUX("Line Out Source Playback Route",
> + SND_SOC_NOPM, 0, 0, sun8i_codec_lineout_src),
> + /* It is unclear if this is a buffer or gate, model it as a supply */
> + SND_SOC_DAPM_SUPPLY("Line Out Enable", SUN8I_ADDA_PAEN_HP_CTRL,
> + SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN, 0, NULL, 0),
> + SND_SOC_DAPM_OUTPUT("LINEOUT"),
> +};
> +
> +static const struct snd_soc_dapm_route sun8i_codec_lineout_routes[] = {
> + { "Line Out Source Playback Route", "Stereo", "Left Mixer" },
> + { "Line Out Source Playback Route", "Stereo", "Right Mixer" },
> + { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" },
> + { "Line Out Source Playback Route", "Mono Differential", "Right Mixer" },
> + { "LINEOUT", NULL, "Line Out Source Playback Route" },
> + { "LINEOUT", NULL, "Line Out Enable", },
> +};
> +
> +static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt)
> +{
> + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
> + struct device *dev = cmpnt->dev;
> + int ret;
> +
> + ret = snd_soc_add_component_controls(cmpnt,
> + sun8i_codec_lineout_controls,
> + ARRAY_SIZE(sun8i_codec_lineout_controls));
> + if (ret) {
> + dev_err(dev, "Failed to add Line Out controls: %d\n", ret);
> + return ret;
> + }
> +
> + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_lineout_widgets,
> + ARRAY_SIZE(sun8i_codec_lineout_widgets));
> + if (ret) {
> + dev_err(dev, "Failed to add Line Out DAPM widgets: %d\n", ret);
> + return ret;
> + }
> +
> + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_lineout_routes,
> + ARRAY_SIZE(sun8i_codec_lineout_routes));
> + if (ret) {
> + dev_err(dev, "Failed to add Line Out DAPM routes: %d\n", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +struct sun8i_codec_analog_quirks {
> + bool has_headphone;
> + bool has_hmic;
> + bool has_lineout;
> +};
> +
> +static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = {
> + .has_headphone = true,
> + .has_hmic = true,
> +};
> +
> +static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = {
> + .has_lineout = true,
> +};
> +
> +static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt)
> +{
> + struct device *dev = cmpnt->dev;
> + const struct sun8i_codec_analog_quirks *quirks;
> + int ret;
> +
> + /*
> + * This would never return NULL unless someone directly registers a
> + * platform device matching this driver's name, without specifying a
> + * device tree node.
> + */
> + quirks = of_device_get_match_data(dev);
> +
> + /* Add controls, widgets, and routes for individual features */
> +
> + if (quirks->has_headphone) {
> + ret = sun8i_codec_add_headphone(cmpnt);
> + if (ret)
> + return ret;
> + }
> +
> + if (quirks->has_hmic) {
> + sun8i_codec_add_hmic(cmpnt);
> + if (ret)
> + return ret;
> + }
> +
> + if (quirks->has_lineout) {
> + ret = sun8i_codec_add_lineout(cmpnt);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const struct snd_soc_component_driver sun8i_codec_analog_cmpnt_drv = {
> + .controls = sun8i_codec_common_controls,
> + .num_controls = ARRAY_SIZE(sun8i_codec_common_controls),
> + .dapm_widgets = sun8i_codec_common_widgets,
> + .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_common_widgets),
> + .dapm_routes = sun8i_codec_common_routes,
> + .num_dapm_routes = ARRAY_SIZE(sun8i_codec_common_routes),
> + .probe = sun8i_codec_analog_cmpnt_probe,
> +};
> +
> +static const struct of_device_id sun8i_codec_analog_of_match[] = {
> + {
> + .compatible = "allwinner,sun8i-a23-codec-analog",
> + .data = &sun8i_a23_quirks,
> + },
> + {
> + .compatible = "allwinner,sun8i-h3-codec-analog",
> + .data = &sun8i_h3_quirks,
> + },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match);
> +
> +static int sun8i_codec_analog_probe(struct platform_device *pdev)
> +{
> + struct resource *res;
> + struct regmap *regmap;
> + void __iomem *base;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(base)) {
> + dev_err(&pdev->dev, "Failed to map the registers\n");
> + return PTR_ERR(base);
> + }
> +
> + regmap = devm_regmap_init(&pdev->dev, NULL, base, &adda_pr_regmap_cfg);
> + if (IS_ERR(regmap)) {
> + dev_err(&pdev->dev, "Failed to create regmap\n");
> + return PTR_ERR(regmap);
> + }
> +
> + return devm_snd_soc_register_component(&pdev->dev,
> + &sun8i_codec_analog_cmpnt_drv,
> + NULL, 0);
> +}
> +
> +static struct platform_driver sun8i_codec_analog_driver = {
> + .driver = {
> + .name = "sun8i-codec-analog",
> + .of_match_table = sun8i_codec_analog_of_match,
> + },
> + .probe = sun8i_codec_analog_probe,
> +};
> +module_platform_driver(sun8i_codec_analog_driver);
> +
> +MODULE_DESCRIPTION("Allwinner internal codec analog controls driver");
> +MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:sun8i-codec-analog");
> --
> 2.10.2
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* [linux-sunxi] [PATCH v6 3/5] ARM: dts: sun8i-h3: add HDMI video nodes
From: Icenowy Zheng @ 2016-11-25  5:27 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <cdf50b8433e2d9a053e35f8788bfcd5d41504312.1479641523.git.moinejf@free.fr>



20.11.2016, 20:07, "Jean-Francois Moine" <moinejf@free.fr>:
> Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
> ---
> ?arch/arm/boot/dts/sun8i-h3.dtsi | 51 +++++++++++++++++++++++++++++++++++++++++
> ?1 file changed, 51 insertions(+)
>
> diff --git a/arch/arm/boot/dts/sun8i-h3.dtsi b/arch/arm/boot/dts/sun8i-h3.dtsi
> index 416b825..7c6b1d5 100644
> --- a/arch/arm/boot/dts/sun8i-h3.dtsi
> +++ b/arch/arm/boot/dts/sun8i-h3.dtsi
> @@ -140,6 +140,16 @@
> ?????????????????#size-cells = <1>;
> ?????????????????ranges;
>
> + de: de-controller at 01000000 {
> + compatible = "allwinner,sun8i-h3-display-engine";
> + reg = <0x01000000 0x400000>;
> + clocks = <&ccu CLK_BUS_DE>, <&ccu CLK_DE>;
> + clock-names = "bus", "clock";
> + resets = <&ccu RST_BUS_DE>;
> + ports = <&lcd0_p>;
> + status = "disabled";
> + };
> +
> ?????????????????dma: dma-controller at 01c02000 {
> ?????????????????????????compatible = "allwinner,sun8i-h3-dma";
> ?????????????????????????reg = <0x01c02000 0x1000>;
> @@ -149,6 +159,23 @@
> ?????????????????????????#dma-cells = <1>;
> ?????????????????};
>
> + lcd0: lcd-controller at 01c0c000 {
> + compatible = "allwinner,sun8i-a83t-tcon";
> + reg = <0x01c0c000 0x400>;
> + clocks = <&ccu CLK_BUS_TCON0>, <&ccu CLK_TCON0>;
> + clock-names = "bus", "clock";
> + resets = <&ccu RST_BUS_TCON0>;
> + interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;
> + status = "disabled";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + lcd0_p: port {
> + lcd0_hdmi: endpoint {
> + remote-endpoint = <&hdmi_lcd0>;
> + };
> + };
> + };
> +
> ?????????????????mmc0: mmc at 01c0f000 {
> ?????????????????????????compatible = "allwinner,sun7i-a20-mmc";
> ?????????????????????????reg = <0x01c0f000 0x1000>;
> @@ -314,6 +341,11 @@
> ?????????????????????????clock-names = "hosc", "losc";
> ?????????????????????????#clock-cells = <1>;
> ?????????????????????????#reset-cells = <1>;
> +
> + assigned-clocks = <&ccu CLK_PLL_DE>,

Cannot get the patch built on 4.9-rc, as CLK_PLL_DE is not an exported clock.

Only CLK_DE is exported.

> + <&ccu CLK_DE>;
> + assigned-clock-rates = <864000000>,
> + <432000000>;
> ?????????????????};
>
> ?????????????????pio: pinctrl at 01c20800 {
> @@ -564,6 +596,25 @@
> ?????????????????????????interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
> ?????????????????};
>
> + hdmi: hdmi at 01ee0000 {
> + compatible = "allwinner,sun8i-h3-hdmi";
> + reg = <0x01ee0000 0x20000>;
> + clocks = <&ccu CLK_BUS_HDMI>, <&ccu CLK_HDMI>,
> + <&ccu CLK_HDMI_DDC>;
> + clock-names = "bus", "clock", "ddc-clock";
> + resets = <&ccu RST_BUS_HDMI0>, <&ccu RST_BUS_HDMI1>;
> + reset-names = "hdmi0", "hdmi1";
> + status = "disabled";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + port at 0 { /* video */
> + reg = <0>;
> + hdmi_lcd0: endpoint {
> + remote-endpoint = <&lcd0_hdmi>;
> + };
> + };
> + };
> +
> ?????????????????rtc: rtc at 01f00000 {
> ?????????????????????????compatible = "allwinner,sun6i-a31-rtc";
> ?????????????????????????reg = <0x01f00000 0x54>;
> --
> 2.10.2
>
> --
> You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe at googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply

* [PATCH V8 2/6] thermal: bcm2835: add thermal driver for bcm2835 soc
From: Eduardo Valentin @ 2016-11-25  5:20 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <28F93ABE-8210-4389-AE77-4D5E830E669B@martin.sperl.org>

Hello,

On Tue, Nov 22, 2016 at 03:28:04PM +0100, Martin Sperl wrote:
> Hi Eduardo!
> 
> On 19.11.2016 05:22, Eduardo Valentin wrote:
> > Hello Martin,

<cut>

> 
> I was asked to implement the "initialize" case just in case FW ever
> stopped setting up the device itself, so that is why this code is
> included.

OK. Looks like we (like, we in the Linux side) do not understand the
conditions that firmware fails to initialize the thermal device, but we
still want to force an initialization, hoping to get the device in a sane
state, even if we do not know if the firmware is correctly booted or
not. And that is done silently, with no notification to user. I see.

> 
> > 
> > Who has the ownership of this device?
> 
> Joined ownership I suppose...
> 

with no synchronization mechanism?

> > 
> >> The above mentioned ?configuration if not running? reflect the values that
> >> the FW is currently setting. We should not change those values as long as the
> >> Firmware is also reading the temperature on its own.
> > 
> > hmm.. that looks like racy to me. Again, How do you synchronize accesses to
> > this device? What if you configure the device and right after the
> > firmware updates the configs? How do you make sure the configs you are
> > writing here are the same used by the firmware? What if the firmware
> > version changes? What versions of the firmware does this driver support?
> > 
> > Would it make sense to simply always initialize the device? Do you have
> > a way to tell the firmware that it should not use the device?
> > 
> > Or, if you want to keep the device driver simply being a dummy reader,
> > would it make sense to simply avoid writing configurations to the
> > device, and simply retry to check if the firmware gets the device
> > initialized?
> 
> Again: the device registers are only ever written if the device is not started
> already. Otherwise the driver only reads for the ADC register, so there
> is no real race here.
> 

and no race?

To me, there is a race when you write to the config of this device,
given that there is no sync between the two. We do not know if the
firmware would be still attempting to initialize the device or not, do
we? 

> > 
> >> 
> >>> 
> >>>> So do you need another version of the patchset that uses that new API?
> >>> 
> >>> I think the API usage is change that can be done together with
> >>> clarification for the above questions too: on hardware state,
> >>> firmware loading, maybe a master driver dependency, and the ADC
> >>> conversion sequence, which are not well clear to me on this driver. As long as
> >>> this is clarified and documented in the code (can be simple comments so
> >>> it is clear to whoever reads in the future), then I would be OK with
> >>> this driver.
> >> 
> >> So how do you want this to get ?documented? in the driver?
> >> The setup and Firmware is a generic feature of the SOC, so if we would put
> >> some clarifications in this driver, then we would need to put it in every
> >> bcm283X driver (which seems unreasonable).
> >> 
> > 
> > I think a simple comment explaining the firmware dependency and the
> > expected pre-conditions to get this driver working in a sane state would
> > do it.
> > 
> > A better device initialization would also be appreciated. Based on my
> > limited understanding of this platform, and your explanations, this
> > device seams to have a serious race condition with firmware while
> > accessing this device.
> 
> Again: the firmware runs before the ARM is started and for all practical
> purposes the firmware is (as of now) configuring the thermal device.
> 

Oh, OK, we know the firmware wont touch the thermal device, right?

Then, If that is the case, what is still not clear is in which conditions
the firmware would leave the thermal device uninitialized.

This driver seams to be only concerned of critical temperature. The
concern I see with this code, and the explanations I heard, is a side
effect of seeing spurious thermal shutdown, or even worse, getting the
device booted hot and never shutting down, due to (possible) race
between the two systems.

Besides, if the firmware would not touch the thermal device when ARM is
running, why not always writing to the device a configuration that you
know is your sane starting point? Just do not rely on what was
previously set. How do you know it is a sane configuration? Have you
done sensor calibration exercise to confirm temperature reads are
correct?

> As for the use of thermal_zone_get_offset/slope: looking into the code
> it looks like this actually blows up the code, as we now would need to
> allocate thermal_zone_params and preset it with the ?correct? values.
> 
> So more code to maintain and more memory consumed.
> The only advantage I would see is that it would allow to set offset and
> slope directly in the device tree.
> 

Any specific reason not to use of-thermal, then?


> Thanks,
> 		Martin

BR,

Eduardo Valentin

^ permalink raw reply

* Tearing down DMA transfer setup after DMA client has finished
From: Vinod Koul @ 2016-11-25  4:55 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <58356EA8.2010806@free.fr>

On Wed, Nov 23, 2016 at 11:25:44AM +0100, Mason wrote:
> Hello,
> 
> On my platform, setting up a DMA transfer is a two-step process:
> 
> 1) configure the "switch box" to connect a device to a memory channel
> 2) configure the transfer details (address, size, command)
> 
> When the transfer is done, the sbox setup can be torn down,
> and the DMA driver can start another transfer.
> 
> The current software architecture for my NFC (NAND Flash controller)
> driver is as follows (for one DMA transfer).
> 
>   sg_init_one
>   dma_map_sg
>   dmaengine_prep_slave_sg
>   dmaengine_submit
>   dma_async_issue_pending
>   configure_NFC_transfer
>   wait_for_IRQ_from_DMA_engine // via DMA_PREP_INTERRUPT
>   wait_for_NFC_idle
>   dma_unmap_sg

Looking at thread and discussion now, first thinking would be to ensure
the transaction is completed properly and then isr fired. You may need
to talk to your HW designers to find a way for that. It is quite common
that DMA controllers will fire and complete whereas the transaction is
still in flight.

If that is not doable, then since you claim this is custom part which
other vendors wont use (hope we are wrong down the line), then we can
have a custom api,

foo_sbox_configure(bool enable, ...);

This can be invoked from NFC driver when required for configuration and
teardown. For very specific cases where people need some specific
configuration we do allow custom APIs.

Only problem with that would be it wont be a generic solution and you
seem to be fine with that.


-- 
~Vinod

^ permalink raw reply

* [PATCH] Media: Platform: Exynos-gsc: - Fix for error handling
From: Shailendra Verma @ 2016-11-25  4:41 UTC (permalink / raw)
  To: linux-arm-kernel

The File handle is not yet added in the vfd list.So no need to call
v4l2_fh_del(&ctx->fh) if it fails to create control.

Signed-off-by: Shailendra Verma <shailendra.v@samsung.com>
---
 drivers/media/platform/exynos-gsc/gsc-m2m.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/media/platform/exynos-gsc/gsc-m2m.c b/drivers/media/platform/exynos-gsc/gsc-m2m.c
index 9f03b79..5ea97c1 100644
--- a/drivers/media/platform/exynos-gsc/gsc-m2m.c
+++ b/drivers/media/platform/exynos-gsc/gsc-m2m.c
@@ -664,8 +664,8 @@ static int gsc_m2m_open(struct file *file)
 
 error_ctrls:
 	gsc_ctrls_delete(ctx);
-error_fh:
 	v4l2_fh_del(&ctx->fh);
+error_fh:
 	v4l2_fh_exit(&ctx->fh);
 	kfree(ctx);
 unlock:
-- 
1.7.9.5

^ permalink raw reply related

* [PATCH] Media: Platform: Exynos4-is: - Fix for error handling
From: Shailendra Verma @ 2016-11-25  4:39 UTC (permalink / raw)
  To: linux-arm-kernel

The File handle is not yet added in the vfd list.So no need to call 
v4l2_fh_del(&ctx->fh) if it fails to create control.

Signed-off-by: Shailendra Verma <shailendra.v@samsung.com>
---
 drivers/media/platform/exynos4-is/fimc-m2m.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/media/platform/exynos4-is/fimc-m2m.c b/drivers/media/platform/exynos4-is/fimc-m2m.c
index 6028e4f..d8724fe 100644
--- a/drivers/media/platform/exynos4-is/fimc-m2m.c
+++ b/drivers/media/platform/exynos4-is/fimc-m2m.c
@@ -663,8 +663,8 @@ static int fimc_m2m_open(struct file *file)
 	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
 error_c:
 	fimc_ctrls_delete(ctx);
-error_fh:
 	v4l2_fh_del(&ctx->fh);
+error_fh:
 	v4l2_fh_exit(&ctx->fh);
 	kfree(ctx);
 unlock:
-- 
1.7.9.5

^ permalink raw reply related

* [PATCH 2/2] dt-bindings: Add DT bindings info for FlexRM mailbox driver
From: Anup Patel @ 2016-11-25  4:35 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1480048551-3285-1-git-send-email-anup.patel@broadcom.com>

This patch adds device tree bindings document for the FlexRM
mailbox driver.

Reviewed-by: Ray Jui <ray.jui@broadcom.com>
Reviewed-by: Scott Branden <scott.branden@broadcom.com>
Signed-off-by: Anup Patel <anup.patel@broadcom.com>
---
 .../bindings/mailbox/brcm,flexrm-mbox.txt          | 60 ++++++++++++++++++++++
 1 file changed, 60 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mailbox/brcm,flexrm-mbox.txt

diff --git a/Documentation/devicetree/bindings/mailbox/brcm,flexrm-mbox.txt b/Documentation/devicetree/bindings/mailbox/brcm,flexrm-mbox.txt
new file mode 100644
index 0000000..7969b1c
--- /dev/null
+++ b/Documentation/devicetree/bindings/mailbox/brcm,flexrm-mbox.txt
@@ -0,0 +1,60 @@
+Broadcom FlexRM Mailbox Driver
+===============================
+The Broadcom FlexRM ring manager provides a set of rings which can be
+used to submit work to offload engines. An SoC may have multiple FlexRM
+hardware blocks. There is one device tree entry per block. The FlexRM
+mailbox drivers creates a mailbox instance using FlexRM rings where
+each mailbox channel is a separate FlexRM ring.
+
+Required properties:
+--------------------
+- compatible:	Should be "brcm,flexrm-mbox"
+- reg:		Specifies base physical address and size of the FlexRM
+		ring registers
+- msi-parent:	Phandles (and potential Device IDs) to MSI controllers
+		The FlexRM engine will send MSIs (instead of wired
+		interrupts) to CPU. There is one MSI for each FlexRM ring.
+		Refer devicetree/bindings/interrupt-controller/msi.txt
+- #mbox-cells:	Specifies the number of cells needed to encode a mailbox
+		channel. This should be 3.
+
+		The 1st cell is the mailbox channel number.
+
+		The 2nd cell contains MSI completion threshold. This is the
+		number of completion messages for which FlexRM will inject
+		one MSI interrupt to CPU.
+
+		The 3nd cell contains MSI timer value representing time for
+		which FlexRM will wait to accumulate N completion messages
+		where N is the value specified by 2nd cell above. If FlexRM
+		does not get required number of completion messages in time
+		specified by this cell then it will inject one MSI interrupt
+		to CPU provided atleast one completion message is available.
+
+Optional properties:
+--------------------
+- dma-coherent:	Present if DMA operations made by the FlexRM engine (such
+		as DMA descriptor access, access to buffers pointed by DMA
+		descriptors and read/write pointer updates to DDR) are
+		cache coherent with the CPU.
+
+Example:
+--------
+crypto_mbox: mbox at 67000000 {
+	compatible = "brcm,flexrm-mbox";
+	reg = <0x67000000 0x200000>;
+	msi-parent = <&gic_its 0x7f00>;
+	#mbox-cells = <3>;
+};
+
+crypto_client {
+	...
+	mboxes = <&crypto_mbox 0 0x1 0xffff>,
+		 <&crypto_mbox 1 0x1 0xffff>,
+		 <&crypto_mbox 16 0x1 0xffff>,
+		 <&crypto_mbox 17 0x1 0xffff>,
+		 <&crypto_mbox 30 0x1 0xffff>,
+		 <&crypto_mbox 31 0x1 0xffff>;
+	};
+	...
+};
-- 
2.7.4

^ permalink raw reply related

* [PATCH 1/2] mailbox: Add driver for Broadcom FlexRM ring manager
From: Anup Patel @ 2016-11-25  4:35 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1480048551-3285-1-git-send-email-anup.patel@broadcom.com>

Some of the Broadcom iProc SoCs have FlexRM ring manager
which provides a ring-based programming interface to various
offload engines (e.g. RAID, Crypto, etc).

This patch adds a common mailbox driver for Broadcom FlexRM
ring manager which can be shared by various offload engine
drivers (implemented as mailbox clients).

Reviewed-by: Ray Jui <ray.jui@broadcom.com>
Reviewed-by: Scott Branden <scott.branden@broadcom.com>
Reviewed-by: Pramod KUMAR <pramod.kumar@broadcom.com>
Signed-off-by: Anup Patel <anup.patel@broadcom.com>
---
 drivers/mailbox/Kconfig                      |  11 +
 drivers/mailbox/Makefile                     |   2 +
 drivers/mailbox/mailbox-flexrm/Makefile      |   6 +
 drivers/mailbox/mailbox-flexrm/flexrm-desc.c | 764 ++++++++++++++++++++++++
 drivers/mailbox/mailbox-flexrm/flexrm-desc.h |  47 ++
 drivers/mailbox/mailbox-flexrm/flexrm-main.c | 829 +++++++++++++++++++++++++++
 include/linux/mailbox/brcm-message.h         |  12 +-
 7 files changed, 1667 insertions(+), 4 deletions(-)
 create mode 100644 drivers/mailbox/mailbox-flexrm/Makefile
 create mode 100644 drivers/mailbox/mailbox-flexrm/flexrm-desc.c
 create mode 100644 drivers/mailbox/mailbox-flexrm/flexrm-desc.h
 create mode 100644 drivers/mailbox/mailbox-flexrm/flexrm-main.c

diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index 11eebfe..bfeced1 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -143,4 +143,15 @@ config BCM_PDC_MBOX
 	  Mailbox implementation for the Broadcom PDC ring manager,
 	  which provides access to various offload engines on Broadcom
 	  SoCs. Say Y here if you want to use the Broadcom PDC.
+
+config BCM_FLEXRM_MBOX
+	tristate "Broadcom FlexRM Mailbox"
+	depends on ARM64 || COMPILE_TEST
+	depends on HAS_DMA
+	select GENERIC_MSI_IRQ_DOMAIN
+	default ARCH_BCM_IPROC
+	help
+	  Mailbox implementation of the Broadcom FlexRM ring manager,
+	  which provides access to various offload engines on Broadcom
+	  SoCs. Say Y here if you want to use the Broadcom FlexRM.
 endif
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index ace6fed..9594266 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -29,3 +29,5 @@ obj-$(CONFIG_XGENE_SLIMPRO_MBOX) += mailbox-xgene-slimpro.o
 obj-$(CONFIG_HI6220_MBOX)	+= hi6220-mailbox.o
 
 obj-$(CONFIG_BCM_PDC_MBOX)	+= bcm-pdc-mailbox.o
+
+obj-$(CONFIG_BCM_FLEXRM_MBOX)	+= mailbox-flexrm/
diff --git a/drivers/mailbox/mailbox-flexrm/Makefile b/drivers/mailbox/mailbox-flexrm/Makefile
new file mode 100644
index 0000000..f5bf069
--- /dev/null
+++ b/drivers/mailbox/mailbox-flexrm/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for Broadcom FlexRM Mailbox Driver.
+#
+
+flexrm-mbox-objs := flexrm-main.o flexrm-desc.o
+obj-$(CONFIG_BCM_FLEXRM_MBOX) += flexrm-mbox.o
diff --git a/drivers/mailbox/mailbox-flexrm/flexrm-desc.c b/drivers/mailbox/mailbox-flexrm/flexrm-desc.c
new file mode 100644
index 0000000..9432ec7
--- /dev/null
+++ b/drivers/mailbox/mailbox-flexrm/flexrm-desc.c
@@ -0,0 +1,764 @@
+/* Broadcom FlexRM Mailbox Driver
+ *
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * FlexRM descriptor library
+ */
+
+#include <asm/barrier.h>
+#include <asm/byteorder.h>
+#include <linux/dma-mapping.h>
+#include <linux/printk.h>
+
+#include "flexrm-desc.h"
+
+/* Completion descriptor format */
+#define CMPL_OPAQUE_SHIFT			0
+#define CMPL_OPAQUE_MASK			0xffff
+#define CMPL_ENGINE_STATUS_SHIFT		16
+#define CMPL_ENGINE_STATUS_MASK			0xffff
+#define CMPL_DME_STATUS_SHIFT			32
+#define CMPL_DME_STATUS_MASK			0xffff
+#define CMPL_RM_STATUS_SHIFT			48
+#define CMPL_RM_STATUS_MASK			0xffff
+
+/* Completion DME status code */
+#define DME_STATUS_MEM_COR_ERR			BIT(0)
+#define DME_STATUS_MEM_UCOR_ERR			BIT(1)
+#define DME_STATUS_FIFO_UNDERFLOW		BIT(2)
+#define DME_STATUS_FIFO_OVERFLOW		BIT(3)
+#define DME_STATUS_RRESP_ERR			BIT(4)
+#define DME_STATUS_BRESP_ERR			BIT(5)
+#define DME_STATUS_ERROR_MASK			(DME_STATUS_MEM_COR_ERR | \
+						 DME_STATUS_MEM_UCOR_ERR | \
+						 DME_STATUS_FIFO_UNDERFLOW | \
+						 DME_STATUS_FIFO_OVERFLOW | \
+						 DME_STATUS_RRESP_ERR | \
+						 DME_STATUS_BRESP_ERR)
+
+/* Completion RM status code */
+#define RM_STATUS_CODE_SHIFT			0
+#define RM_STATUS_CODE_MASK			0x3ff
+#define RM_STATUS_CODE_GOOD			0x0
+#define RM_STATUS_CODE_AE_TIMEOUT		0x3ff
+
+/* General descriptor format */
+#define DESC_TYPE_SHIFT				60
+#define DESC_TYPE_MASK				0xf
+#define DESC_PAYLOAD_SHIFT			0
+#define DESC_PAYLOAD_MASK			0x0fffffffffffffff
+
+/* Null descriptor format  */
+#define NULL_TYPE				0
+#define NULL_TOGGLE_SHIFT			58
+#define NULL_TOGGLE_MASK			0x1
+
+/* Header descriptor format */
+#define HEADER_TYPE				1
+#define HEADER_TOGGLE_SHIFT			58
+#define HEADER_TOGGLE_MASK			0x1
+#define HEADER_ENDPKT_SHIFT			57
+#define HEADER_ENDPKT_MASK			0x1
+#define HEADER_STARTPKT_SHIFT			56
+#define HEADER_STARTPKT_MASK			0x1
+#define HEADER_BDCOUNT_SHIFT			36
+#define HEADER_BDCOUNT_MASK			0x1f
+#define HEADER_BDCOUNT_MAX			HEADER_BDCOUNT_MASK
+#define HEADER_FLAGS_SHIFT			16
+#define HEADER_FLAGS_MASK			0xffff
+#define HEADER_OPAQUE_SHIFT			0
+#define HEADER_OPAQUE_MASK			0xffff
+
+/* Source (SRC) descriptor format */
+#define SRC_TYPE				2
+#define SRC_LENGTH_SHIFT			44
+#define SRC_LENGTH_MASK				0xffff
+#define SRC_ADDR_SHIFT				0
+#define SRC_ADDR_MASK				0x00000fffffffffff
+
+/* Destination (DST) descriptor format */
+#define DST_TYPE				3
+#define DST_LENGTH_SHIFT			44
+#define DST_LENGTH_MASK				0xffff
+#define DST_ADDR_SHIFT				0
+#define DST_ADDR_MASK				0x00000fffffffffff
+
+/* Immediate (IMM) descriptor format */
+#define IMM_TYPE				4
+#define IMM_DATA_SHIFT				0
+#define IMM_DATA_MASK				0x0fffffffffffffff
+
+/* Next pointer (NPTR) descriptor format */
+#define NPTR_TYPE				5
+#define NPTR_TOGGLE_SHIFT			58
+#define NPTR_TOGGLE_MASK			0x1
+#define NPTR_ADDR_SHIFT				0
+#define NPTR_ADDR_MASK				0x00000fffffffffff
+
+/* Mega source (MSRC) descriptor format */
+#define MSRC_TYPE				6
+#define MSRC_LENGTH_SHIFT			44
+#define MSRC_LENGTH_MASK			0xffff
+#define MSRC_ADDR_SHIFT				0
+#define MSRC_ADDR_MASK				0x00000fffffffffff
+
+/* Mega destination (MDST) descriptor format */
+#define MDST_TYPE				7
+#define MDST_LENGTH_SHIFT			44
+#define MDST_LENGTH_MASK			0xffff
+#define MDST_ADDR_SHIFT				0
+#define MDST_ADDR_MASK				0x00000fffffffffff
+
+/* Source with tlast (SRCT) descriptor format */
+#define SRCT_TYPE				8
+#define SRCT_LENGTH_SHIFT			44
+#define SRCT_LENGTH_MASK			0xffff
+#define SRCT_ADDR_SHIFT				0
+#define SRCT_ADDR_MASK				0x00000fffffffffff
+
+/* Destination with tlast (DSTT) descriptor format */
+#define DSTT_TYPE				9
+#define DSTT_LENGTH_SHIFT			44
+#define DSTT_LENGTH_MASK			0xffff
+#define DSTT_ADDR_SHIFT				0
+#define DSTT_ADDR_MASK				0x00000fffffffffff
+
+/* Immediate with tlast (IMMT) descriptor format */
+#define IMMT_TYPE				10
+#define IMMT_DATA_SHIFT				0
+#define IMMT_DATA_MASK				0x0fffffffffffffff
+
+/* Descriptor helper macros */
+#define DESC_DEC(_d, _s, _m)			(((_d) >> (_s)) & (_m))
+#define DESC_ENC(_d, _v, _s, _m)		\
+			do { \
+				(_d) &= ~((u64)(_m) << (_s)); \
+				(_d) |= (((u64)(_v) & (_m)) << (_s)); \
+			} while (0)
+
+u64 flexrm_read_desc(void *desc_ptr)
+{
+	return le64_to_cpu(*((u64 *)desc_ptr));
+}
+
+void flexrm_write_desc(void *desc_ptr, u64 desc)
+{
+	*((u64 *)desc_ptr) = cpu_to_le64(desc);
+}
+
+u32 flexrm_cmpl_desc_to_reqid(u64 cmpl_desc)
+{
+	return (u32)(cmpl_desc & CMPL_OPAQUE_MASK);
+}
+
+int flexrm_cmpl_desc_to_error(u64 cmpl_desc)
+{
+	u32 status;
+
+	status = DESC_DEC(cmpl_desc, CMPL_DME_STATUS_SHIFT,
+			  CMPL_DME_STATUS_MASK);
+	if (status & DME_STATUS_ERROR_MASK)
+		return -EIO;
+
+	status = DESC_DEC(cmpl_desc, CMPL_RM_STATUS_SHIFT,
+			  CMPL_RM_STATUS_MASK);
+	status &= RM_STATUS_CODE_MASK;
+	if (status == RM_STATUS_CODE_AE_TIMEOUT)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+bool flexrm_is_next_table_desc(void *desc_ptr)
+{
+	u64 desc = flexrm_read_desc(desc_ptr);
+	u32 type = DESC_DEC(desc, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+
+	return (type == NPTR_TYPE) ? true : false;
+}
+
+u64 flexrm_next_table_desc(u32 toggle, dma_addr_t next_addr)
+{
+	u64 desc = 0;
+
+	DESC_ENC(desc, NPTR_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+	DESC_ENC(desc, toggle, NPTR_TOGGLE_SHIFT, NPTR_TOGGLE_MASK);
+	DESC_ENC(desc, next_addr, NPTR_ADDR_SHIFT, NPTR_ADDR_MASK);
+
+	return desc;
+}
+
+u64 flexrm_null_desc(u32 toggle)
+{
+	u64 desc = 0;
+
+	DESC_ENC(desc, NULL_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+	DESC_ENC(desc, toggle, NULL_TOGGLE_SHIFT, NULL_TOGGLE_MASK);
+
+	return desc;
+}
+
+u32 flexrm_estimate_header_desc_count(u32 nhcnt)
+{
+	u32 hcnt = nhcnt / HEADER_BDCOUNT_MAX;
+
+	if (!(nhcnt % HEADER_BDCOUNT_MAX))
+		hcnt += 1;
+
+	return hcnt;
+}
+
+static void flexrm_flip_header_toogle(void *desc_ptr)
+{
+	u64 desc = flexrm_read_desc(desc_ptr);
+
+	if (desc & ((u64)0x1 << HEADER_TOGGLE_SHIFT))
+		desc &= ~((u64)0x1 << HEADER_TOGGLE_SHIFT);
+	else
+		desc |= ((u64)0x1 << HEADER_TOGGLE_SHIFT);
+
+	flexrm_write_desc(desc_ptr, desc);
+}
+
+static u64 flexrm_header_desc(u32 toggle, u32 startpkt, u32 endpkt,
+			       u32 bdcount, u32 flags, u32 opaque)
+{
+	u64 desc = 0;
+
+	DESC_ENC(desc, HEADER_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+	DESC_ENC(desc, toggle, HEADER_TOGGLE_SHIFT, HEADER_TOGGLE_MASK);
+	DESC_ENC(desc, startpkt, HEADER_STARTPKT_SHIFT, HEADER_STARTPKT_MASK);
+	DESC_ENC(desc, endpkt, HEADER_ENDPKT_SHIFT, HEADER_ENDPKT_MASK);
+	DESC_ENC(desc, bdcount, HEADER_BDCOUNT_SHIFT, HEADER_BDCOUNT_MASK);
+	DESC_ENC(desc, flags, HEADER_FLAGS_SHIFT, HEADER_FLAGS_MASK);
+	DESC_ENC(desc, opaque, HEADER_OPAQUE_SHIFT, HEADER_OPAQUE_MASK);
+
+	return desc;
+}
+
+static void flexrm_enqueue_desc(u32 nhpos, u32 nhcnt, u32 reqid,
+				 u64 desc, void **desc_ptr, u32 *toggle,
+				 void *start_desc, void *end_desc)
+{
+	u64 d;
+	u32 nhavail, _toggle, _startpkt, _endpkt, _bdcount;
+
+	/* Sanity check */
+	if (nhcnt <= nhpos)
+		return;
+
+	/*
+	 * Each request or packet start with a HEADER descriptor followed
+	 * by one or more non-HEADER descriptors (SRC, SRCT, MSRC, DST,
+	 * DSTT, MDST, IMM, and IMMT). The number of non-HEADER descriptors
+	 * following a HEADER descriptor is represented by BDCOUNT field
+	 * of HEADER descriptor. The max value of BDCOUNT field is 31 which
+	 * means we can only have 31 non-HEADER descriptors following one
+	 * HEADER descriptor.
+	 *
+	 * In general use, number of non-HEADER descriptors can easily go
+	 * beyond 31. To tackle this situation, we have packet (or request)
+	 * extenstion bits (STARTPKT and ENDPKT) in the HEADER descriptor.
+	 *
+	 * To use packet extension, the first HEADER descriptor of request
+	 * (or packet) will have STARTPKT=1 and ENDPKT=0. The intermediate
+	 * HEADER descriptors will have STARTPKT=0 and ENDPKT=0. The last
+	 * HEADER descriptor will have STARTPKT=0 and ENDPKT=1. Also, the
+	 * TOGGLE bit of the first HEADER will be set to invalid state to
+	 * ensure that FlexRM does not start fetching descriptors till all
+	 * descriptors are enqueued. The user of this function will flip
+	 * the TOGGLE bit of first HEADER after all descriptors are
+	 * enqueued.
+	 */
+
+	if ((nhpos % HEADER_BDCOUNT_MAX == 0) && (nhcnt - nhpos)) {
+		/* Prepare the header descriptor */
+		nhavail = (nhcnt - nhpos);
+		_toggle = (nhpos == 0) ? !(*toggle) : (*toggle);
+		_startpkt = (nhpos == 0) ? 0x1 : 0x0;
+		_endpkt = (nhavail <= HEADER_BDCOUNT_MAX) ? 0x1 : 0x0;
+		_bdcount = (nhavail <= HEADER_BDCOUNT_MAX) ?
+				nhavail : HEADER_BDCOUNT_MAX;
+		if (nhavail <= HEADER_BDCOUNT_MAX)
+			_bdcount = nhavail;
+		else
+			_bdcount = HEADER_BDCOUNT_MAX;
+		d = flexrm_header_desc(_toggle, _startpkt, _endpkt,
+					_bdcount, 0x0, reqid);
+
+		/* Write header descriptor */
+		flexrm_write_desc(*desc_ptr, d);
+
+		/* Point to next descriptor */
+		*desc_ptr += sizeof(desc);
+		if (*desc_ptr == end_desc)
+			*desc_ptr = start_desc;
+
+		/* Skip next pointer descriptors */
+		while (flexrm_is_next_table_desc(*desc_ptr)) {
+			*toggle = (*toggle) ? 0 : 1;
+			*desc_ptr += sizeof(desc);
+			if (*desc_ptr == end_desc)
+				*desc_ptr = start_desc;
+		}
+	}
+
+	/* Write desired descriptor */
+	flexrm_write_desc(*desc_ptr, desc);
+
+	/* Point to next descriptor */
+	*desc_ptr += sizeof(desc);
+	if (*desc_ptr == end_desc)
+		*desc_ptr = start_desc;
+
+	/* Skip next pointer descriptors */
+	while (flexrm_is_next_table_desc(*desc_ptr)) {
+		*toggle = (*toggle) ? 0 : 1;
+		*desc_ptr += sizeof(desc);
+		if (*desc_ptr == end_desc)
+			*desc_ptr = start_desc;
+	}
+}
+
+static u64 flexrm_src_desc(dma_addr_t addr, unsigned int length)
+{
+	u64 desc = 0;
+
+	DESC_ENC(desc, SRC_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+	DESC_ENC(desc, length, SRC_LENGTH_SHIFT, SRC_LENGTH_MASK);
+	DESC_ENC(desc, addr, SRC_ADDR_SHIFT, SRC_ADDR_MASK);
+
+	return desc;
+}
+
+static u64 flexrm_msrc_desc(dma_addr_t addr, unsigned int length_div_16)
+{
+	u64 desc = 0;
+
+	DESC_ENC(desc, MSRC_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+	DESC_ENC(desc, length_div_16, MSRC_LENGTH_SHIFT, MSRC_LENGTH_MASK);
+	DESC_ENC(desc, addr, MSRC_ADDR_SHIFT, MSRC_ADDR_MASK);
+
+	return desc;
+}
+
+static u64 flexrm_dst_desc(dma_addr_t addr, unsigned int length)
+{
+	u64 desc = 0;
+
+	DESC_ENC(desc, DST_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+	DESC_ENC(desc, length, DST_LENGTH_SHIFT, DST_LENGTH_MASK);
+	DESC_ENC(desc, addr, DST_ADDR_SHIFT, DST_ADDR_MASK);
+
+	return desc;
+}
+
+static u64 flexrm_mdst_desc(dma_addr_t addr, unsigned int length_div_16)
+{
+	u64 desc = 0;
+
+	DESC_ENC(desc, MDST_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+	DESC_ENC(desc, length_div_16, MDST_LENGTH_SHIFT, MDST_LENGTH_MASK);
+	DESC_ENC(desc, addr, MDST_ADDR_SHIFT, MDST_ADDR_MASK);
+
+	return desc;
+}
+
+static u64 flexrm_imm_desc(u64 data)
+{
+	u64 desc = 0;
+
+	DESC_ENC(desc, IMM_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+	DESC_ENC(desc, data, IMM_DATA_SHIFT, IMM_DATA_MASK);
+
+	return desc;
+}
+
+static u64 flexrm_srct_desc(dma_addr_t addr, unsigned int length)
+{
+	u64 desc = 0;
+
+	DESC_ENC(desc, SRCT_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+	DESC_ENC(desc, length, SRCT_LENGTH_SHIFT, SRCT_LENGTH_MASK);
+	DESC_ENC(desc, addr, SRCT_ADDR_SHIFT, SRCT_ADDR_MASK);
+
+	return desc;
+}
+
+static u64 flexrm_dstt_desc(dma_addr_t addr, unsigned int length)
+{
+	u64 desc = 0;
+
+	DESC_ENC(desc, DSTT_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+	DESC_ENC(desc, length, DSTT_LENGTH_SHIFT, DSTT_LENGTH_MASK);
+	DESC_ENC(desc, addr, DSTT_ADDR_SHIFT, DSTT_ADDR_MASK);
+
+	return desc;
+}
+
+static u64 flexrm_immt_desc(u64 data)
+{
+	u64 desc = 0;
+
+	DESC_ENC(desc, IMMT_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK);
+	DESC_ENC(desc, data, IMMT_DATA_SHIFT, IMMT_DATA_MASK);
+
+	return desc;
+}
+
+static bool flexrm_spu_sanity_check(struct brcm_message *msg)
+{
+	struct scatterlist *sg;
+
+	if (!msg->spu.src || !msg->spu.dst)
+		return false;
+	for (sg = msg->spu.src; sg; sg = sg_next(sg)) {
+		if (sg->length & 0xf) {
+			if (sg->length > SRC_LENGTH_MASK)
+				return false;
+		} else {
+			if (sg->length > (MSRC_LENGTH_MASK * 16))
+				return false;
+		}
+	}
+	for (sg = msg->spu.dst; sg; sg = sg_next(sg)) {
+		if (sg->length & 0xf) {
+			if (sg->length > DST_LENGTH_MASK)
+				return false;
+		} else {
+			if (sg->length > (MDST_LENGTH_MASK * 16))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+static u32 flexrm_spu_estimate_nonheader_desc_count(struct brcm_message *msg)
+{
+	u32 cnt = 0;
+	unsigned int dst_target = 0;
+	struct scatterlist *src_sg = msg->spu.src, *dst_sg = msg->spu.dst;
+
+	while (src_sg || dst_sg) {
+		if (src_sg) {
+			cnt++;
+			dst_target = src_sg->length;
+			src_sg = sg_next(src_sg);
+		} else
+			dst_target = UINT_MAX;
+
+		while (dst_target && dst_sg) {
+			cnt++;
+			if (dst_sg->length < dst_target)
+				dst_target -= dst_sg->length;
+			else
+				dst_target = 0;
+			dst_sg = sg_next(dst_sg);
+		}
+	}
+
+	return cnt;
+}
+
+static int flexrm_spu_dma_map(struct device *dev, struct brcm_message *msg)
+{
+	int rc;
+
+	rc = dma_map_sg(dev, msg->spu.src, sg_nents(msg->spu.src),
+			DMA_TO_DEVICE);
+	if (rc < 0)
+		return rc;
+
+	rc = dma_map_sg(dev, msg->spu.dst, sg_nents(msg->spu.dst),
+			DMA_FROM_DEVICE);
+	if (rc < 0) {
+		dma_unmap_sg(dev, msg->spu.src, sg_nents(msg->spu.src),
+			     DMA_TO_DEVICE);
+		return rc;
+	}
+
+	return 0;
+}
+
+static void flexrm_spu_dma_unmap(struct device *dev, struct brcm_message *msg)
+{
+	dma_unmap_sg(dev, msg->spu.dst, sg_nents(msg->spu.dst),
+		     DMA_FROM_DEVICE);
+	dma_unmap_sg(dev, msg->spu.src, sg_nents(msg->spu.src),
+		     DMA_TO_DEVICE);
+}
+
+static void *flexrm_spu_write_descs(struct brcm_message *msg, u32 nhcnt,
+				     u32 reqid, void *desc_ptr, u32 toggle,
+				     void *start_desc, void *end_desc)
+{
+	u64 d;
+	u32 nhpos = 0;
+	void *orig_desc_ptr = desc_ptr;
+	unsigned int dst_target = 0;
+	struct scatterlist *src_sg = msg->spu.src, *dst_sg = msg->spu.dst;
+
+	while (src_sg || dst_sg) {
+		if (src_sg) {
+			if (sg_dma_len(src_sg) & 0xf)
+				d = flexrm_src_desc(sg_dma_address(src_sg),
+						     sg_dma_len(src_sg));
+			else
+				d = flexrm_msrc_desc(sg_dma_address(src_sg),
+						      sg_dma_len(src_sg)/16);
+			flexrm_enqueue_desc(nhpos, nhcnt, reqid,
+					     d, &desc_ptr, &toggle,
+					     start_desc, end_desc);
+			nhpos++;
+			dst_target = sg_dma_len(src_sg);
+			src_sg = sg_next(src_sg);
+		} else
+			dst_target = UINT_MAX;
+
+		while (dst_target && dst_sg) {
+			if (sg_dma_len(dst_sg) & 0xf)
+				d = flexrm_dst_desc(sg_dma_address(dst_sg),
+						     sg_dma_len(dst_sg));
+			else
+				d = flexrm_mdst_desc(sg_dma_address(dst_sg),
+						      sg_dma_len(dst_sg)/16);
+			flexrm_enqueue_desc(nhpos, nhcnt, reqid,
+					     d, &desc_ptr, &toggle,
+					     start_desc, end_desc);
+			nhpos++;
+			if (sg_dma_len(dst_sg) < dst_target)
+				dst_target -= sg_dma_len(dst_sg);
+			else
+				dst_target = 0;
+			dst_sg = sg_next(dst_sg);
+		}
+	}
+
+	/* Null descriptor with invalid toggle bit */
+	flexrm_write_desc(desc_ptr, flexrm_null_desc(!toggle));
+
+	/* Ensure that descriptors have been written to memory */
+	wmb();
+
+	/* Flip toggle bit in header */
+	flexrm_flip_header_toogle(orig_desc_ptr);
+
+	return desc_ptr;
+}
+
+static bool flexrm_sba_sanity_check(struct brcm_message *msg)
+{
+	u32 i;
+
+	if (!msg->sba.cmds || !msg->sba.cmds_count)
+		return false;
+
+	for (i = 0; i < msg->sba.cmds_count; i++) {
+		if (((msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_B) ||
+		     (msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_C)) &&
+		    (msg->sba.cmds[i].flags & BRCM_SBA_CMD_HAS_OUTPUT))
+			return false;
+		if ((msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_B) &&
+		    (msg->sba.cmds[i].data_len > SRCT_LENGTH_MASK))
+			return false;
+		if ((msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_C) &&
+		    (msg->sba.cmds[i].data_len > SRCT_LENGTH_MASK))
+			return false;
+		if ((msg->sba.cmds[i].flags & BRCM_SBA_CMD_HAS_RESP) &&
+		    (msg->sba.cmds[i].resp_len > DSTT_LENGTH_MASK))
+			return false;
+		if ((msg->sba.cmds[i].flags & BRCM_SBA_CMD_HAS_OUTPUT) &&
+		    (msg->sba.cmds[i].data_len > DSTT_LENGTH_MASK))
+			return false;
+	}
+
+	return true;
+}
+
+static u32 flexrm_sba_estimate_nonheader_desc_count(struct brcm_message *msg)
+{
+	u32 i, cnt;
+
+	cnt = 0;
+	for (i = 0; i < msg->sba.cmds_count; i++) {
+		cnt++;
+
+		if ((msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_B) ||
+		    (msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_C))
+			cnt++;
+
+		if (msg->sba.cmds[i].flags & BRCM_SBA_CMD_HAS_RESP)
+			cnt++;
+
+		if (msg->sba.cmds[i].flags & BRCM_SBA_CMD_HAS_OUTPUT)
+			cnt++;
+	}
+
+	return cnt;
+}
+
+static void *flexrm_sba_write_descs(struct brcm_message *msg, u32 nhcnt,
+				     u32 reqid, void *desc_ptr, u32 toggle,
+				     void *start_desc, void *end_desc)
+{
+	u64 d;
+	u32 i, nhpos = 0;
+	struct brcm_sba_command *c;
+	void *orig_desc_ptr = desc_ptr;
+
+	/* Convert SBA commands into descriptors */
+	for (i = 0; i < msg->sba.cmds_count; i++) {
+		c = &msg->sba.cmds[i];
+
+		if ((c->flags & BRCM_SBA_CMD_HAS_RESP) &&
+		    (c->flags & BRCM_SBA_CMD_HAS_OUTPUT)) {
+			/* Destination response descriptor */
+			d = flexrm_dst_desc(c->resp, c->resp_len);
+			flexrm_enqueue_desc(nhpos, nhcnt, reqid,
+					     d, &desc_ptr, &toggle,
+					     start_desc, end_desc);
+			nhpos++;
+		} else if (c->flags & BRCM_SBA_CMD_HAS_RESP) {
+			/* Destination response with tlast descriptor */
+			d = flexrm_dstt_desc(c->resp, c->resp_len);
+			flexrm_enqueue_desc(nhpos, nhcnt, reqid,
+					     d, &desc_ptr, &toggle,
+					     start_desc, end_desc);
+			nhpos++;
+		}
+
+		if (c->flags & BRCM_SBA_CMD_HAS_OUTPUT) {
+			/* Destination with tlast descriptor */
+			d = flexrm_dstt_desc(c->data, c->data_len);
+			flexrm_enqueue_desc(nhpos, nhcnt, reqid,
+					     d, &desc_ptr, &toggle,
+					     start_desc, end_desc);
+			nhpos++;
+		}
+
+		if (c->flags & BRCM_SBA_CMD_TYPE_B) {
+			/* Command as immediate descriptor */
+			d = flexrm_imm_desc(c->cmd);
+			flexrm_enqueue_desc(nhpos, nhcnt, reqid,
+					     d, &desc_ptr, &toggle,
+					     start_desc, end_desc);
+			nhpos++;
+		} else {
+			/* Command as immediate descriptor with tlast */
+			d = flexrm_immt_desc(c->cmd);
+			flexrm_enqueue_desc(nhpos, nhcnt, reqid,
+					     d, &desc_ptr, &toggle,
+					     start_desc, end_desc);
+			nhpos++;
+		}
+
+		if ((c->flags & BRCM_SBA_CMD_TYPE_B) ||
+		    (c->flags & BRCM_SBA_CMD_TYPE_C)) {
+			/* Source with tlast descriptor */
+			d = flexrm_srct_desc(c->data, c->data_len);
+			flexrm_enqueue_desc(nhpos, nhcnt, reqid,
+					     d, &desc_ptr, &toggle,
+					     start_desc, end_desc);
+			nhpos++;
+		}
+	}
+
+	/* Null descriptor with invalid toggle bit */
+	flexrm_write_desc(desc_ptr, flexrm_null_desc(!toggle));
+
+	/* Ensure that descriptors have been written to memory */
+	wmb();
+
+	/* Flip toggle bit in header */
+	flexrm_flip_header_toogle(orig_desc_ptr);
+
+	return desc_ptr;
+}
+
+bool flexrm_sanity_check(struct brcm_message *msg)
+{
+	if (!msg)
+		return false;
+
+	switch (msg->type) {
+	case BRCM_MESSAGE_SPU:
+		return flexrm_spu_sanity_check(msg);
+	case BRCM_MESSAGE_SBA:
+		return flexrm_sba_sanity_check(msg);
+	default:
+		return false;
+	};
+}
+
+u32 flexrm_estimate_nonheader_desc_count(struct brcm_message *msg)
+{
+	if (!msg)
+		return 0;
+
+	switch (msg->type) {
+	case BRCM_MESSAGE_SPU:
+		return flexrm_spu_estimate_nonheader_desc_count(msg);
+	case BRCM_MESSAGE_SBA:
+		return flexrm_sba_estimate_nonheader_desc_count(msg);
+	default:
+		return 0;
+	};
+}
+
+int flexrm_dma_map(struct device *dev, struct brcm_message *msg)
+{
+	if (!dev || !msg)
+		return -EINVAL;
+
+	switch (msg->type) {
+	case BRCM_MESSAGE_SPU:
+		return flexrm_spu_dma_map(dev, msg);
+	default:
+		break;
+	};
+
+	return 0;
+}
+
+void flexrm_dma_unmap(struct device *dev, struct brcm_message *msg)
+{
+	if (!dev || !msg)
+		return;
+
+	switch (msg->type) {
+	case BRCM_MESSAGE_SPU:
+		flexrm_spu_dma_unmap(dev, msg);
+		break;
+	default:
+		break;
+	};
+}
+
+void *flexrm_write_descs(struct brcm_message *msg, u32 nhcnt,
+			  u32 reqid, void *desc_ptr, u32 toggle,
+			  void *start_desc, void *end_desc)
+{
+	if (!msg || !desc_ptr || !start_desc || !end_desc)
+		return ERR_PTR(-ENOTSUPP);
+
+	if ((desc_ptr < start_desc) || (end_desc <= desc_ptr))
+		return ERR_PTR(-ERANGE);
+
+	switch (msg->type) {
+	case BRCM_MESSAGE_SPU:
+		return flexrm_spu_write_descs(msg, nhcnt, reqid,
+					       desc_ptr, toggle,
+					       start_desc, end_desc);
+	case BRCM_MESSAGE_SBA:
+		return flexrm_sba_write_descs(msg, nhcnt, reqid,
+					       desc_ptr, toggle,
+					       start_desc, end_desc);
+	default:
+		return ERR_PTR(-ENOTSUPP);
+	};
+}
diff --git a/drivers/mailbox/mailbox-flexrm/flexrm-desc.h b/drivers/mailbox/mailbox-flexrm/flexrm-desc.h
new file mode 100644
index 0000000..a95cf61
--- /dev/null
+++ b/drivers/mailbox/mailbox-flexrm/flexrm-desc.h
@@ -0,0 +1,47 @@
+/* Broadcom FlexRM Mailbox Driver
+ *
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * FlexRM descriptor library
+ */
+
+#ifndef __FLEXRM_DESC_H__
+#define __FLEXRM_DESC_H__
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/mailbox/brcm-message.h>
+
+extern u64 flexrm_read_desc(void *desc_ptr);
+
+extern void flexrm_write_desc(void *desc_ptr, u64 desc);
+
+extern u32 flexrm_cmpl_desc_to_reqid(u64 cmpl_desc);
+
+extern int flexrm_cmpl_desc_to_error(u64 cmpl_desc);
+
+extern bool flexrm_is_next_table_desc(void *desc_ptr);
+
+extern u64 flexrm_next_table_desc(u32 toggle, dma_addr_t next_addr);
+
+extern u64 flexrm_null_desc(u32 toogle);
+
+extern u32 flexrm_estimate_header_desc_count(u32 nhcnt);
+
+extern bool flexrm_sanity_check(struct brcm_message *msg);
+
+extern u32 flexrm_estimate_nonheader_desc_count(struct brcm_message *msg);
+
+extern int flexrm_dma_map(struct device *dev, struct brcm_message *msg);
+
+extern void flexrm_dma_unmap(struct device *dev, struct brcm_message *msg);
+
+extern void *flexrm_write_descs(struct brcm_message *msg, u32 nhcnt,
+				 u32 reqid, void *desc_ptr, u32 toggle,
+				 void *start_desc, void *end_desc);
+
+#endif /* __FLEXRM_DESC_H__ */
diff --git a/drivers/mailbox/mailbox-flexrm/flexrm-main.c b/drivers/mailbox/mailbox-flexrm/flexrm-main.c
new file mode 100644
index 0000000..66ca497
--- /dev/null
+++ b/drivers/mailbox/mailbox-flexrm/flexrm-main.c
@@ -0,0 +1,829 @@
+/* Broadcom FlexRM Mailbox Driver
+ *
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Each Broadcom FlexSparx4 offload engine is implemented as an
+ * extension to Broadcom FlexRM ring manager. The FlexRM ring
+ * manager provides a set of rings which can be used to submit
+ * work to a FlexSparx4 offload engine.
+ *
+ * This driver creates a mailbox controller using a set of FlexRM
+ * rings where each mailbox channel represents a separate FlexRM ring.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/err.h>
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mailbox_controller.h>
+#include <linux/mailbox_client.h>
+#include <linux/mailbox/brcm-message.h>
+#include <linux/module.h>
+#include <linux/msi.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#include "flexrm-desc.h"
+
+/* FlexRM configuration */
+#define RING_REGS_SIZE					0x10000
+#define RING_DESC_SIZE					8
+#define RING_DESC_INDEX(offset)				\
+			((offset) / RING_DESC_SIZE)
+#define RING_DESC_OFFSET(index)				\
+			((index) * RING_DESC_SIZE)
+#define RING_MAX_REQ_COUNT				1024
+#define RING_BD_ALIGN_ORDER				12
+#define RING_BD_ALIGN_CHECK(addr)			\
+			(!((addr) & ((0x1 << RING_BD_ALIGN_ORDER) - 1)))
+#define RING_BD_TOGGLE_INVALID(offset)			\
+			(((offset) >> RING_BD_ALIGN_ORDER) & 0x1)
+#define RING_BD_TOGGLE_VALID(offset)			\
+			(!RING_BD_TOGGLE_INVALID(offset))
+#define RING_BD_DESC_PER_REQ				32
+#define RING_BD_DESC_COUNT				\
+			(RING_MAX_REQ_COUNT * RING_BD_DESC_PER_REQ)
+#define RING_BD_SIZE					\
+			(RING_BD_DESC_COUNT * RING_DESC_SIZE)
+#define RING_CMPL_ALIGN_ORDER				13
+#define RING_CMPL_DESC_COUNT				RING_MAX_REQ_COUNT
+#define RING_CMPL_SIZE					\
+			(RING_CMPL_DESC_COUNT * RING_DESC_SIZE)
+#define RING_VER_MAGIC					0x76303031
+
+/* Per-Ring register offsets */
+#define RING_VER					0x000
+#define RING_BD_START_ADDR				0x004
+#define RING_BD_READ_PTR				0x008
+#define RING_BD_WRITE_PTR				0x00c
+#define RING_BD_READ_PTR_DDR_LS				0x010
+#define RING_BD_READ_PTR_DDR_MS				0x014
+#define RING_CMPL_START_ADDR				0x018
+#define RING_CMPL_WRITE_PTR				0x01c
+#define RING_NUM_REQ_RECV_LS				0x020
+#define RING_NUM_REQ_RECV_MS				0x024
+#define RING_NUM_REQ_TRANS_LS				0x028
+#define RING_NUM_REQ_TRANS_MS				0x02c
+#define RING_NUM_REQ_OUTSTAND				0x030
+#define RING_CONTROL					0x034
+#define RING_FLUSH_DONE					0x038
+#define RING_MSI_ADDR_LS				0x03c
+#define RING_MSI_ADDR_MS				0x040
+#define RING_MSI_CONTROL				0x048
+#define RING_BD_READ_PTR_DDR_CONTROL			0x04c
+#define RING_MSI_DATA_VALUE				0x064
+
+/* Register RING_BD_START_ADDR fields */
+#define BD_LAST_UPDATE_HW_SHIFT				28
+#define BD_LAST_UPDATE_HW_MASK				0x1
+#define BD_START_ADDR_VALUE(pa)				\
+	((u32)((((dma_addr_t)(pa)) >> RING_BD_ALIGN_ORDER) & 0x0fffffff))
+#define BD_START_ADDR_DECODE(val)			\
+	((dma_addr_t)((val) & 0x0fffffff) << RING_BD_ALIGN_ORDER)
+
+/* Register RING_CMPL_START_ADDR fields */
+#define CMPL_START_ADDR_VALUE(pa)			\
+	((u32)((((u64)(pa)) >> RING_CMPL_ALIGN_ORDER) & 0x03ffffff))
+
+/* Register RING_CONTROL fields */
+#define CONTROL_MASK_DISABLE_CONTROL			12
+#define CONTROL_FLUSH_SHIFT				5
+#define CONTROL_ACTIVE_SHIFT				4
+#define CONTROL_RATE_ADAPT_MASK				0xf
+#define CONTROL_RATE_DYNAMIC				0x0
+#define CONTROL_RATE_FAST				0x8
+#define CONTROL_RATE_MEDIUM				0x9
+#define CONTROL_RATE_SLOW				0xa
+#define CONTROL_RATE_IDLE				0xb
+
+/* Register RING_FLUSH_DONE fields */
+#define FLUSH_DONE_MASK					0x1
+
+/* Register RING_MSI_CONTROL fields */
+#define MSI_TIMER_VAL_SHIFT				16
+#define MSI_TIMER_VAL_MASK				0xffff
+#define MSI_ENABLE_SHIFT				15
+#define MSI_ENABLE_MASK					0x1
+#define MSI_COUNT_SHIFT					0
+#define MSI_COUNT_MASK					0x3ff
+
+/* Register RING_BD_READ_PTR_DDR_CONTROL fields */
+#define BD_READ_PTR_DDR_TIMER_VAL_SHIFT			16
+#define BD_READ_PTR_DDR_TIMER_VAL_MASK			0xffff
+#define BD_READ_PTR_DDR_ENABLE_SHIFT			15
+#define BD_READ_PTR_DDR_ENABLE_MASK			0x1
+
+struct flexrm_ring {
+	/* Unprotected members */
+	int num;
+	struct flexrm_mbox *mbox;
+	void __iomem *regs;
+	bool irq_requested;
+	unsigned int irq;
+	unsigned int msi_timer_val;
+	unsigned int msi_count_threshold;
+	struct ida requests_ida;
+	struct brcm_message *requests[RING_MAX_REQ_COUNT];
+	void *bd_base;
+	dma_addr_t bd_dma_base;
+	u32 bd_write_offset;
+	void *cmpl_base;
+	dma_addr_t cmpl_dma_base;
+	/* Protected members */
+	spinlock_t lock;
+	struct brcm_message *last_pending_msg;
+	u32 cmpl_read_offset;
+};
+
+struct flexrm_mbox {
+	struct device *dev;
+	void __iomem *regs;
+	u32 num_rings;
+	struct flexrm_ring *rings;
+	u64 dma_mask;
+	struct dma_pool *bd_pool;
+	struct dma_pool *cmpl_pool;
+	struct mbox_controller controller;
+};
+
+static int flexrm_new_request(struct flexrm_ring *ring,
+				struct brcm_message *batch_msg,
+				struct brcm_message *msg)
+{
+	void *next;
+	unsigned long flags;
+	u32 val, count, nhcnt;
+	u32 read_offset, write_offset;
+	bool exit_cleanup = false;
+	int ret = 0, reqid;
+
+	/* Do sanity check on message */
+	if (!flexrm_sanity_check(msg))
+		return -EIO;
+	msg->error = 0;
+
+	/* If no requests possible then save data pointer and goto done. */
+	reqid = ida_simple_get(&ring->requests_ida, 0,
+				RING_MAX_REQ_COUNT, GFP_KERNEL);
+	if (reqid < 0) {
+		spin_lock_irqsave(&ring->lock, flags);
+		if (batch_msg)
+			ring->last_pending_msg = batch_msg;
+		else
+			ring->last_pending_msg = msg;
+		spin_unlock_irqrestore(&ring->lock, flags);
+		return 0;
+	}
+	ring->requests[reqid] = msg;
+
+	/* Do DMA mappings for the message */
+	ret = flexrm_dma_map(ring->mbox->dev, msg);
+	if (ret < 0) {
+		ring->requests[reqid] = NULL;
+		ida_simple_remove(&ring->requests_ida, reqid);
+		return ret;
+	}
+
+	/* If last_pending_msg is already set then goto done with error */
+	spin_lock_irqsave(&ring->lock, flags);
+	if (ring->last_pending_msg)
+		ret = -ENOSPC;
+	spin_unlock_irqrestore(&ring->lock, flags);
+	if (ret < 0) {
+		dev_warn(ring->mbox->dev, "no space in ring %d\n", ring->num);
+		exit_cleanup = true;
+		goto exit;
+	}
+
+	/* Determine current HW BD read offset */
+	read_offset = readl_relaxed(ring->regs + RING_BD_READ_PTR);
+	val = readl_relaxed(ring->regs + RING_BD_START_ADDR);
+	read_offset *= RING_DESC_SIZE;
+	read_offset += (u32)(BD_START_ADDR_DECODE(val) - ring->bd_dma_base);
+
+	/*
+	 * Number required descriptors = number of non-header descriptors +
+	 *				 number of header descriptors +
+	 *				 1x null descriptor
+	 */
+	nhcnt = flexrm_estimate_nonheader_desc_count(msg);
+	count = flexrm_estimate_header_desc_count(nhcnt) + nhcnt + 1;
+
+	/* Check for available descriptor space. */
+	write_offset = ring->bd_write_offset;
+	while (count) {
+		if (!flexrm_is_next_table_desc(ring->bd_base + write_offset))
+			count--;
+		write_offset += RING_DESC_SIZE;
+		if (write_offset == RING_BD_SIZE)
+			write_offset = 0x0;
+		if (write_offset == read_offset)
+			break;
+	}
+	if (count) {
+		spin_lock_irqsave(&ring->lock, flags);
+		if (batch_msg)
+			ring->last_pending_msg = batch_msg;
+		else
+			ring->last_pending_msg = msg;
+		spin_unlock_irqrestore(&ring->lock, flags);
+		ret = 0;
+		exit_cleanup = true;
+		goto exit;
+	}
+
+	/* Write descriptors to ring */
+	next = flexrm_write_descs(msg, nhcnt, reqid,
+			ring->bd_base + ring->bd_write_offset,
+			RING_BD_TOGGLE_VALID(ring->bd_write_offset),
+			ring->bd_base, ring->bd_base + RING_BD_SIZE);
+	if (IS_ERR(next)) {
+		ret = PTR_ERR(next);
+		exit_cleanup = true;
+		goto exit;
+	}
+
+	/* Save ring BD write offset */
+	ring->bd_write_offset = (unsigned long)(next - ring->bd_base);
+
+exit:
+	/* Update error status in message */
+	msg->error = ret;
+
+	/* Cleanup if we failed */
+	if (exit_cleanup) {
+		flexrm_dma_unmap(ring->mbox->dev, msg);
+		ring->requests[reqid] = NULL;
+		ida_simple_remove(&ring->requests_ida, reqid);
+	}
+
+	return ret;
+}
+
+static int flexrm_process_completions(struct flexrm_ring *ring)
+{
+	u64 desc;
+	int err, count = 0;
+	unsigned long flags;
+	struct brcm_message *msg = NULL;
+	u32 reqid, cmpl_read_offset, cmpl_write_offset;
+	struct mbox_chan *chan = &ring->mbox->controller.chans[ring->num];
+
+	spin_lock_irqsave(&ring->lock, flags);
+
+	/* Check last_pending_msg */
+	if (ring->last_pending_msg) {
+		msg = ring->last_pending_msg;
+		ring->last_pending_msg = NULL;
+	}
+
+	/*
+	 * Get current completion read and write offset
+	 *
+	 * Note: We should read completion write pointer atleast once
+	 * after we get a MSI interrupt because HW maintains internal
+	 * MSI status which will allow next MSI interrupt only after
+	 * completion write pointer is read.
+	 */
+	cmpl_write_offset = readl_relaxed(ring->regs + RING_CMPL_WRITE_PTR);
+	cmpl_write_offset *= RING_DESC_SIZE;
+	cmpl_read_offset = ring->cmpl_read_offset;
+	ring->cmpl_read_offset = cmpl_write_offset;
+
+	spin_unlock_irqrestore(&ring->lock, flags);
+
+	/* If last_pending_msg was set then queue it back */
+	if (msg)
+		mbox_send_message(chan, msg);
+
+	/* For each completed request notify mailbox clients */
+	reqid = 0;
+	while (cmpl_read_offset != cmpl_write_offset) {
+		/* Dequeue next completion descriptor */
+		desc = *((u64 *)(ring->cmpl_base + cmpl_read_offset));
+
+		/* Next read offset */
+		cmpl_read_offset += RING_DESC_SIZE;
+		if (cmpl_read_offset == RING_CMPL_SIZE)
+			cmpl_read_offset = 0;
+
+		/* Decode error from completion descriptor */
+		err = flexrm_cmpl_desc_to_error(desc);
+		if (err < 0) {
+			dev_warn(ring->mbox->dev,
+				 "got completion desc=0x%lx with error %d",
+				 (unsigned long)desc, err);
+		}
+
+		/* Determine request id from completion descriptor */
+		reqid = flexrm_cmpl_desc_to_reqid(desc);
+
+		/* Determine message pointer based on reqid */
+		msg = ring->requests[reqid];
+		if (!msg) {
+			dev_warn(ring->mbox->dev,
+				 "null msg pointer for completion desc=0x%lx",
+				 (unsigned long)desc);
+			continue;
+		}
+
+		/* Release reqid for recycling */
+		ring->requests[reqid] = NULL;
+		ida_simple_remove(&ring->requests_ida, reqid);
+
+		/* Unmap DMA mappings */
+		flexrm_dma_unmap(ring->mbox->dev, msg);
+
+		/* Give-back message to mailbox client */
+		msg->error = err;
+		mbox_chan_received_data(chan, msg);
+
+		/* Increment number of completions processed */
+		count++;
+	}
+
+	return count;
+}
+
+static irqreturn_t flexrm_irq_event(int irq, void *dev_id)
+{
+	/* We only have MSI for completions so just wakeup IRQ thread */
+	/* Ring related errors will be informed via completion descriptors */
+
+	return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t flexrm_irq_thread(int irq, void *dev_id)
+{
+	flexrm_process_completions(dev_id);
+
+	return IRQ_HANDLED;
+}
+
+static int flexrm_send_data(struct mbox_chan *chan, void *data)
+{
+	int i, rc;
+	struct flexrm_ring *ring = chan->con_priv;
+	struct brcm_message *msg = data;
+
+	if (msg->type == BRCM_MESSAGE_BATCH) {
+		for (i = msg->batch.msgs_queued;
+		     i < msg->batch.msgs_count; i++) {
+			rc = flexrm_new_request(ring, msg,
+						 &msg->batch.msgs[i]);
+			if (rc) {
+				msg->error = rc;
+				return rc;
+			}
+			msg->batch.msgs_queued++;
+		}
+		return 0;
+	}
+
+	return flexrm_new_request(ring, NULL, data);
+}
+
+static bool flexrm_peek_data(struct mbox_chan *chan)
+{
+	int cnt = flexrm_process_completions(chan->con_priv);
+
+	return (cnt > 0) ? true : false;
+}
+
+static int flexrm_startup(struct mbox_chan *chan)
+{
+	u64 d;
+	u32 val, off;
+	int ret = 0;
+	dma_addr_t next_addr;
+	struct flexrm_ring *ring = chan->con_priv;
+
+	/* Allocate BD memory */
+	ring->bd_base = dma_pool_alloc(ring->mbox->bd_pool,
+				       GFP_KERNEL, &ring->bd_dma_base);
+	if (!ring->bd_base) {
+		dev_err(ring->mbox->dev, "can't allocate BD memory\n");
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	/* Configure next table pointer entries in BD memory */
+	for (off = 0; off < RING_BD_SIZE; off += RING_DESC_SIZE) {
+		next_addr = off + RING_DESC_SIZE;
+		if (next_addr == RING_BD_SIZE)
+			next_addr = 0;
+		next_addr += ring->bd_dma_base;
+		if (RING_BD_ALIGN_CHECK(next_addr))
+			d = flexrm_next_table_desc(RING_BD_TOGGLE_VALID(off),
+						    next_addr);
+		else
+			d = flexrm_null_desc(RING_BD_TOGGLE_INVALID(off));
+		flexrm_write_desc(ring->bd_base + off, d);
+	}
+
+	/* Allocate completion memory */
+	ring->cmpl_base = dma_pool_alloc(ring->mbox->cmpl_pool,
+					 GFP_KERNEL, &ring->cmpl_dma_base);
+	if (!ring->cmpl_base) {
+		dev_err(ring->mbox->dev, "can't allocate completion memory\n");
+		ret = -ENOMEM;
+		goto fail_free_bd_memory;
+	}
+	memset(ring->cmpl_base, 0, RING_CMPL_SIZE);
+
+	/* Request IRQ */
+	if (ring->irq == UINT_MAX) {
+		dev_err(ring->mbox->dev, "ring IRQ not available\n");
+		ret = -ENODEV;
+		goto fail_free_cmpl_memory;
+	}
+	ret = request_threaded_irq(ring->irq,
+				   flexrm_irq_event,
+				   flexrm_irq_thread,
+				   0, dev_name(ring->mbox->dev), ring);
+	if (ret) {
+		dev_err(ring->mbox->dev, "failed to request ring IRQ\n");
+		goto fail_free_cmpl_memory;
+	}
+	ring->irq_requested = true;
+
+	/* Disable/inactivate ring */
+	writel_relaxed(0x0, ring->regs + RING_CONTROL);
+
+	/* Program BD start address */
+	val = BD_START_ADDR_VALUE(ring->bd_dma_base);
+	writel_relaxed(val, ring->regs + RING_BD_START_ADDR);
+
+	/* BD write pointer will be same as HW write pointer */
+	ring->bd_write_offset =
+			readl_relaxed(ring->regs + RING_BD_WRITE_PTR);
+	ring->bd_write_offset *= RING_DESC_SIZE;
+
+	/* Program completion start address */
+	val = CMPL_START_ADDR_VALUE(ring->cmpl_dma_base);
+	writel_relaxed(val, ring->regs + RING_CMPL_START_ADDR);
+
+	/* Ensure last pending message is cleared */
+	ring->last_pending_msg = NULL;
+
+	/* Completion read pointer will be same as HW write pointer */
+	ring->cmpl_read_offset =
+			readl_relaxed(ring->regs + RING_CMPL_WRITE_PTR);
+	ring->cmpl_read_offset *= RING_DESC_SIZE;
+
+	/* Read ring Tx, Rx, and Outstanding counts to clear */
+	readl_relaxed(ring->regs + RING_NUM_REQ_RECV_LS);
+	readl_relaxed(ring->regs + RING_NUM_REQ_RECV_MS);
+	readl_relaxed(ring->regs + RING_NUM_REQ_TRANS_LS);
+	readl_relaxed(ring->regs + RING_NUM_REQ_TRANS_MS);
+	readl_relaxed(ring->regs + RING_NUM_REQ_OUTSTAND);
+
+	/* Configure RING_MSI_CONTROL */
+	val = 0;
+	val |= (ring->msi_timer_val << MSI_TIMER_VAL_SHIFT);
+	val |= BIT(MSI_ENABLE_SHIFT);
+	val |= (ring->msi_count_threshold & MSI_COUNT_MASK) << MSI_COUNT_SHIFT;
+	writel_relaxed(val, ring->regs + RING_MSI_CONTROL);
+
+	/* Enable/activate ring */
+	val = BIT(CONTROL_ACTIVE_SHIFT);
+	writel_relaxed(val, ring->regs + RING_CONTROL);
+
+	return 0;
+
+fail_free_cmpl_memory:
+	dma_pool_free(ring->mbox->cmpl_pool,
+		      ring->cmpl_base, ring->cmpl_dma_base);
+	ring->cmpl_base = NULL;
+fail_free_bd_memory:
+	dma_pool_free(ring->mbox->bd_pool,
+		      ring->bd_base, ring->bd_dma_base);
+	ring->bd_base = NULL;
+fail:
+	return ret;
+}
+
+static void flexrm_shutdown(struct mbox_chan *chan)
+{
+	u32 reqid;
+	unsigned int timeout;
+	struct brcm_message *msg;
+	struct flexrm_ring *ring = chan->con_priv;
+
+	/* Disable/inactivate ring */
+	writel_relaxed(0x0, ring->regs + RING_CONTROL);
+
+	/* Flush ring with timeout of 1s */
+	timeout = 1000;
+	writel_relaxed(BIT(CONTROL_FLUSH_SHIFT),
+			ring->regs + RING_CONTROL);
+	do {
+		if (readl_relaxed(ring->regs + RING_FLUSH_DONE) &
+		    FLUSH_DONE_MASK)
+			break;
+		mdelay(1);
+	} while (timeout--);
+
+	/* Abort all in-flight requests */
+	for (reqid = 0; reqid < RING_MAX_REQ_COUNT; reqid++) {
+		msg = ring->requests[reqid];
+		if (!msg)
+			continue;
+
+		/* Release reqid for recycling */
+		ring->requests[reqid] = NULL;
+		ida_simple_remove(&ring->requests_ida, reqid);
+
+		/* Unmap DMA mappings */
+		flexrm_dma_unmap(ring->mbox->dev, msg);
+
+		/* Give-back message to mailbox client */
+		msg->error = -EIO;
+		mbox_chan_received_data(chan, msg);
+	}
+
+	/* Release IRQ */
+	if (ring->irq_requested) {
+		free_irq(ring->irq, ring);
+		ring->irq_requested = false;
+	}
+
+	/* Free-up completion descriptor ring */
+	if (ring->cmpl_base) {
+		dma_pool_free(ring->mbox->cmpl_pool,
+			      ring->cmpl_base, ring->cmpl_dma_base);
+		ring->cmpl_base = NULL;
+	}
+
+	/* Free-up BD descriptor ring */
+	if (ring->bd_base) {
+		dma_pool_free(ring->mbox->bd_pool,
+			      ring->bd_base, ring->bd_dma_base);
+		ring->bd_base = NULL;
+	}
+}
+
+static bool flexrm_last_tx_done(struct mbox_chan *chan)
+{
+	bool ret;
+	unsigned long flags;
+	struct flexrm_ring *ring = chan->con_priv;
+
+	spin_lock_irqsave(&ring->lock, flags);
+	ret = (ring->last_pending_msg) ? false : true;
+	spin_unlock_irqrestore(&ring->lock, flags);
+
+	return ret;
+}
+
+static const struct mbox_chan_ops flexrm_mbox_chan_ops = {
+	.send_data	= flexrm_send_data,
+	.startup	= flexrm_startup,
+	.shutdown	= flexrm_shutdown,
+	.last_tx_done	= flexrm_last_tx_done,
+	.peek_data	= flexrm_peek_data,
+};
+
+static void flexrm_mbox_msi_write(struct msi_desc *desc, struct msi_msg *msg)
+{
+	struct device *dev = msi_desc_to_dev(desc);
+	struct flexrm_mbox *mbox = dev_get_drvdata(dev);
+	struct flexrm_ring *ring = &mbox->rings[desc->platform.msi_index];
+
+	/* Configure per-Ring MSI registers */
+	writel_relaxed(msg->address_lo, ring->regs + RING_MSI_ADDR_LS);
+	writel_relaxed(msg->address_hi, ring->regs + RING_MSI_ADDR_MS);
+	writel_relaxed(msg->data, ring->regs + RING_MSI_DATA_VALUE);
+}
+
+static struct mbox_chan *flexrm_mbox_of_xlate(struct mbox_controller *cntlr,
+					const struct of_phandle_args *pa)
+{
+	struct mbox_chan *chan;
+	struct flexrm_ring *ring;
+
+	if (pa->args_count < 3)
+		return ERR_PTR(-EINVAL);
+
+	if (pa->args[0] >= cntlr->num_chans)
+		return ERR_PTR(-ENOENT);
+
+	if (pa->args[1] > MSI_COUNT_MASK)
+		return ERR_PTR(-EINVAL);
+
+	if (pa->args[2] > MSI_TIMER_VAL_MASK)
+		return ERR_PTR(-EINVAL);
+
+	chan = &cntlr->chans[pa->args[0]];
+	ring = chan->con_priv;
+	ring->msi_count_threshold = pa->args[1];
+	ring->msi_timer_val = pa->args[2];
+
+	return chan;
+}
+
+static int flexrm_mbox_probe(struct platform_device *pdev)
+{
+	int index, ret = 0;
+	void __iomem *regs;
+	void __iomem *regs_end;
+	struct msi_desc *desc;
+	struct resource *iomem;
+	struct flexrm_ring *ring;
+	struct flexrm_mbox *mbox;
+	struct device *dev = &pdev->dev;
+
+	/* Allocate driver mailbox struct */
+	mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
+	if (!mbox) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+	mbox->dev = dev;
+	platform_set_drvdata(pdev, mbox);
+
+	/* Get resource for registers */
+	iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!iomem || (resource_size(iomem) < RING_REGS_SIZE)) {
+		ret = -ENODEV;
+		goto fail;
+	}
+
+	/* Map registers of all rings */
+	mbox->regs = devm_ioremap_resource(&pdev->dev, iomem);
+	if (IS_ERR(mbox->regs)) {
+		ret = PTR_ERR(mbox->regs);
+		dev_err(&pdev->dev, "Failed to remap mailbox regs: %d\n", ret);
+		goto fail;
+	}
+	regs_end = mbox->regs + resource_size(iomem);
+
+	/* Scan and count available rings */
+	mbox->num_rings = 0;
+	for (regs = mbox->regs; regs < regs_end; regs += RING_REGS_SIZE) {
+		if (readl_relaxed(regs + RING_VER) == RING_VER_MAGIC)
+			mbox->num_rings++;
+	}
+	if (!mbox->num_rings) {
+		ret = -ENODEV;
+		goto fail;
+	}
+
+	/* Allocate driver ring structs */
+	ring = devm_kcalloc(dev, mbox->num_rings, sizeof(*ring), GFP_KERNEL);
+	if (!ring) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+	mbox->rings = ring;
+
+	/* Initialize members of driver ring structs */
+	regs = mbox->regs;
+	for (index = 0; index < mbox->num_rings; index++) {
+		ring = &mbox->rings[index];
+		ring->num = index;
+		ring->mbox = mbox;
+		while ((regs < regs_end) &&
+		       (readl_relaxed(regs + RING_VER) != RING_VER_MAGIC))
+			regs += RING_REGS_SIZE;
+		if (regs_end <= regs) {
+			ret = -ENODEV;
+			goto fail;
+		}
+		ring->regs = regs;
+		regs += RING_REGS_SIZE;
+		ring->irq = UINT_MAX;
+		ring->irq_requested = false;
+		ring->msi_timer_val = MSI_TIMER_VAL_MASK;
+		ring->msi_count_threshold = 0x1;
+		ida_init(&ring->requests_ida);
+		memset(ring->requests, 0, sizeof(ring->requests));
+		ring->bd_base = NULL;
+		ring->bd_dma_base = 0;
+		ring->cmpl_base = NULL;
+		ring->cmpl_dma_base = 0;
+		spin_lock_init(&ring->lock);
+		ring->last_pending_msg = NULL;
+		ring->cmpl_read_offset = 0;
+	}
+
+	/* FlexRM is capable of 40-bit physical addresses only */
+	mbox->dma_mask = DMA_BIT_MASK(40);
+	dev->dma_mask = &mbox->dma_mask;
+
+	/* Create DMA pool for ring BD memory */
+	mbox->bd_pool = dma_pool_create("bd", dev, RING_BD_SIZE,
+					1 << RING_BD_ALIGN_ORDER, 0);
+	if (!mbox->bd_pool) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	/* Create DMA pool for ring completion memory */
+	mbox->cmpl_pool = dma_pool_create("cmpl", dev, RING_CMPL_SIZE,
+					  1 << RING_CMPL_ALIGN_ORDER, 0);
+	if (!mbox->cmpl_pool) {
+		ret = -ENOMEM;
+		goto fail_destroy_bd_pool;
+	}
+
+	/* Allocate platform MSIs for each ring */
+	ret = platform_msi_domain_alloc_irqs(dev, mbox->num_rings,
+						flexrm_mbox_msi_write);
+	if (ret)
+		goto fail_destroy_cmpl_pool;
+
+	/* Save alloced IRQ numbers for each ring */
+	for_each_msi_entry(desc, dev) {
+		ring = &mbox->rings[desc->platform.msi_index];
+		ring->irq = desc->irq;
+	}
+
+	/* Initialize mailbox controller */
+	mbox->controller.txdone_irq = false;
+	mbox->controller.txdone_poll = true;
+	mbox->controller.txpoll_period = 1;
+	mbox->controller.ops = &flexrm_mbox_chan_ops;
+	mbox->controller.dev = dev;
+	mbox->controller.num_chans = mbox->num_rings;
+	mbox->controller.of_xlate = flexrm_mbox_of_xlate;
+	mbox->controller.chans = devm_kcalloc(dev, mbox->num_rings,
+				sizeof(*mbox->controller.chans), GFP_KERNEL);
+	if (!mbox->controller.chans) {
+		ret = -ENOMEM;
+		goto fail_free_msis;
+	}
+	for (index = 0; index < mbox->num_rings; index++)
+		mbox->controller.chans[index].con_priv = &mbox->rings[index];
+
+	/* Register mailbox controller */
+	ret = mbox_controller_register(&mbox->controller);
+	if (ret)
+		goto fail_free_msis;
+
+	dev_info(dev, "registered flexrm mailbox with %d channels\n",
+			mbox->controller.num_chans);
+
+	return 0;
+
+fail_free_msis:
+	platform_msi_domain_free_irqs(dev);
+fail_destroy_cmpl_pool:
+	dma_pool_destroy(mbox->cmpl_pool);
+fail_destroy_bd_pool:
+	dma_pool_destroy(mbox->bd_pool);
+fail:
+	return ret;
+}
+
+static int flexrm_mbox_remove(struct platform_device *pdev)
+{
+	int index;
+	struct device *dev = &pdev->dev;
+	struct flexrm_ring *ring;
+	struct flexrm_mbox *mbox = platform_get_drvdata(pdev);
+
+	mbox_controller_unregister(&mbox->controller);
+
+	platform_msi_domain_free_irqs(dev);
+
+	dma_pool_destroy(mbox->cmpl_pool);
+	dma_pool_destroy(mbox->bd_pool);
+
+	for (index = 0; index < mbox->num_rings; index++) {
+		ring = &mbox->rings[index];
+		ida_destroy(&ring->requests_ida);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id flexrm_mbox_of_match[] = {
+	{ .compatible = "brcm,flexrm-mbox", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, flexrm_mbox_of_match);
+
+static struct platform_driver flexrm_mbox_driver = {
+	.driver = {
+		.name = "brcm-flexrm-mbox",
+		.of_match_table = flexrm_mbox_of_match,
+	},
+	.probe		= flexrm_mbox_probe,
+	.remove		= flexrm_mbox_remove,
+};
+module_platform_driver(flexrm_mbox_driver);
+
+MODULE_AUTHOR("Anup Patel <anup.patel@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom FlexRM mailbox driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mailbox/brcm-message.h b/include/linux/mailbox/brcm-message.h
index 6b55c93..f03bbdd 100644
--- a/include/linux/mailbox/brcm-message.h
+++ b/include/linux/mailbox/brcm-message.h
@@ -16,6 +16,7 @@
 
 enum brcm_message_type {
 	BRCM_MESSAGE_UNKNOWN = 0,
+	BRCM_MESSAGE_BATCH,
 	BRCM_MESSAGE_SPU,
 	BRCM_MESSAGE_SBA,
 	BRCM_MESSAGE_MAX,
@@ -29,18 +30,21 @@ struct brcm_sba_command {
 #define BRCM_SBA_CMD_HAS_RESP		BIT(3)
 #define BRCM_SBA_CMD_HAS_OUTPUT		BIT(4)
 	u64 flags;
-	dma_addr_t input;
-	size_t input_len;
 	dma_addr_t resp;
 	size_t resp_len;
-	dma_addr_t output;
-	size_t output_len;
+	dma_addr_t data;
+	size_t data_len;
 };
 
 struct brcm_message {
 	enum brcm_message_type type;
 	union {
 		struct {
+			struct brcm_message *msgs;
+			unsigned int msgs_queued;
+			unsigned int msgs_count;
+		} batch;
+		struct {
 			struct scatterlist *src;
 			struct scatterlist *dst;
 		} spu;
-- 
2.7.4

^ permalink raw reply related

* [PATCH 0/2] Broadcom FlexRM ring manager support
From: Anup Patel @ 2016-11-25  4:35 UTC (permalink / raw)
  To: linux-arm-kernel

The Broadcom FlexRM ring manager provides producer-consumer style
ring interface for offload engines on Broadcom iProc SoCs. We can
have one or more instances of Broadcom FlexRM ring manager in a SoC.

This patchset adds a mailbox driver for Broadcom FlexRM ring manager
which can be used by offload engine drivers as mailbox clients.

The Broadcom FlexRM mailbox driver is feature complete for RAID and
Crypto offload engines. We will have incremental patches in-future
for ring-level statistics using debugfs and minor optimizations.

This patchset is based on Linux-4.9-rc6 and it is also available
at flexrm-v1 branch of https://github.com/Broadcom/arm64-linux.git

Anup Patel (2):
  mailbox: Add driver for Broadcom FlexRM ring manager
  dt-bindings: Add DT bindings info for FlexRM mailbox driver

 .../bindings/mailbox/brcm,flexrm-mbox.txt          |  60 ++
 drivers/mailbox/Kconfig                            |  11 +
 drivers/mailbox/Makefile                           |   2 +
 drivers/mailbox/mailbox-flexrm/Makefile            |   6 +
 drivers/mailbox/mailbox-flexrm/flexrm-desc.c       | 764 +++++++++++++++++++
 drivers/mailbox/mailbox-flexrm/flexrm-desc.h       |  47 ++
 drivers/mailbox/mailbox-flexrm/flexrm-main.c       | 829 +++++++++++++++++++++
 include/linux/mailbox/brcm-message.h               |  12 +-
 8 files changed, 1727 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mailbox/brcm,flexrm-mbox.txt
 create mode 100644 drivers/mailbox/mailbox-flexrm/Makefile
 create mode 100644 drivers/mailbox/mailbox-flexrm/flexrm-desc.c
 create mode 100644 drivers/mailbox/mailbox-flexrm/flexrm-desc.h
 create mode 100644 drivers/mailbox/mailbox-flexrm/flexrm-main.c

-- 
2.7.4

^ permalink raw reply

* [PATCH] reset: hisilicon: add a polarity cell for reset line specifier
From: Jiancheng Xue @ 2016-11-25  3:45 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <03bb74da-6b24-ee4c-307d-3b3f10ac2f7a@hisilicon.com>


On 2016/11/21 10:58, Jiancheng Xue wrote:
> Hi Philipp,
> 
>> On 2016/11/15 18:43, Philipp Zabel wrote:
>>> Hi Jiancheng,
>>>
>>> Am Dienstag, den 15.11.2016, 15:09 +0800 schrieb Jiancheng Xue:
>>>> Add a polarity cell for reset line specifier. If the reset line
>>>> is asserted when the register bit is 1, the polarity is
>>>> normal. Otherwise, it is inverted.
>>>>
>>>> Signed-off-by: Jiancheng Xue <xuejiancheng@hisilicon.com>
>>>> ---
>> Thank you very much for replying so soon.
>>
>> Please allow me to decribe the reason why this patch exists first.
>> All bits in the reset controller were designed to be active-high.
>> But in a recent chip only one bit was implemented to be active-low :(
>>
>>>>  .../devicetree/bindings/clock/hisi-crg.txt         | 11 ++++---
>>>>  arch/arm/boot/dts/hi3519.dtsi                      |  2 +-
>>>>  drivers/clk/hisilicon/reset.c                      | 36 ++++++++++++++++------
>>>>  3 files changed, 33 insertions(+), 16 deletions(-)
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/clock/hisi-crg.txt b/Documentation/devicetree/bindings/clock/hisi-crg.txt
>>>> index e3919b6..fcbb4f3 100644
>>>> --- a/Documentation/devicetree/bindings/clock/hisi-crg.txt
>>>> +++ b/Documentation/devicetree/bindings/clock/hisi-crg.txt
>>>> @@ -25,19 +25,20 @@ to specify the clock which they consume.
>>>>  
>>>>  All these identifier could be found in <dt-bindings/clock/hi3519-clock.h>.
>>>>  
>>>> -- #reset-cells: should be 2.
>>>> +- #reset-cells: should be 3.
>>>>  
>>>>  A reset signal can be controlled by writing a bit register in the CRG module.
>>>> -The reset specifier consists of two cells. The first cell represents the
>>>> +The reset specifier consists of three cells. The first cell represents the
>>>>  register offset relative to the base address. The second cell represents the
>>>> -bit index in the register.
>>>> +bit index in the register. The third cell represents the polarity of the reset
>>>> +line (0 for normal, 1 for inverted).
>>>
> #reset-cells: Should be 2 if compatilbe string is "hisilicon,hi3519-crg". Should be 3 otherwise.
> 	      A reset signal can be controlled by writing a bit register in the CRG module.
> 	      The reset specifier consists of two or three cells. The first cell represents the
> 	      register offset relative to the base address. The second cell represents the
> 	      bit index in the register.The third cell represents the polarity of the reset
> 	      line (0 for active-high, 1 for active-low).
> 
> If I change the binding like this, can it be accepted?
> 
Hi Philipp,

Could you give me more suggestions about this?  If you really don't like changing the
reset-cells like this, I can modify the patch according to your suggestions.
Thank you.

Regards,
Jiancheng

^ permalink raw reply

* [PATCH v3] clk: qoriq: added ls1012a clock configuration
From: Y.T. Tang @ 2016-11-25  3:32 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <DB5PR0401MB1928925ACE6E6E066961C64A91B60@DB5PR0401MB1928.eurprd04.prod.outlook.com>

Hi Scott,

> -----Original Message-----
> From: Scott Wood
> Sent: Thursday, November 24, 2016 4:21 PM
> To: Y.T. Tang <yuantian.tang@nxp.com>; mturquette at baylibre.com
> Cc: sboyd at codeaurora.org; linux-clk at vger.kernel.org; linux-
> kernel at vger.kernel.org; linux-arm-kernel at lists.infradead.org
> Subject: Re: [PATCH v3] clk: qoriq: added ls1012a clock configuration
> 
> On 11/23/2016 08:50 PM, yuantian.tang at nxp.com wrote:
> > From: Tang Yuantian <Yuantian.Tang@nxp.com>
> >
> > Signed-off-by: Tang Yuantian <yuantian.tang@nxp.com>
> > ---
> > v3:
> >   - rebased to latest kernel and re-sorted the code
> [snip]
> > @@ -1316,6 +1334,7 @@ CLK_OF_DECLARE(qoriq_clockgen_2,
> > "fsl,qoriq-clockgen-2.0", clockgen_init);
> > CLK_OF_DECLARE(qoriq_clockgen_ls1021a, "fsl,ls1021a-clockgen",
> > clockgen_init);  CLK_OF_DECLARE(qoriq_clockgen_ls1043a,
> > "fsl,ls1043a-clockgen", clockgen_init);
> > CLK_OF_DECLARE(qoriq_clockgen_ls1046a, "fsl,ls1046a-clockgen",
> > clockgen_init);
> > +CLK_OF_DECLARE(qoriq_clockgen_ls1012a, "fsl,ls1012a-clockgen",
> > +clockgen_init);
> >  CLK_OF_DECLARE(qoriq_clockgen_ls2080a, "fsl,ls2080a-clockgen",
> > clockgen_init);
> 
> You need a better sorting algorithm. :-P
> 
I don't think so. This time, each code snip goes right after ls1046a.
The sorting 'algorithm' is best ever. :)

Regards,
Yuantian
 
> -Scott

^ permalink raw reply

* [linux-sunxi] [PATCH] clk: sunxi-ng: fix PLL_CPUX adjusting on H3
From: Chen-Yu Tsai @ 2016-11-25  3:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161125002852.18097-1-megous@megous.com>

On Fri, Nov 25, 2016 at 8:28 AM,  <megous@megous.com> wrote:
> From: Ondrej Jirman <megous@megous.com>
>
> When adjusting PLL_CPUX on H3, the PLL is temporarily driven
> too high, and the system becomes unstable (oopses or hangs).
>
> Add a notifier to avoid this situation by temporarily switching
> to a known stable 24 MHz oscillator.
>
> Signed-off-by: Ondrej Jirman <megous@megous.com>
> Tested-by: Lutz Sammer <johns98@gmx.net>

A Fixes tag would be nice. Otherwise,

Acked-by: Chen-Yu Tsai <wens@csie.org>

^ permalink raw reply

* [PATCH v4 1/6] arm64: arch_timer: Add device tree binding for hisilicon-161601 erratum
From: Ding Tianhong @ 2016-11-25  3:18 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <58379AA4.6030401@linaro.org>

OK

On 2016/11/25 9:57, Hanjun Guo wrote:
> Hi John,
> 
> On 11/24/2016 08:12 PM, John Garry wrote:
>> On 21/11/2016 12:49, Ding Tianhong wrote:
>>> Ping....
>>
>> Hi,
>>
>> was there a cover letter for 0/6? I never saw it.
> 
> There isn't a cover letter, do we need to add it and
> resend (to make thing clear)?
> 
> Thanks
> Hanjun
> 
> .
> 

^ permalink raw reply

* [RFC v2: PATCH 1/2] dt-bindings: Document the hi3660 reset bindings
From: zhangfei @ 2016-11-25  3:04 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1479979605.2472.4.camel@pengutronix.de>



On 2016?11?24? 17:26, Philipp Zabel wrote:
> Am Mittwoch, den 23.11.2016, 16:07 +0800 schrieb Zhangfei Gao:
>> Add DT bindings documentation for hi3660 SoC reset controller.
>>
>> Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org>
>> ---
>>   .../bindings/reset/hisilicon,hi3660-reset.txt      | 51 ++++++++++++++++++++++
>>   1 file changed, 51 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/reset/hisilicon,hi3660-reset.txt
>>
>> diff --git a/Documentation/devicetree/bindings/reset/hisilicon,hi3660-reset.txt b/Documentation/devicetree/bindings/reset/hisilicon,hi3660-reset.txt
>> new file mode 100644
>> index 0000000..250daf2
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/reset/hisilicon,hi3660-reset.txt
>> @@ -0,0 +1,51 @@
>> +Hisilicon System Reset Controller
>> +======================================
>> +
>> +Please also refer to reset.txt in this directory for common reset
>> +controller binding usage.
>> +
>> +The reset controller registers are part of the system-ctl block on
>> +hi3660 SoC.
>> +
>> +Required properties:
>> +- compatible: should be
>> +		 "hisilicon,hi3660-reset"
>> +- #reset-cells: 1, see below
>> +- hisi,rst-syscon: phandle of the reset's syscon.
>> +- hisi,reset-bits: Contains the reset control register information
>> +		  Should contain 2 cells for each reset exposed to
>> +		  consumers, defined as:
>> +			Cell #1 : offset from the syscon register base
>> +			Cell #2 : bits position of the control register
>> +
>> +Example:
>> +	iomcu: iomcu at ffd7e000 {
>> +		compatible = "hisilicon,hi3660-iomcu", "syscon";
>> +		reg = <0x0 0xffd7e000 0x0 0x1000>;
>> +	};
>> +
>> +	iomcu_rst: iomcu_rst_controller {
> This should be
> 	iomcu_rst: reset-controller {
By the way, could I keep the original name?
Since there will be build error if several nodes use the same name.
like:
-               iomcu_rst: iomcu_rst_controller {
+               iomcu_rst: reset-controller {

-               crg_rst: crg_rst_controller {
+               crg_rst: reset-controller {

Thanks

^ permalink raw reply

* [RFC v2: PATCH 1/2] dt-bindings: Document the hi3660 reset bindings
From: Zhangfei Gao @ 2016-11-25  2:38 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1479888476-13138-2-git-send-email-zhangfei.gao@linaro.org>

On Wed, Nov 23, 2016 at 4:07 PM, Zhangfei Gao <zhangfei.gao@linaro.org> wrote:
> Add DT bindings documentation for hi3660 SoC reset controller.
>
> Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org>
> ---
>  .../bindings/reset/hisilicon,hi3660-reset.txt      | 51 ++++++++++++++++++++++
>  1 file changed, 51 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/reset/hisilicon,hi3660-reset.txt
>
> diff --git a/Documentation/devicetree/bindings/reset/hisilicon,hi3660-reset.txt b/Documentation/devicetree/bindings/reset/hisilicon,hi3660-reset.txt
> new file mode 100644
> index 0000000..250daf2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/reset/hisilicon,hi3660-reset.txt
> @@ -0,0 +1,51 @@
> +Hisilicon System Reset Controller
> +======================================
> +
> +Please also refer to reset.txt in this directory for common reset
> +controller binding usage.
> +
> +The reset controller registers are part of the system-ctl block on
> +hi3660 SoC.
> +
> +Required properties:
> +- compatible: should be
> +                "hisilicon,hi3660-reset"
> +- #reset-cells: 1, see below
> +- hisi,rst-syscon: phandle of the reset's syscon.
> +- hisi,reset-bits: Contains the reset control register information
> +                 Should contain 2 cells for each reset exposed to
> +                 consumers, defined as:
> +                       Cell #1 : offset from the syscon register base
> +                       Cell #2 : bits position of the control register
> +
> +Example:
> +       iomcu: iomcu at ffd7e000 {
> +               compatible = "hisilicon,hi3660-iomcu", "syscon";
> +               reg = <0x0 0xffd7e000 0x0 0x1000>;
> +       };
> +
> +       iomcu_rst: iomcu_rst_controller {
> +               compatible = "hisilicon,hi3660-reset";
> +               #reset-cells = <1>;
> +               hisi,rst-syscon = <&iomcu>;
> +               hisi,reset-bits = <0x20 0x8             /* 0: i2c0 */
> +                                  0x20 0x10            /* 1: i2c1 */
> +                                  0x20 0x20            /* 2: i2c2 */
> +                                  0x20 0x8000000>;     /* 3: i2c6 */
> +       };
> +
> +Specifying reset lines connected to IP modules
> +==============================================
> +example:
> +
> +        i2c0: i2c at ..... {
> +                ...
> +               resets = <&iomcu_rst 0>;
> +                ...
> +        };
> +
> +       i2c1: i2c at ..... {
> +                ...
> +               resets = <&iomcu_rst 1>;
> +                ...
> +        };
> --
> 2.7.4


Sorry, missing cc when send-email.
Help take a look.

Thanks

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox