Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] arm64: kpti: Fix the interaction between ASID switching and software PAN
From: James Morse @ 2018-01-12 15:19 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20180112123043.43535-1-catalin.marinas@arm.com>

Hi Catalin,

On 12/01/18 12:30, Catalin Marinas wrote:
> With ARM64_SW_TTBR0_PAN enabled, the exception entry code checks the
> active ASID to decide whether user access was enabled (non-zero ASID)
> when the exception was taken. On return from exception, if user access
> was previously disabled, it re-instates TTBR0_EL1 from the per-thread
> saved value (updated in switch_mm() or efi_set_pgd()).
> 
> Commit 7655abb95386 ("arm64: mm: Move ASID from TTBR0 to TTBR1") makes a
> TTBR0_EL1 + ASID switching non-atomic. Subsequently, commit 27a921e75711
> ("arm64: mm: Fix and re-enable ARM64_SW_TTBR0_PAN") changes the
> __uaccess_ttbr0_disable() function and asm macro to first write the
> reserved TTBR0_EL1 followed by the ASID=0 update in TTBR1_EL1. If an
> exception occurs between these two, the exception return code will
> re-instate a valid TTBR0_EL1. Similar scenario can happen in
> cpu_switch_mm() between setting the reserved TTBR0_EL1 and the ASID
> update in cpu_do_switch_mm().
> 
> This patch reverts the entry.S check for ASID == 0 to TTBR0_EL1 and
> disables the interrupts around the TTBR0_EL1 and ASID switching code in
> __uaccess_ttbr0_disable(). It also ensures that, when returning from the
> EFI runtime services, efi_set_pgd() doesn't leave a non-zero ASID in
> TTBR1_EL1.
> 
> As a safety measure, __uaccess_ttbr0_enable() always masks out any
> existing non-zero ASID TTBR1_EL1 before writing in the new ASID.
> 
> Fixes: 27a921e75711 ("arm64: mm: Fix and re-enable ARM64_SW_TTBR0_PAN")
> Cc: Will Deacon <will.deacon@arm.com>
> Cc: James Morse <james.morse@arm.com>
> Reported-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
> Co-developed-by: Marc Zyngier <marc.zyngier@arm.com>
> Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>

I saw sporadic assert-failures and translation faults running hackbench in a
loop on Seattle with software PAN. I think this explains (and fixes) it.

Tested-by: James Morse <james.morse@arm.com>
Reviewed-by: James Morse <james.morse@arm.com>


Thanks,

James

^ permalink raw reply

* [PATCH 0/5] sha3 fixes and new implementation for arm64
From: Ard Biesheuvel @ 2018-01-12 15:13 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20180112131522.25663-1-ard.biesheuvel@linaro.org>

On 12 January 2018 at 13:15, Ard Biesheuvel <ard.biesheuvel@linaro.org> wrote:
> Add an implementation of SHA3 to arm64 using the new special instructions (#4)
>
> In preparation of that, fix a bug in the SHA3 and refactor it a bit so it
> can serve as a fallback for the other code. Also, add some new test vectors
> to get better test coverage.
>
> Ard Biesheuvel (5):
>   crypto/generic: sha3 - fixes for alignment and big endian operation
>   crypto/generic: sha3 - simplify code
>   crypto/generic: sha3 - export init/update/final routines
>   crypto/arm64: sha3 - new implementation based on special instructions

Forgot to mention: this is an RFT for patch #4, as it has not been
validated against a real implementation, only against my own QEMU
code.

>   crypto/testmgr: sha3 - add new testcases
>
>  arch/arm64/crypto/Kconfig        |   6 +
>  arch/arm64/crypto/Makefile       |   3 +
>  arch/arm64/crypto/sha3-ce-core.S | 224 ++++++++
>  arch/arm64/crypto/sha3-ce-glue.c | 156 ++++++
>  crypto/sha3_generic.c            | 198 +++----
>  crypto/testmgr.h                 | 550 ++++++++++++++++++++
>  include/crypto/sha3.h            |   6 +-
>  7 files changed, 1012 insertions(+), 131 deletions(-)
>  create mode 100644 arch/arm64/crypto/sha3-ce-core.S
>  create mode 100644 arch/arm64/crypto/sha3-ce-glue.c
>
> --
> 2.11.0
>

^ permalink raw reply

* [rfc] Sound support for n9
From: Pavel Machek @ 2018-01-12 15:00 UTC (permalink / raw)
  To: linux-arm-kernel

Hi!

Binding documentation is pending, and code will need to be updated to
match it.

I guess burst_bclkdiv should be handled as int, not as u8?

SPDX is modern these days.

Anything else that needs to be fixed?

Best regards,
								Pavel


diff --git a/include/sound/n9.h b/include/sound/n9.h
new file mode 100644
index 0000000..b14ddf0
--- /dev/null
+++ b/include/sound/n9.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2009 Nokia
+
+#ifndef _N9_H_
+#define _N9_H_
+
+struct dfl61audio_hsmic_event {
+	void *private;
+	void (*event)(void *priv, bool on);
+};
+
+void dfl61_jack_report(int status);
+int dfl61_request_hsmicbias(bool enable);
+void dfl61_register_hsmic_event_cb(struct dfl61audio_hsmic_event *event);
+int dfl61_request_hp_enable(bool enable);
+#endif
diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c
index 5b94a15..3823bcc 100644
--- a/sound/soc/codecs/tlv320dac33.c
+++ b/sound/soc/codecs/tlv320dac33.c
@@ -30,6 +32,7 @@
 #include <linux/interrupt.h>
 #include <linux/gpio.h>
 #include <linux/regulator/consumer.h>
+#include <linux/of_gpio.h>
 #include <linux/slab.h>
 #include <sound/core.h>
 #include <sound/pcm.h>
@@ -1484,16 +1487,11 @@ static struct snd_soc_dai_driver dac33_dai = {
 static int dac33_i2c_probe(struct i2c_client *client,
 			   const struct i2c_device_id *id)
 {
-	struct tlv320dac33_platform_data *pdata;
+	struct tlv320dac33_platform_data *pdata = client->dev.platform_data;
 	struct tlv320dac33_priv *dac33;
+	struct device_node *np = client->dev.of_node;
 	int ret, i;
 
-	if (client->dev.platform_data == NULL) {
-		dev_err(&client->dev, "Platform data not set\n");
-		return -ENODEV;
-	}
-	pdata = client->dev.platform_data;
-
 	dac33 = devm_kzalloc(&client->dev, sizeof(struct tlv320dac33_priv),
 			     GFP_KERNEL);
 	if (dac33 == NULL)
@@ -1505,10 +1503,26 @@ static int dac33_i2c_probe(struct i2c_client *client,
 
 	i2c_set_clientdata(client, dac33);
 
-	dac33->power_gpio = pdata->power_gpio;
-	dac33->burst_bclkdiv = pdata->burst_bclkdiv;
-	dac33->keep_bclk = pdata->keep_bclk;
-	dac33->mode1_latency = pdata->mode1_latency;
+	if (pdata) {
+		dac33->power_gpio = pdata->power_gpio;
+		dac33->burst_bclkdiv = pdata->burst_bclkdiv;
+		dac33->keep_bclk = pdata->keep_bclk;
+		dac33->mode1_latency = pdata->mode1_latency;
+	} else if (np) {
+		ret = of_get_named_gpio(np, "power-gpio", 0);
+		if (ret >= 0)
+			dac33->power_gpio = ret;
+		else
+			dac33->power_gpio = -1;
+
+		if (of_property_read_bool(np, "keep-bclk"))
+			dac33->keep_bclk = true;
+
+		of_property_read_u8(np, "burst-bclkdiv", &dac33->burst_bclkdiv);
+	} else {
+		dev_err(&client->dev, "Platform data not set\n");
+		return -ENODEV;
+	}
 	if (!dac33->mode1_latency)
 		dac33->mode1_latency = 10000; /* 10ms */
 	dac33->irq = client->irq;
@@ -1574,9 +1588,16 @@ static const struct i2c_device_id tlv320dac33_i2c_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, tlv320dac33_i2c_id);
 
+static const struct of_device_id tlv320dac33_of_match[] = {
+	{ .compatible = "ti,tlv320dac33", },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, tlv320dac33_of_match);
+
 static struct i2c_driver tlv320dac33_i2c_driver = {
 	.driver = {
 		.name = "tlv320dac33-codec",
+		.of_match_table = of_match_ptr(tlv320dac33_of_match),
 	},
 	.probe		= dac33_i2c_probe,
 	.remove		= dac33_i2c_remove,
diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig
index f5451c7..2772414 100644
--- a/sound/soc/omap/Kconfig
+++ b/sound/soc/omap/Kconfig
@@ -47,6 +47,18 @@ config SND_OMAP_SOC_RX51
 	  Say Y if you want to add support for SoC audio on Nokia N900
 	  cellphone.
 
+config SND_OMAP_SOC_N9
+	tristate "SoC Audio support for Nokia N9/N950"
+	depends on SND_OMAP_SOC && ARM && I2C
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TWL4030
+	select SND_SOC_TLV320DAC33
+	select SND_SOC_TPA6130A2
+	select SND_SOC_WL1273 if MFD_WL1273_CORE
+	depends on GPIOLIB
+	help
+	  Say Y if you want to add support for SoC audio on Nokia N9/N950.
+
 config SND_OMAP_SOC_AMS_DELTA
 	tristate "SoC Audio support for Amstrad E3 (Delta) videophone"
 	depends on SND_OMAP_SOC && MACH_AMS_DELTA && TTY
diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile
index a6785dc..07efd21 100644
--- a/sound/soc/omap/Makefile
+++ b/sound/soc/omap/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_SND_OMAP_SOC_HDMI_AUDIO) += snd-soc-omap-hdmi-audio.o
 # OMAP Machine Support
 snd-soc-n810-objs := n810.o
 snd-soc-rx51-objs := rx51.o
+snd-soc-n9-objs := n9.o
 snd-soc-ams-delta-objs := ams-delta.o
 snd-soc-osk5912-objs := osk5912.o
 snd-soc-am3517evm-objs := am3517evm.o
@@ -24,6 +25,7 @@ snd-soc-omap3pandora-objs := omap3pandora.o
 
 obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o
 obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o
+obj-$(CONFIG_SND_OMAP_SOC_N9) += snd-soc-n9.o
 obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o
 obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o
 obj-$(CONFIG_SND_OMAP_SOC_AM3517EVM) += snd-soc-am3517evm.o
diff --git a/sound/soc/omap/n9.c b/sound/soc/omap/n9.c
new file mode 100644
index 0000000..db61463
--- /dev/null
+++ b/sound/soc/omap/n9.c
@@ -0,0 +1,748 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// n9.c  --  SoC audio for Nokia N9/N950
+//
+// Copyright (C) 2008 - 2009 Nokia Corporation
+//
+// Contact: Peter Ujfalusi <peter.ujfalusi@ti.com>
+//          Eduardo Valentin <eduardo.valentin@nokia.com>
+//          Jarkko Nikula <jarkko.nikula@bitmer.com>
+//
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <linux/platform_data/asoc-ti-mcbsp.h>
+#include <linux/mfd/twl4030-audio.h>
+#include "../codecs/tlv320dac33.h"
+#include "../codecs/tpa6130a2.h"
+#include "../codecs/wl1273.h"
+
+#include <asm/mach-types.h>
+
+#include <sound/n9.h>
+#include "omap-mcbsp.h"
+#include "mcbsp.h"
+
+#define JACK_REPORT_MASK	(SND_JACK_MECHANICAL | SND_JACK_AVOUT | \
+							 SND_JACK_HEADSET)
+
+struct dfl61wl1273_audio_pdata {
+	struct gpio_desc *power_gpio;
+};
+
+struct dfl61twl_audio_pdata {
+	struct gpio_desc *speaker_amp_gpio;
+};
+
+static int dfl61dac33_interconnect_enable(int);
+static struct snd_soc_card dfl61dac33_sound_card;
+static struct snd_soc_card dfl61twl_sound_card;
+static struct snd_soc_jack dfl61_jack;
+static struct dfl61audio_hsmic_event *hsmic_event;
+
+static struct snd_soc_component *find_component(struct snd_soc_card *card) {
+	struct snd_soc_component *component;
+
+	if (list_empty(&card->component_dev_list)) {
+		pr_err("Can't find codec for %s\n", card->name);
+		return NULL;
+	}
+
+	component = list_entry(card->component_dev_list.next,
+			       struct snd_soc_component, card_list);
+
+	return component;
+}
+
+/* TWL4030 */
+void dfl61_jack_report(int status)
+{
+	if (dfl61_jack.card)
+		snd_soc_jack_report(&dfl61_jack, status, JACK_REPORT_MASK);
+	else
+		pr_err("twl4030: Cannot report jack status");
+}
+EXPORT_SYMBOL_GPL(dfl61_jack_report);
+
+int dfl61_request_hsmicbias(bool enable)
+{
+	struct snd_soc_component *component;
+	struct snd_soc_dapm_context *dapm;
+	bool lock = false;
+	int ret;
+
+	if (!dfl61twl_sound_card.instantiated) {
+		pr_warn("twl4030: sound card not instantiated yet");
+		return -EPROBE_DEFER;
+	}
+
+	component = find_component(&dfl61twl_sound_card);
+	if (!component)
+		return -ENODEV;
+
+	dapm = snd_soc_component_get_dapm(component);
+	if (!dapm) {
+		pr_err("twl4030: Cannot set hsmicbias yet");
+		return -ENODEV;
+	}
+
+	mutex_lock(&dfl61twl_sound_card.dapm_mutex);
+	lock = true;
+
+	if (enable)
+		snd_soc_dapm_force_enable_pin_unlocked(dapm, "Headset Mic Bias");
+	else
+		snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Mic Bias");
+
+	ret = snd_soc_dapm_sync_unlocked(dapm);
+
+	if (lock)
+		mutex_unlock(&dfl61twl_sound_card.dapm_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(dfl61_request_hsmicbias);
+
+void dfl61_register_hsmic_event_cb(struct dfl61audio_hsmic_event *event)
+{
+	hsmic_event = event;
+}
+EXPORT_SYMBOL(dfl61_register_hsmic_event_cb);
+
+static int dfl61twl_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	unsigned int fmt;
+	int r;
+
+	switch (params_channels(params)) {
+	case 2: /* Stereo I2S mode */
+		fmt =	SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_CBM_CFM;
+	case 4: /* Four channel TDM mode */
+		fmt =	SND_SOC_DAIFMT_DSP_A |
+			SND_SOC_DAIFMT_IB_NF |
+			SND_SOC_DAIFMT_CBM_CFM;
+		break;
+	default:
+		return -EINVAL;
+	}
+	/* Set codec DAI configuration */
+	r = snd_soc_dai_set_fmt(rtd->codec_dai, fmt);
+	if (r < 0) {
+		pr_err("Can't set codec DAI configuration for twl4030: %d\n", r);
+		return r;
+	}
+
+	/* Set cpu DAI configuration */
+	r = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt);
+	if (r < 0) {
+		pr_err("Can't set cpu DAI configuration for twl4030: %d\n", r);
+		return r;
+	}
+
+	return 0;
+}
+
+static int dfl61twl_spk_event(struct snd_soc_dapm_widget *w,
+			  struct snd_kcontrol *k, int event)
+{
+	struct snd_soc_dapm_context *dapm = w->dapm;
+	struct snd_soc_card *card = dapm->card;
+	struct dfl61twl_audio_pdata *pdata = snd_soc_card_get_drvdata(card);
+
+	gpiod_set_raw_value_cansleep(pdata->speaker_amp_gpio,
+				     !!SND_SOC_DAPM_EVENT_ON(event));
+
+	return 0;
+}
+
+static int dfl61twl_tlv320dac33_event(struct snd_soc_dapm_widget *w,
+			  struct snd_kcontrol *k, int event)
+{
+	int r;
+
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		r = dfl61dac33_interconnect_enable(1);
+	else
+		r = dfl61dac33_interconnect_enable(0);
+
+	return r;
+}
+
+static int dfl61twl_hsmic_event(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *k, int event)
+{
+	if (!hsmic_event || !hsmic_event->event)
+		return 0;
+
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		hsmic_event->event(hsmic_event->private, 1);
+	else
+		hsmic_event->event(hsmic_event->private, 0);
+
+	return 0;
+}
+
+/* DAPM widgets and routing for TWL4030 */
+static const struct snd_soc_dapm_widget dfl61twl_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Ext Spk", dfl61twl_spk_event),
+	SND_SOC_DAPM_SPK("Earpiece", NULL),
+	SND_SOC_DAPM_SPK("HAC", NULL),
+	SND_SOC_DAPM_SPK("Vibra", NULL),
+	SND_SOC_DAPM_SPK("DAC33 interconnect", dfl61twl_tlv320dac33_event),
+
+	SND_SOC_DAPM_MIC("Digital Mic", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", dfl61twl_hsmic_event),
+
+	SND_SOC_DAPM_LINE("FMRX Left Line-in", NULL),
+	SND_SOC_DAPM_LINE("FMRX Right Line-in", NULL),
+};
+
+static const struct snd_soc_dapm_route dfl61twl_audio_map[] = {
+	{"Ext Spk", NULL, "PREDRIVER"},
+	{"Earpiece", NULL, "EARPIECE"},
+	{"HAC", NULL, "HFL"},
+	{"Vibra", NULL, "VIBRA"},
+	{"DAC33 interconnect", NULL, "PREDRIVEL"},
+
+	{"DIGIMIC0", NULL, "Digital Mic"},
+	{"Digital Mic", NULL, "Mic Bias 1"},
+
+	{"HSMIC", NULL, "Headset Mic"},
+	{"Headset Mic", NULL, "Headset Mic Bias"},
+
+	{"AUXL", NULL, "FMRX Left Line-in"},
+	{"AUXR", NULL, "FMRX Right Line-in"},
+};
+
+/* Pre DAC routings for the twl4030 codec */
+static const char *twl4030_predacl1_texts[] = {
+	"SDRL1", "SDRM1", "SDRL2", "SDRM2",
+};
+static const char *twl4030_predacr1_texts[] = {
+	"SDRR1", "SDRM1", "SDRR2", "SDRM2"
+};
+static const char *twl4030_predacl2_texts[] = {"SDRL2", "SDRM2"};
+static const char *twl4030_predacr2_texts[] = {"SDRR2", "SDRM2"};
+
+static const struct soc_enum twl4030_predacl1_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_RX_PATH_SEL, 2,
+			ARRAY_SIZE(twl4030_predacl1_texts),
+			twl4030_predacl1_texts);
+
+static const struct soc_enum twl4030_predacr1_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_RX_PATH_SEL, 0,
+			ARRAY_SIZE(twl4030_predacr1_texts),
+			twl4030_predacr1_texts);
+
+static const struct soc_enum twl4030_predacl2_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_RX_PATH_SEL, 5,
+			ARRAY_SIZE(twl4030_predacl2_texts),
+			twl4030_predacl2_texts);
+
+static const struct soc_enum twl4030_predacr2_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_RX_PATH_SEL, 4,
+			ARRAY_SIZE(twl4030_predacr2_texts),
+			twl4030_predacr2_texts);
+
+static const struct snd_kcontrol_new dfl61twl_controls[] = {
+	/* Mux controls before the DACs */
+	SOC_ENUM("DACL1 Playback Mux", twl4030_predacl1_enum),
+	SOC_ENUM("DACR1 Playback Mux", twl4030_predacr1_enum),
+	SOC_ENUM("DACL2 Playback Mux", twl4030_predacl2_enum),
+	SOC_ENUM("DACR2 Playback Mux", twl4030_predacr2_enum),
+};
+
+static int dfl61twl_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	int r;
+
+	/* Create jack for accessory reporting */
+	r = snd_soc_card_jack_new(&dfl61twl_sound_card, "Jack",
+				JACK_REPORT_MASK , &dfl61_jack, NULL, 0);
+	if (r) {
+		pr_err("Failed to add Jack\n");
+		return r;
+	}
+
+	snd_soc_add_codec_controls(rtd->codec, dfl61twl_controls,
+				ARRAY_SIZE(dfl61twl_controls));
+
+	if (omap_mcbsp_st_add_controls(rtd, 3))
+		dev_dbg(rtd->codec->dev, "Unable to set Sidetone for McBSP3\n");
+
+	mcbsp->dma_op_mode = MCBSP_DMA_MODE_THRESHOLD;
+
+	return 0;
+}
+
+static struct snd_soc_ops dfl61twl_ops = {
+	.hw_params = dfl61twl_hw_params,
+};
+
+/* TLV320DAC33 */
+static int dfl61dac33_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int r;
+
+	/* Set codec DAI configuration */
+	r = snd_soc_dai_set_fmt(rtd->codec_dai,
+				SND_SOC_DAIFMT_LEFT_J |
+				SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBM_CFM);
+	if (r < 0) {
+		pr_err("Can't set codec DAI configuration for tlv320dac33: %d\n", r);
+		return r;
+	}
+
+	/* Set cpu DAI configuration */
+	r = snd_soc_dai_set_fmt(rtd->cpu_dai,
+				SND_SOC_DAIFMT_LEFT_J |
+				SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBM_CFM);
+	if (r < 0) {
+		pr_err("Can't set cpu DAI configuration for tlv320dac33: %d\n", r);
+		return r;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	r = snd_soc_dai_set_sysclk(rtd->codec_dai, TLV320DAC33_SLEEPCLK, 32768,
+					    SND_SOC_CLOCK_IN);
+	if (r < 0) {
+		pr_err("Can't set codec system clock for tlv320dac33\n");
+		return r;
+	}
+
+	return 0;
+}
+
+static int dfl61dac33_interconnect_enable(int enable)
+{
+	struct snd_soc_component *component =
+		find_component(&dfl61dac33_sound_card);
+	struct snd_soc_dapm_context *dapm;
+	bool lock = false;
+
+	if (!component)
+		return -ENODEV;
+
+	dapm = snd_soc_component_get_dapm(component);
+
+	mutex_lock(&dapm->card->dapm_mutex);
+		lock = true;
+
+	if (enable)
+		snd_soc_dapm_enable_pin_unlocked(dapm, "twl4030 interconnect");
+	else
+		snd_soc_dapm_disable_pin_unlocked(dapm, "twl4030 interconnect");
+
+	snd_soc_dapm_sync_unlocked(dapm);
+
+	if (lock)
+		mutex_unlock(&dapm->card->dapm_mutex);
+
+	return 0;
+}
+
+static void dfl61dac33_hp_enable(struct snd_soc_component *component, int enable)
+{
+	struct snd_soc_dapm_context *dapm =
+		snd_soc_component_get_dapm(component);
+
+	snd_soc_dapm_mutex_lock(dapm);
+
+	if (enable) {
+		snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone");
+	} else {
+		snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone");
+	}
+
+	snd_soc_dapm_sync_unlocked(dapm);
+
+	snd_soc_dapm_mutex_unlock(dapm);
+}
+
+int dfl61_request_hp_enable(bool enable)
+{
+	struct snd_soc_component *component =
+		find_component(&dfl61dac33_sound_card);
+
+	if (!component) {
+		pr_err("dfl61-request_hp_enable");
+		return -ENODEV;
+	}
+
+	dfl61dac33_hp_enable(component, enable);
+
+	return 0;
+}
+EXPORT_SYMBOL(dfl61_request_hp_enable);
+
+static const struct snd_kcontrol_new dfl61dac33_controls[] = {
+	SOC_DAPM_PIN_SWITCH("Headphone"),
+};
+
+static const struct snd_soc_dapm_widget dfl61dac33_dapm_widgets[] = {
+	/* Outputs */
+	SND_SOC_DAPM_LINE("FMTX_L Line Out", NULL),
+	SND_SOC_DAPM_LINE("FMTX_R Line Out", NULL),
+	SND_SOC_DAPM_HP("Headphone", NULL),
+	/* Inputs */
+	SND_SOC_DAPM_LINE("twl4030 interconnect", NULL),
+};
+
+static const struct snd_soc_dapm_route dfl61dac33_audio_map[] = {
+	{"Headphone", NULL, "TPA6140A2 HPLEFT"},
+	{"Headphone", NULL, "TPA6140A2 HPRIGHT"},
+	{"TPA6140A2 HPLEFT", NULL, "LEFT_LO"},
+	{"TPA6140A2 HPRIGHT", NULL, "RIGHT_LO"},
+
+	{"FMTX_L Line Out", NULL, "LEFT_LO"},
+	{"FMTX_R Line Out", NULL, "RIGHT_LO"},
+
+	{"LINER", NULL, "twl4030 interconnect"},
+	{"LINEL", NULL, "twl4030 interconnect"},
+};
+
+static int dfl61dac33_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(rtd->codec);
+
+	snd_soc_limit_volume(rtd->card, "TPA6140A2 Headphone Playback Volume", 21);
+
+	snd_soc_dapm_disable_pin(dapm, "twl4030 interconnect");
+
+	if (omap_mcbsp_st_add_controls(rtd, 2))
+		dev_dbg(rtd->codec->dev, "Unable to set Sidetone for McBSP2\n");
+
+	mcbsp->dma_op_mode = MCBSP_DMA_MODE_THRESHOLD;
+
+	return 0;
+}
+
+static struct snd_soc_ops dfl61dac33_ops = {
+	.hw_params = dfl61dac33_hw_params,
+};
+
+/* WL1273 */
+static int dfl61wl1273_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	mcbsp->dma_op_mode = MCBSP_DMA_MODE_THRESHOLD;
+
+	return 0;
+}
+
+static int dfl61wl1273_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	unsigned int fmt;
+	int r;
+
+	r = wl1273_get_format(rtd->codec, &fmt);
+	if (r < 0) {
+		pr_err("Can't get fmt for wl1273: %d\n", r);
+		return r;
+	}
+
+	/* Set cpu DAI configuration */
+	r = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt);
+	if (r < 0) {
+		pr_err("Can't set cpu DAI configuration for wl1273: %d\n", r);
+		return r;
+	}
+	return 0;
+}
+
+static struct snd_soc_ops dfl61wl1273_ops = {
+	.startup = dfl61wl1273_startup,
+};
+
+static int dfl61wl1273_card_probe(struct snd_soc_card *card)
+{
+	struct dfl61wl1273_audio_pdata *pdata = snd_soc_card_get_drvdata(card);
+
+	gpiod_set_value(pdata->power_gpio, 1);
+	return 0;
+}
+
+
+static int dfl61wl1273_card_remove(struct snd_soc_card *card)
+{
+	struct dfl61wl1273_audio_pdata *pdata = snd_soc_card_get_drvdata(card);
+
+	gpiod_set_value(pdata->power_gpio, 0);
+	return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link dfl61twl_dai[] = {
+	{
+		.name = "TWL4030",
+		.stream_name = "TWL4030",
+		.cpu_dai_name = "omap-mcbsp.3",
+		.codec_dai_name = "twl4030-hifi",
+		.platform_name = "omap-pcm-audio",
+		.codec_name = "twl4030-codec",
+		.dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF |
+					SND_SOC_DAIFMT_CBM_CFM,
+		.init = dfl61twl_init,
+		.ops = &dfl61twl_ops,
+	},
+};
+
+static struct snd_soc_dai_link dfl61dac33_dai[] = {
+	{
+		.name = "TLV320DAC33",
+		.stream_name = "DAC33",
+		.cpu_dai_name = "omap-mcbsp.2",
+		.codec_dai_name = "tlv320dac33-hifi",
+		.platform_name = "omap-pcm-audio",
+		.codec_name = "tlv320dac33-codec.1-0019",
+		.init = dfl61dac33_init,
+		.ops = &dfl61dac33_ops,
+	},
+};
+
+static struct snd_soc_aux_dev dfl61dac33_aux_dev[] = {
+	{
+		.name = "TPA6140A2",
+		.codec_name = "tpa6130a2.1-0060",
+	},
+};
+
+static struct snd_soc_codec_conf dfl61dac33_codec_conf[] = {
+	{
+		.dev_name = "tpa6130a2.2-0060",
+		.name_prefix = "TPA6140A2",
+	},
+};
+
+static struct snd_soc_dai_link dfl61wl1273_dai[] = {
+	{
+		.name = "BT/FM PCM",
+		.stream_name = "BT/FM Stream",
+		.cpu_dai_name = "omap-mcbsp.4",
+		.codec_dai_name = "wl1273-fm",
+		.platform_name = "omap-pcm-audio",
+		.codec_name = "wl1273-codec",
+		.init = dfl61wl1273_init,
+		.ops = &dfl61wl1273_ops,
+	},
+};
+
+/* Audio cards */
+static struct snd_soc_card dfl61twl_sound_card = {
+	.name = "dfl61-twl4030",
+	.owner = THIS_MODULE,
+	.dai_link = dfl61twl_dai,
+	.num_links = ARRAY_SIZE(dfl61twl_dai),
+	.fully_routed = true,
+	.dapm_widgets = dfl61twl_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(dfl61twl_dapm_widgets),
+	.dapm_routes = dfl61twl_audio_map,
+	.num_dapm_routes = ARRAY_SIZE(dfl61twl_audio_map),
+};
+
+static struct snd_soc_card dfl61dac33_sound_card = {
+	.name = "dfl61-dac33",
+	.owner = THIS_MODULE,
+	.dai_link = dfl61dac33_dai,
+	.num_links = ARRAY_SIZE(dfl61dac33_dai),
+	.aux_dev = dfl61dac33_aux_dev,
+	.num_aux_devs = ARRAY_SIZE(dfl61dac33_aux_dev),
+	.codec_conf = dfl61dac33_codec_conf,
+	.num_configs = ARRAY_SIZE(dfl61dac33_codec_conf),
+	.fully_routed = true,
+	.controls = dfl61dac33_controls,
+	.num_controls = ARRAY_SIZE(dfl61dac33_controls),
+	.dapm_widgets = dfl61dac33_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(dfl61dac33_dapm_widgets),
+	.dapm_routes = dfl61dac33_audio_map,
+	.num_dapm_routes = ARRAY_SIZE(dfl61dac33_audio_map),
+};
+
+static struct snd_soc_card dfl61wl1273_sound_card = {
+	.name = "dfl61-wl1273",
+	.owner = THIS_MODULE,
+	.probe = dfl61wl1273_card_probe,
+	.remove = dfl61wl1273_card_remove,
+	.dai_link = dfl61wl1273_dai,
+	.num_links = ARRAY_SIZE(dfl61wl1273_dai),
+	.fully_routed = true,
+};
+
+static int n9_soc_probe(struct platform_device *pdev)
+{
+	struct dfl61twl_audio_pdata *pdata_twl;
+	struct dfl61wl1273_audio_pdata *pdata_wl1273;
+	struct device_node *np = pdev->dev.of_node;
+	struct snd_soc_card *card_twl = &dfl61twl_sound_card;
+	struct snd_soc_card *card_dac33 = &dfl61dac33_sound_card;
+	struct snd_soc_card *card_wl1273 = &dfl61wl1273_sound_card;
+	int err;
+
+	if (!(machine_is_nokia_rm696() || machine_is_nokia_rm680())
+		&& !(of_machine_is_compatible("nokia,omap3-n9")
+		|| of_machine_is_compatible("nokia,omap3-n950")))
+		return -ENODEV;
+
+	card_twl->dev = &pdev->dev;
+	card_dac33->dev = &pdev->dev;
+	card_wl1273->dev = &pdev->dev;
+
+	if (np) {
+		struct device_node *dai_node;
+
+		dai_node = of_parse_phandle(np, "nokia,twl4030-cpu-dai", 0);
+		if (!dai_node) {
+			dev_err(&pdev->dev, "McBSP node for TWL4030 is not provided\n");
+			return -EINVAL;
+		}
+		dfl61twl_dai[0].cpu_dai_name = NULL;
+		dfl61twl_dai[0].platform_name = NULL;
+		dfl61twl_dai[0].cpu_of_node = dai_node;
+		dfl61twl_dai[0].platform_of_node = dai_node;
+
+		dai_node = of_parse_phandle(np, "nokia,tlv320dac33-cpu-dai", 0);
+		if (!dai_node) {
+			dev_err(&pdev->dev, "McBSP node for TLV320DAC33 is not provided\n");
+			return -EINVAL;
+		}
+		dfl61dac33_dai[0].cpu_dai_name = NULL;
+		dfl61dac33_dai[0].platform_name = NULL;
+		dfl61dac33_dai[0].cpu_of_node = dai_node;
+		dfl61dac33_dai[0].platform_of_node = dai_node;
+
+		dai_node = of_parse_phandle(np, "nokia,wl1273-cpu-dai", 0);
+		if (!dai_node) {
+			dev_err(&pdev->dev, "McBSP node for WL1273 is not provided\n");
+			return -EINVAL;
+		}
+		dfl61wl1273_dai[0].cpu_dai_name = NULL;
+		dfl61wl1273_dai[0].platform_name = NULL;
+		dfl61wl1273_dai[0].cpu_of_node = dai_node;
+		dfl61wl1273_dai[0].platform_of_node = dai_node;
+
+		dai_node = of_parse_phandle(np, "nokia,twl4030-codec", 0);
+		if (!dai_node) {
+			dev_err(&pdev->dev, "TWL4030 codec node is not provided\n");
+			return -EINVAL;
+		}
+		dfl61twl_dai[0].codec_name = NULL;
+		dfl61twl_dai[0].codec_of_node = dai_node;
+
+		dai_node = of_parse_phandle(np, "nokia,tlv320dac33-codec", 0);
+		if (!dai_node) {
+			dev_err(&pdev->dev, "TLV320DAC33 codec node is not provided\n");
+			return -EINVAL;
+		}
+		dfl61dac33_dai[0].codec_name = NULL;
+		dfl61dac33_dai[0].codec_of_node = dai_node;
+
+		dai_node = of_parse_phandle(np, "nokia,headphone-amplifier", 0);
+		if (!dai_node) {
+			dev_err(&pdev->dev, "Headphone amplifier node is not provided\n");
+			return -EINVAL;
+		}
+		dfl61dac33_aux_dev[0].codec_name = NULL;
+		dfl61dac33_aux_dev[0].codec_of_node = dai_node;
+		dfl61dac33_codec_conf[0].dev_name = NULL;
+		dfl61dac33_codec_conf[0].of_node = dai_node;
+
+		dai_node = of_parse_phandle(np, "nokia,wl1273-codec", 0);
+		if (!dai_node) {
+			dev_err(&pdev->dev, "WL1273 codec node is not provided\n");
+			return -EINVAL;
+		}
+		dfl61wl1273_dai[0].codec_name = NULL;
+		dfl61wl1273_dai[0].codec_of_node = dai_node;
+	}
+
+	pdata_twl = devm_kzalloc(&pdev->dev, sizeof(*pdata_twl), GFP_KERNEL);
+	if (pdata_twl == NULL) {
+		dev_err(card_twl->dev, "failed to create private data for twl4030\n");
+		return -ENOMEM;
+	}
+	snd_soc_card_set_drvdata(card_twl, pdata_twl);
+
+	pdata_twl->speaker_amp_gpio = devm_gpiod_get(card_twl->dev,
+						     "speaker-amplifier",
+						     GPIOD_OUT_LOW);
+	if (IS_ERR(pdata_twl->speaker_amp_gpio)) {
+		dev_err(card_twl->dev, "could not get speaker enable gpio\n");
+		return PTR_ERR(pdata_twl->speaker_amp_gpio);
+	}
+
+	pdata_wl1273 = devm_kzalloc(&pdev->dev, sizeof(*pdata_wl1273), GFP_KERNEL);
+	if (pdata_wl1273 == NULL) {
+		dev_err(card_wl1273->dev, "failed to create private data for wl1273\n");
+		return -ENOMEM;
+	}
+	snd_soc_card_set_drvdata(card_wl1273, pdata_wl1273);
+
+	pdata_wl1273->power_gpio = devm_gpiod_get(card_wl1273->dev,
+						  "wl1273-power",
+						  GPIOD_OUT_LOW);
+	if (IS_ERR(pdata_wl1273->power_gpio)) {
+		dev_err(card_wl1273->dev, "could not get wl1273 enable gpio\n");
+		return PTR_ERR(pdata_wl1273->power_gpio);
+	}
+
+	err = devm_snd_soc_register_card(&pdev->dev, card_twl);
+	if (err < 0) {
+		dev_err(card_twl->dev, "failed to register twl4030 card: %d\n", err);
+		return err;
+	}
+
+	err = devm_snd_soc_register_card(&pdev->dev, card_dac33);
+	if (err < 0) {
+		dev_err(card_dac33->dev, "failed to register tlv320dac33 card: %d\n", err);
+		return err;
+	}
+
+
+
+	err = devm_snd_soc_register_card(&pdev->dev, card_wl1273);
+	if (err < 0) {
+		dev_err(card_wl1273->dev, "failed to register wl1273 card\n");
+		return err;
+	}
+
+	return 0;
+}
+
+static const struct of_device_id n9_audio_of_match[] = {
+	{ .compatible = "nokia,n9-audio", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, n9_audio_of_match);
+
+static struct platform_driver n9_soc_driver = {
+	.driver = {
+		.name = "n9-audio",
+		.of_match_table = of_match_ptr(n9_audio_of_match),
+	},
+	.probe = n9_soc_probe,
+};
+
+module_platform_driver(n9_soc_driver);
+
+MODULE_AUTHOR("Nokia Corporation");
+MODULE_DESCRIPTION("ALSA SoC Nokia N9/N950");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:n9-audio");

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 181 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20180112/d4ae8582/attachment-0001.sig>

^ permalink raw reply related

* [PATCH v5 06/20] firmware: arm_scmi: add initial support for performance protocol
From: Alexey Klimov @ 2018-01-12 14:55 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1514904162-11201-7-git-send-email-sudeep.holla@arm.com>

On Tue, Jan 2, 2018 at 2:42 PM, Sudeep Holla <sudeep.holla@arm.com> wrote:
> The performance protocol is intended for the performance management of
> group(s) of device(s) that run in the same performance domain. It
> includes even the CPUs. A performance domain is defined by a set of
> devices that always have to run at the same performance level.
> For example, a set of CPUs that share a voltage domain, and have a
> common frequency control, is said to be in the same performance domain.
>
> The commands in this protocol provide functionality to describe the
> protocol version, describe various attribute flags, set and get the
> performance level of a domain. It also supports discovery of the list
> of performance levels supported by a performance domain, and the
> properties of each performance level.
>
> This patch adds basic support for the performance protocol.
>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
> ---
>  drivers/firmware/arm_scmi/Makefile |   2 +-
>  drivers/firmware/arm_scmi/common.h |   1 +
>  drivers/firmware/arm_scmi/perf.c   | 527 +++++++++++++++++++++++++++++++++++++
>  include/linux/scmi_protocol.h      |  34 +++
>  4 files changed, 563 insertions(+), 1 deletion(-)

[...]

> diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
> new file mode 100644
> index 000000000000..a1f5cf136748
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/perf.c
> @@ -0,0 +1,527 @@
> +/*
> + * System Control and Management Interface (SCMI) Performance Protocol
> + *
> + * Copyright (C) 2017 ARM Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_opp.h>
> +#include <linux/sort.h>
> +
> +#include "common.h"
> +
> +enum scmi_performance_protocol_cmd {
> +       PERF_DOMAIN_ATTRIBUTES = 0x3,
> +       PERF_DESCRIBE_LEVELS = 0x4,
> +       PERF_LIMITS_SET = 0x5,
> +       PERF_LIMITS_GET = 0x6,
> +       PERF_LEVEL_SET = 0x7,
> +       PERF_LEVEL_GET = 0x8,
> +       PERF_NOTIFY_LIMITS = 0x9,
> +       PERF_NOTIFY_LEVEL = 0xa,
> +};
> +
> +struct scmi_opp {
> +       u32 perf;
> +       u32 power;
> +       u32 trans_latency_us;
> +};
> +
> +struct scmi_msg_resp_perf_attributes {
> +       __le16 num_domains;
> +       __le16 flags;
> +#define POWER_SCALE_IN_MILLIWATT(x)    ((x) & BIT(0))
> +       __le32 stats_addr_low;
> +       __le32 stats_addr_high;
> +       __le32 stats_size;
> +};
> +
> +struct scmi_msg_resp_perf_domain_attributes {
> +       __le32 flags;
> +#define SUPPORTS_SET_LIMITS(x)         ((x) & BIT(31))
> +#define SUPPORTS_SET_PERF_LVL(x)       ((x) & BIT(30))
> +#define SUPPORTS_PERF_LIMIT_NOTIFY(x)  ((x) & BIT(29))
> +#define SUPPORTS_PERF_LEVEL_NOTIFY(x)  ((x) & BIT(28))
> +       __le32 rate_limit_us;
> +       __le32 sustained_freq_khz;
> +       __le32 sustained_perf_level;
> +           u8 name[SCMI_MAX_STR_SIZE];
> +};
> +
> +struct scmi_msg_perf_describe_levels {
> +       __le32 domain;
> +       __le32 level_index;
> +};
> +
> +struct scmi_perf_set_limits {
> +       __le32 domain;
> +       __le32 max_level;
> +       __le32 min_level;
> +};
> +
> +struct scmi_perf_get_limits {
> +       __le32 max_level;
> +       __le32 min_level;
> +};
> +
> +struct scmi_perf_set_level {
> +       __le32 domain;
> +       __le32 level;
> +};
> +
> +struct scmi_perf_notify_level_or_limits {
> +       __le32 domain;
> +       __le32 notify_enable;
> +};
> +
> +struct scmi_msg_resp_perf_describe_levels {
> +       __le16 num_returned;
> +       __le16 num_remaining;
> +       struct {
> +               __le32 perf_val;
> +               __le32 power;
> +               __le16 transition_latency_us;
> +               __le16 reserved;
> +       } opp[0];
> +};
> +
> +struct perf_dom_info {
> +       bool set_limits;
> +       bool set_perf;
> +       bool perf_limit_notify;
> +       bool perf_level_notify;
> +       u32 opp_count;
> +       u32 sustained_freq_khz;
> +       u32 sustained_perf_level;
> +       u32 mult_factor;
> +       char name[SCMI_MAX_STR_SIZE];
> +       struct scmi_opp opp[MAX_OPPS];
> +};
> +
> +struct scmi_perf_info {
> +       int num_domains;
> +       bool power_scale_mw;
> +       u64 stats_addr;
> +       u32 stats_size;
> +       struct perf_dom_info *dom_info;
> +};
> +
> +static int scmi_perf_attributes_get(const struct scmi_handle *handle,
> +                                   struct scmi_perf_info *pi)
> +{
> +       int ret;
> +       struct scmi_xfer *t;
> +       struct scmi_msg_resp_perf_attributes *attr;
> +
> +       ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
> +                                SCMI_PROTOCOL_PERF, 0, sizeof(*attr), &t);
> +       if (ret)
> +               return ret;
> +
> +       attr = t->rx.buf;
> +
> +       ret = scmi_do_xfer(handle, t);
> +       if (!ret) {
> +               u16 flags = le16_to_cpu(attr->flags);
> +
> +               pi->num_domains = le16_to_cpu(attr->num_domains);
> +               pi->power_scale_mw = POWER_SCALE_IN_MILLIWATT(flags);
> +               pi->stats_addr = le32_to_cpu(attr->stats_addr_low) |
> +                               (u64)le32_to_cpu(attr->stats_addr_high) << 32;
> +               pi->stats_size = le32_to_cpu(attr->stats_size);
> +       }
> +
> +       scmi_one_xfer_put(handle, t);
> +       return ret;
> +}
> +
> +static int
> +scmi_perf_domain_attributes_get(const struct scmi_handle *handle, u32 domain,
> +                               struct perf_dom_info *dom_info)
> +{
> +       int ret;
> +       struct scmi_xfer *t;
> +       struct scmi_msg_resp_perf_domain_attributes *attr;
> +
> +       ret = scmi_one_xfer_init(handle, PERF_DOMAIN_ATTRIBUTES,
> +                                SCMI_PROTOCOL_PERF, sizeof(domain),
> +                                sizeof(*attr), &t);
> +       if (ret)
> +               return ret;
> +
> +       *(__le32 *)t->tx.buf = cpu_to_le32(domain);
> +       attr = t->rx.buf;
> +
> +       ret = scmi_do_xfer(handle, t);
> +       if (!ret) {
> +               u32 flags = le32_to_cpu(attr->flags);
> +
> +               dom_info->set_limits = SUPPORTS_SET_LIMITS(flags);
> +               dom_info->set_perf = SUPPORTS_SET_PERF_LVL(flags);
> +               dom_info->perf_limit_notify = SUPPORTS_PERF_LIMIT_NOTIFY(flags);
> +               dom_info->perf_level_notify = SUPPORTS_PERF_LEVEL_NOTIFY(flags);
> +               dom_info->sustained_freq_khz =
> +                                       le32_to_cpu(attr->sustained_freq_khz);
> +               dom_info->sustained_perf_level =
> +                                       le32_to_cpu(attr->sustained_perf_level);
> +               dom_info->mult_factor = (dom_info->sustained_freq_khz * 1000) /
> +                                       dom_info->sustained_perf_level;
> +               memcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE);
> +       }
> +
> +       scmi_one_xfer_put(handle, t);
> +       return ret;
> +}
> +
> +static int opp_cmp_func(const void *opp1, const void *opp2)
> +{
> +       const struct scmi_opp *t1 = opp1, *t2 = opp2;
> +
> +       return t1->perf - t2->perf;
> +}
> +
> +static int
> +scmi_perf_describe_levels_get(const struct scmi_handle *handle, u32 domain,
> +                             struct perf_dom_info *perf_dom)
> +{
> +       int ret, cnt;
> +       u32 tot_opp_cnt = 0;
> +       u16 num_returned, num_remaining;
> +       struct scmi_xfer *t;
> +       struct scmi_opp *opp;
> +       struct scmi_msg_perf_describe_levels *dom_info;
> +       struct scmi_msg_resp_perf_describe_levels *level_info;
> +
> +       ret = scmi_one_xfer_init(handle, PERF_DESCRIBE_LEVELS,
> +                                SCMI_PROTOCOL_PERF, sizeof(*dom_info), 0, &t);
> +       if (ret)
> +               return ret;
> +
> +       dom_info = t->tx.buf;
> +       level_info = t->rx.buf;
> +
> +       do {
> +               dom_info->domain = cpu_to_le32(domain);
> +               /* Set the number of OPPs to be skipped/already read */
> +               dom_info->level_index = cpu_to_le32(tot_opp_cnt);
> +
> +               ret = scmi_do_xfer(handle, t);
> +               if (ret)
> +                       break;
> +
> +               num_returned = le16_to_cpu(level_info->num_returned);
> +               num_remaining = le16_to_cpu(level_info->num_remaining);
> +               if (tot_opp_cnt + num_returned > MAX_OPPS) {
> +                       dev_err(handle->dev, "No. of OPPs exceeded MAX_OPPS");
> +                       break;
> +               }
> +
> +               opp = &perf_dom->opp[tot_opp_cnt];
> +               for (cnt = 0; cnt < num_returned; cnt++, opp++) {
> +                       opp->perf = le32_to_cpu(level_info->opp[cnt].perf_val);
> +                       opp->power = le32_to_cpu(level_info->opp[cnt].power);
> +                       opp->trans_latency_us = le16_to_cpu(
> +                               level_info->opp[cnt].transition_latency_us);
> +
> +                       dev_dbg(handle->dev, "Level %d Power %d Latency %dus\n",
> +                               opp->perf, opp->power, opp->trans_latency_us);
> +               }
> +
> +               tot_opp_cnt += num_returned;
> +               /*
> +                * check for both returned and remaining to avoid infinite
> +                * loop due to buggy firmware
> +                */
> +       } while (num_returned && num_remaining);
> +
> +       perf_dom->opp_count = tot_opp_cnt;
> +       scmi_one_xfer_put(handle, t);
> +
> +       sort(perf_dom->opp, tot_opp_cnt, sizeof(*opp), opp_cmp_func, NULL);
> +       return ret;
> +}
> +
> +static int scmi_perf_limits_set(const struct scmi_handle *handle, u32 domain,
> +                               u32 max_perf, u32 min_perf)
> +{
> +       int ret;
> +       struct scmi_xfer *t;
> +       struct scmi_perf_set_limits *limits;
> +
> +       ret = scmi_one_xfer_init(handle, PERF_LIMITS_SET, SCMI_PROTOCOL_PERF,
> +                                sizeof(*limits), 0, &t);
> +       if (ret)
> +               return ret;
> +
> +       limits = t->tx.buf;
> +       limits->domain = cpu_to_le32(domain);
> +       limits->max_level = cpu_to_le32(max_perf);
> +       limits->min_level = cpu_to_le32(min_perf);
> +
> +       ret = scmi_do_xfer(handle, t);
> +
> +       scmi_one_xfer_put(handle, t);
> +       return ret;
> +}
> +
> +static int scmi_perf_limits_get(const struct scmi_handle *handle, u32 domain,
> +                               u32 *max_perf, u32 *min_perf)
> +{
> +       int ret;
> +       struct scmi_xfer *t;
> +       struct scmi_perf_get_limits *limits;
> +
> +       ret = scmi_one_xfer_init(handle, PERF_LIMITS_GET, SCMI_PROTOCOL_PERF,
> +                                sizeof(__le32), 0, &t);
> +       if (ret)
> +               return ret;
> +
> +       *(__le32 *)t->tx.buf = cpu_to_le32(domain);
> +
> +       ret = scmi_do_xfer(handle, t);
> +       if (!ret) {
> +               limits = t->rx.buf;
> +
> +               *max_perf = le32_to_cpu(limits->max_level);
> +               *min_perf = le32_to_cpu(limits->min_level);
> +       }
> +
> +       scmi_one_xfer_put(handle, t);
> +       return ret;
> +}
> +
> +static int
> +scmi_perf_level_set(const struct scmi_handle *handle, u32 domain, u32 level)
> +{
> +       int ret;
> +       struct scmi_xfer *t;
> +       struct scmi_perf_set_level *lvl;
> +
> +       ret = scmi_one_xfer_init(handle, PERF_LEVEL_SET, SCMI_PROTOCOL_PERF,
> +                                sizeof(*lvl), 0, &t);
> +       if (ret)
> +               return ret;
> +
> +       lvl = t->tx.buf;
> +       lvl->domain = cpu_to_le32(domain);
> +       lvl->level = cpu_to_le32(level);
> +
> +       ret = scmi_do_xfer(handle, t);
> +
> +       scmi_one_xfer_put(handle, t);
> +       return ret;
> +}
> +
> +static int
> +scmi_perf_level_get(const struct scmi_handle *handle, u32 domain, u32 *level)
> +{
> +       int ret;
> +       struct scmi_xfer *t;
> +
> +       ret = scmi_one_xfer_init(handle, PERF_LEVEL_GET, SCMI_PROTOCOL_PERF,
> +                                sizeof(u32), sizeof(u32), &t);
> +       if (ret)
> +               return ret;
> +
> +       *(__le32 *)t->tx.buf = cpu_to_le32(domain);
> +
> +       ret = scmi_do_xfer(handle, t);
> +       if (!ret)
> +               *level = le32_to_cpu(*(__le32 *)t->rx.buf);
> +
> +       scmi_one_xfer_put(handle, t);
> +       return ret;
> +}
> +
> +static int __scmi_perf_notify_enable(const struct scmi_handle *handle, u32 cmd,
> +                                    u32 domain, bool enable)
> +{
> +       int ret;
> +       struct scmi_xfer *t;
> +       struct scmi_perf_notify_level_or_limits *notify;
> +
> +       ret = scmi_one_xfer_init(handle, cmd, SCMI_PROTOCOL_PERF,
> +                                sizeof(*notify), 0, &t);
> +       if (ret)
> +               return ret;
> +
> +       notify = t->tx.buf;
> +       notify->domain = cpu_to_le32(domain);
> +       notify->notify_enable = cpu_to_le32(enable & BIT(0));
> +
> +       ret = scmi_do_xfer(handle, t);
> +
> +       scmi_one_xfer_put(handle, t);
> +       return ret;
> +}
> +
> +static int scmi_perf_limits_notify_enable(const struct scmi_handle *handle,
> +                                         u32 domain, bool enable)
> +{
> +       return __scmi_perf_notify_enable(handle, PERF_NOTIFY_LIMITS,
> +                                        domain, enable);
> +}
> +
> +static int scmi_perf_level_notify_enable(const struct scmi_handle *handle,
> +                                        u32 domain, bool enable)
> +{
> +       return __scmi_perf_notify_enable(handle, PERF_NOTIFY_LEVEL,
> +                                        domain, enable);
> +}
> +

Do you have any support to correctly handle notifications without
errors/warnings?
It looks like this two functions are accessible to some user through
perf_ops. But are you sure that notifications will be correctly
handled by transport, mailbox framework and scmi protocol?

The reason I ask is that it looks like it's better to return
-EOPNOTSUPP or -ENODEV, maybe -EINVAL here.
When you add notifications support you can allow these operations when
it's safe to do it.

[..]

Best regards,
Alexey Klimov

^ permalink raw reply

* [PATCH v12 0/3] iommu/smmu-v3: Workaround for hisilicon 161010801 erratum(reserve HW MSI)
From: Shameerali Kolothum Thodi @ 2018-01-12 14:48 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20171214160957.13716-1-shameerali.kolothum.thodi@huawei.com>

Hi,

> -----Original Message-----
> From: Shameerali Kolothum Thodi
> Sent: Friday, December 15, 2017 3:01 PM
> To: lorenzo.pieralisi at arm.com; robin.murphy at arm.com;
> marc.zyngier at arm.com; will.deacon at arm.com
> Cc: joro at 8bytes.org; John Garry <john.garry@huawei.com>; xuwei (O)
> <xuwei5@hisilicon.com>; Guohanjun (Hanjun Guo) <guohanjun@huawei.com>;
> iommu at lists.linux-foundation.org; linux-arm-kernel at lists.infradead.org; linux-
> acpi at vger.kernel.org; devicetree at vger.kernel.org; Linuxarm
> <linuxarm@huawei.com>
> Subject: RE: [PATCH v12 0/3] iommu/smmu-v3: Workaround for hisilicon
> 161010801 erratum(reserve HW MSI)
> 
> Hi Lorenzo/Robin/Will,
> 
> Since this has all the necessary reviewed-by from all concerned now(Thanks to
> all),
> just wondering how this will be picked up? through iort or iommu?
> 
> Please let me know.
> 

A gentle ping on this series...

Thanks,
Shameer

> 
> > -----Original Message-----
> > From: Shameerali Kolothum Thodi
> > Sent: Thursday, December 14, 2017 4:10 PM
> > To: lorenzo.pieralisi at arm.com; robin.murphy at arm.com;
> > marc.zyngier at arm.com; will.deacon at arm.com
> > Cc: joro at 8bytes.org; John Garry <john.garry@huawei.com>; xuwei (O)
> > <xuwei5@hisilicon.com>; Guohanjun (Hanjun Guo)
> <guohanjun@huawei.com>;
> > iommu at lists.linux-foundation.org; linux-arm-kernel at lists.infradead.org;
> linux-
> > acpi at vger.kernel.org; devicetree at vger.kernel.org; Linuxarm
> > <linuxarm@huawei.com>; Shameerali Kolothum Thodi
> > <shameerali.kolothum.thodi@huawei.com>
> > Subject: [PATCH v12 0/3] iommu/smmu-v3: Workaround for hisilicon
> > 161010801 erratum(reserve HW MSI)
> >
> > On certain HiSilicon platforms (hip06/hip07) the GIC ITS and PCIe RC
> > deviates from the standard implementation and this breaks PCIe MSI
> > functionality when SMMU is enabled.
> >
> > The HiSilicon erratum 161010801 describes this limitation of certain
> > HiSilicon platforms to support the SMMU mappings for MSI transactions.
> > On these platforms GICv3 ITS translator is presented with the deviceID
> > by extending the MSI payload data to 64 bits to include the deviceID.
> > Hence, the PCIe controller on this platforms has to differentiate the MSI
> > payload against other DMA payload and has to modify the MSI payload.
> > This basically makes it difficult for this platforms to have a SMMU
> > translation for MSI.
> >
> > This patch implements an ACPI based quirk to reserve the hw msi regions
> > in the smmu-v3 driver which means these address regions will not be
> > translated and will be excluded from iova allocations.
> >
> > To implement this quirk, the following changes are incorporated:
> > 1. Added a generic helper function to IORT code to retrieve and reserve
> >    the associated ITS base address from a device IORT node. The function
> >    has a check for smmu model to determine whether the platform requires
> >    the HW MSI reservation or not.
> > 2. Added smmu node entries and explicitly disabled them in hip06/hip07
> >     dts files so that users are warned about the non-DT support for this
> >     erratum.
> >
> > Changelog:
> >
> > v11--> v12
> > -Thanks to Lorenzo, Fixed !CONFIG_IOMMU_API compile error(patch #1).
> >
> > v10 --> v11
> > -Addressed comments from Lorenzo(patch#1)
> > -Added Robin's Reviewed-by to patch #2
> >
> > v9 --> v10
> > Addressed comments:
> > -Moved smmu model check to iort helper function to selectively apply
> >  the msi reservation which will make the fn call generic from iommu-dma.
> > -Removed PCI blacklisting patch, instead added smmu nodes(disabled)
> >  with comments to hip06/hip07 dts file.
> >
> > v8 --> v9
> > -Thanks to Marc, fixed IORT helper function to reserve the ITS
> >  translater region only.
> > -Removed the DT support for MSI reservation and blacklisted
> >  HiSilicon PCIe controllers on DT based systems when SMMUv3 is
> >  enabled.
> >
> > v7 --> v8
> > Addressed comments from Rob and Lorenzo:
> >  -Modified to use DT compatible string for errata.
> >  -Changed logic to retrieve the msi-parent for DT case.
> >
> > v6 --> v7
> > Addressed request from Will to add DT support for the erratum:
> >  - added bt binding
> >  - add of_iommu_msi_get_resv_regions()
> > New arm64 silicon errata entry
> > Rename iort_iommu_{its->msi}_get_resv_regions
> >
> > v5 --> v6
> > Addressed comments from Robin and Lorenzo:
> > -No change to patch#1 .
> > -Reverted v5 patch#2 as this might break the platforms where this quirk
> >   is not applicable. Provided a generic function in iommu code and added
> >   back the quirk implementation in SMMU v3 driver(patch#3)
> >
> > v4 --> v5
> > Addressed comments from Robin and Lorenzo:
> > -Added a comment to make it clear that, for now, only straightforward
> >   HW topologies are handled while reserving ITS regions(patch #1).
> >
> > v3 --> v4
> > Rebased on 4.13-rc1.
> > Addressed comments from Robin, Will and Lorenzo:
> > -As suggested by Robin, moved the ITS msi reservation into
> >   iommu_dma_get_resv_regions().
> > -Added its_count != resv region failure case(patch #1).
> >
> > v2 --> v3
> > Addressed comments from Lorenzo and Robin:
> > -Removed dev_is_pci() check in smmuV3 driver.
> > -Don't treat device not having an ITS mapping as an error in
> >   iort helper function.
> >
> > v1 --> v2
> > -patch 2/2: Invoke iort helper fn based on fwnode type(acpi).
> >
> > RFCv2 -->PATCH
> > -Incorporated Lorenzo's review comments.
> >
> > RFC v1 --> RFC v2
> > Based on Robin's review comments,
> > -Removed  the generic erratum framework.
> > -Using IORT/MADT tables to retrieve the ITS base addr instead
> >  of vendor specific CSRT table.
> >
> > Shameer Kolothum (3):
> >   ACPI/IORT: Add msi address regions reservation helper
> >   iommu/dma: Add HW MSI(GICv3 ITS) address regions reservation
> >   arm64:dts:hisilicon Disable hisilicon smmu node on hip06/hip07
> >
> >  arch/arm64/boot/dts/hisilicon/hip06.dtsi |  56 ++++++++++++++++
> >  arch/arm64/boot/dts/hisilicon/hip07.dtsi |  25 +++++++
> >  drivers/acpi/arm64/iort.c                | 111
> ++++++++++++++++++++++++++++++-
> >  drivers/iommu/dma-iommu.c                |   8 ++-
> >  drivers/irqchip/irq-gic-v3-its.c         |   3 +-
> >  include/linux/acpi_iort.h                |   7 +-
> >  6 files changed, 204 insertions(+), 6 deletions(-)
> >
> > --
> > 1.9.1
> >

^ permalink raw reply

* [Letux-kernel] [PATCH v5 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
From: Johan Hovold @ 2018-01-12 14:46 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20180109184347.28ba0a6e@aktux>

On Tue, Jan 09, 2018 at 06:43:47PM +0100, Andreas Kemnade wrote:
> On Fri, 22 Dec 2017 13:44:27 +0100
> Johan Hovold <johan@kernel.org> wrote:
> 
> [...]
> > I'd suggest reiterating the problem you're trying to solve and
> > enumerating the previously discussed potential solutions in order to
> > find a proper abstraction level for this (before getting lost in
> > implementation details).
> > 
> The main point here is in short words: Having a device powered on or off
> when the uart it is attached to, is used or not used anymore,
> so the already available userspace applications do not need to be changed.

So we'd end up with something in-between a kernel driver and a
user-space solution. What about devices that need to be (partially)
powered also when the port isn't open? A pure user-space solution would
be able to handle all variants.

> I digged out a bit around:
> alternative aproaches were:
> adding hooks to the uart/tty layer:
> https://marc.info/?l=linux-kernel&m=143333222014616&w=2
> https://marc.info/?l=devicetree&m=143130955414580&w=2

Thanks for the pointers, I remember those threads...

> I do not find it right now in my archive:
> adding a virtual gpio for dtr to the omap_serial driver.
> The driver behind the virtual io would then handle pm. One reason it was
> rejected was that the devicetree should only contain real hardware and
> not virtual stuff.

Oh, yeah, I think something like that made it in briefly before getting
reverted again.

I'll respond to Nikolaus mail as well.

Johan

^ permalink raw reply

* [PATCH v2 3/7] PCI: aardvark: set host and device to the same MAX payload size
From: Bjorn Helgaas @ 2018-01-12 14:40 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20180112111448.00feb9f8@windsurf.lan>

On Fri, Jan 12, 2018 at 11:14:48AM +0100, Thomas Petazzoni wrote:
> Hello,
> 
> On Tue, 9 Jan 2018 16:14:36 -0600, Bjorn Helgaas wrote:
> 
> > > I'm trying to get back (finally) to this topic. Unfortunately, your
> > > branch has been rebased, and this commit no longer exists. Do you have
> > > an updated pointer about what you suggest to use for systems that don't
> > > have Root Ports ?  
> > 
> > Sorry, about that; here's the upstream commit, FWIW:
> > 
> > http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=ee8bdfb6568d
> 
> Thanks. I don't see how this commit can fix our problem though, see below.

Sorry, I didn't mean that commit would fix your problem.  It's just an
example of another case where generic code incorrectly assumed a Root
Port would always be present.

> > If the OS sees no Root Port (I haven't seen the full lspci or kernel
> > enumeration log, so I don't know what the topology actually is), I
> > assume you probably have some Endpoints that have valid Link
> > Capabilities, Control, and Status registers.  Those refer to the
> > downstream end of the Link, and the Root Port would normally have
> > corresponding registers that refer to the upstream end.
> > 
> > The lack of the Root Port means we can't do any management of those
> > top-level Links, so no ASPM, no MPS, no link width/speed management,
> > etc.
> > 
> > I see that advk_pcie_probe() calls pcie_bus_configure_settings() like
> > all other drivers, and ideally we would try to make that work just
> > like it does on other platforms.  The code is:
> > 
> >   pci_scan_root_bus_bridge(bridge);
> >   bus = bridge->bus;
> >   list_for_each_entry(child, &bus->children, node)
> >     pcie_bus_configure_settings(child);
> > 
> > This MPS setting is all strictly in the PCIe domain (it's not in the
> > Aardvark domain and shouldn't have any Aardvark dependencies), so I
> > would expect the core code to just work, modulo some possible
> > confusion if it expects to find a Root Port but doesn't.
> > 
> > Can you collect "lspci -vv" output and details about what currently
> > goes wrong?  Then we'd have something more concrete to talk about.
> 
> With an E1000E PCIe NIC connected, the entire lspci -vvv output is:
> 
> # lspci -vv
> 00:00.0 Ethernet controller: Intel Corporation 82572EI Gigabit Ethernet Controller (Copper) (rev 06)
> 	Subsystem: Intel Corporation PRO/1000 PT Server Adapter
> 	Control: I/O- Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+
> 	Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
> 	Latency: 0, Cache Line Size: 32 bytes
> 	Interrupt: pin A routed to IRQ 40
> 	Region 0: Memory at e8000000 (32-bit, non-prefetchable) [size=128K]
> 	Region 1: Memory at e8020000 (32-bit, non-prefetchable) [size=128K]
> 	Region 2: I/O ports at 1000 [disabled] [size=32]
> 	Expansion ROM at e8040000 [disabled] [size=128K]
> 	Capabilities: [c8] Power Management version 2
> 		Flags: PMEClk- DSI+ D1- D2- AuxCurrent=0mA PME(D0+,D1-,D2-,D3hot+,D3cold+)
> 		Status: D0 NoSoftRst- PME-Enable- DSel=0 DScale=1 PME-
> 	Capabilities: [d0] MSI: Enable+ Count=1/1 Maskable- 64bit+
> 		Address: 000000001d1f6f68  Data: 0028
> 	Capabilities: [e0] Express (v1) Endpoint, MSI 00
> 		DevCap:	MaxPayload 256 bytes, PhantFunc 0, Latency L0s <512ns, L1 <64us
> 			ExtTag- AttnBtn- AttnInd- PwrInd- RBE- FLReset- SlotPowerLimit 0.000W
> 		DevCtl:	Report errors: Correctable+ Non-Fatal+ Fatal+ Unsupported+
> 			RlxdOrd+ ExtTag- PhantFunc- AuxPwr- NoSnoop+
> 			MaxPayload 256 bytes, MaxReadReq 512 bytes
> 		DevSta:	CorrErr- UncorrErr- FatalErr- UnsuppReq- AuxPwr+ TransPend-
> 		LnkCap:	Port #0, Speed 2.5GT/s, Width x1, ASPM L0s, Exit Latency L0s <4us
> 			ClockPM- Surprise- LLActRep- BwNot- ASPMOptComp-
> 		LnkCtl:	ASPM Disabled; RCB 64 bytes Disabled- CommClk-
> 			ExtSynch- ClockPM- AutWidDis- BWInt- AutBWInt-
> 		LnkSta:	Speed 2.5GT/s, Width x1, TrErr- Train- SlotClk+ DLActive- BWMgmt- ABWMgmt-
> 	Capabilities: [100 v1] Advanced Error Reporting
> 		UESta:	DLP- SDES- TLP- FCP- CmpltTO- CmpltAbrt- UnxCmplt- RxOF- MalfTLP- ECRC- UnsupReq- ACSViol-
> 		UEMsk:	DLP- SDES- TLP- FCP- CmpltTO- CmpltAbrt- UnxCmplt- RxOF- MalfTLP- ECRC- UnsupReq- ACSViol-
> 		UESvrt:	DLP+ SDES- TLP- FCP+ CmpltTO- CmpltAbrt- UnxCmplt- RxOF+ MalfTLP+ ECRC- UnsupReq- ACSViol-
> 		CESta:	RxErr- BadTLP- BadDLLP- Rollover- Timeout- NonFatalErr-
> 		CEMsk:	RxErr- BadTLP- BadDLLP- Rollover- Timeout- NonFatalErr-
> 		AERCap:	First Error Pointer: 00, ECRCGenCap- ECRCGenEn- ECRCChkCap- ECRCChkEn-
> 			MultHdrRecCap- MultHdrRecEn- TLPPfxPres- HdrLogCap-
> 		HeaderLog: 00000000 00000000 00000000 00000000
> 	Capabilities: [140 v1] Device Serial Number 00-1b-21-ff-ff-c1-c4-fe
> 	Kernel driver in use: e1000e
> 
> I.e, there is no Root Port. Therefore, I don't see how the kernel
> can know what is the maximum allowed payload size of the PCIe
> controller, nor how to adjust the payload size to use. Same for the L0s
> configuration.

The Device Control MPS field defaults to 128 bytes.  Generic software
can only change that default if it knows that every element that might
receive a packet from the device can handle it.  In this case, we have
no information about what the invisible Root Port can handle, so I
would argue that we cannot change MPS.

In the lspci above, MPS is set to 256 bytes.  If that was done by
firmware, it might be safe because it knows things about the Root Port
that Linux doesn't.  But I don't think the Linux PCI core could set it
to 256.

ASPM L0s is similar.  We should only enable L0s if we can tell that
both ends of the link support it.  If there's no Root Port, we don't
have any ASPM capability information for the upstream end of the link,
so we shouldn't enable ASPM at all.

> This is why we need those changes, one to update the PCIe controller
> MPS according to the Maximum Payload Size acceptable by the endpoint,
> and one to disable L0s entirely to avoid issues with non-L0s compliant
> devices.

The generic core code should perform minimal, guaranteed-to-work
configuration using the least information and fewest assumptions
possible.  That may not lead to optimal performance, but it should at
least be functional.  This should work even if there is no Root Port.

Once we have that figured out, then we can worry about whether we can
or should do platform-specific tweaks to improve performance, e.g.,
increase MPS if we know Root Port capabilities implicitly.

I had the impression that these patches were required for correct
functionality, not just to improve performance.  But maybe I
misunderstood?

Bjorn

^ permalink raw reply

* [PATCH v2 16/16] pwm: atmel: add push-pull mode support
From: Claudiu Beznea @ 2018-01-12 14:23 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Add support for PWM push-pull mode. This is only supported by SAMA5D2 SoCs.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/pwm/pwm-atmel.c | 38 ++++++++++++++++++++++++++++++++++----
 1 file changed, 34 insertions(+), 4 deletions(-)

diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c
index e879d5459b55..0122f51723f8 100644
--- a/drivers/pwm/pwm-atmel.c
+++ b/drivers/pwm/pwm-atmel.c
@@ -33,8 +33,11 @@
 
 #define PWM_CMR			0x0
 /* Bit field in CMR */
-#define PWM_CMR_CPOL		(1 << 9)
-#define PWM_CMR_UPD_CDTY	(1 << 10)
+#define PWM_CMR_CPOL		BIT(9)
+#define PWM_CMR_UPD_CDTY	BIT(10)
+#define PWM_CMR_DTHI		BIT(17)
+#define PWM_CMR_DTLI		BIT(18)
+#define PWM_CMR_PPM		BIT(19)
 #define PWM_CMR_CPRE_MSK	0xF
 
 /* The following registers for PWM v1 */
@@ -228,7 +231,8 @@ static int atmel_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	if (state->enabled) {
 		if (cstate.enabled &&
 		    cstate.polarity == state->polarity &&
-		    cstate.period == state->period) {
+		    cstate.period == state->period &&
+		    cstate.mode == state->mode) {
 			cprd = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm,
 						  atmel_pwm->regs->period);
 			atmel_pwm_calculate_cdty(state, cprd, &cdty);
@@ -263,6 +267,18 @@ static int atmel_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 			val &= ~PWM_CMR_CPOL;
 		else
 			val |= PWM_CMR_CPOL;
+
+		/* PWM mode. */
+		if (chip->caps->modes & PWM_MODE_PUSH_PULL) {
+			if (state->mode == PWM_MODE_PUSH_PULL) {
+				val |= PWM_CMR_PPM | PWM_CMR_DTHI;
+				val &= ~PWM_CMR_DTLI;
+			} else {
+				val &= ~(PWM_CMR_PPM | PWM_CMR_DTLI |
+					 PWM_CMR_DTHI);
+			}
+		}
+
 		atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
 		atmel_pwm_set_cprd_cdty(chip, pwm, cprd, cdty);
 		mutex_lock(&atmel_pwm->isr_lock);
@@ -306,6 +322,20 @@ static const struct atmel_pwm_data atmel_pwm_data_v2 = {
 	},
 };
 
+static const struct atmel_pwm_data atmel_pwm_data_v3 = {
+	.regs = {
+		.period		= PWMV2_CPRD,
+		.period_upd	= PWMV2_CPRDUPD,
+		.duty		= PWMV2_CDTY,
+		.duty_upd	= PWMV2_CDTYUPD,
+	},
+	.caps = {
+		.modes = PWM_MODE_NORMAL |
+			 PWM_MODE_COMPLEMENTARY |
+			 PWM_MODE_PUSH_PULL,
+	},
+};
+
 static const struct platform_device_id atmel_pwm_devtypes[] = {
 	{
 		.name = "at91sam9rl-pwm",
@@ -328,7 +358,7 @@ static const struct of_device_id atmel_pwm_dt_ids[] = {
 		.data = &atmel_pwm_data_v2,
 	}, {
 		.compatible = "atmel,sama5d2-pwm",
-		.data = &atmel_pwm_data_v2,
+		.data = &atmel_pwm_data_v3,
 	}, {
 		/* sentinel */
 	},
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 15/16] pwm: add documentation for pwm push-pull mode
From: Claudiu Beznea @ 2018-01-12 14:23 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Add documentation for PWM push-pull mode.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 Documentation/devicetree/bindings/pwm/pwm.txt |  8 +++++++-
 Documentation/pwm.txt                         | 18 ++++++++++++++++++
 2 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/pwm/pwm.txt b/Documentation/devicetree/bindings/pwm/pwm.txt
index fdff25bad1db..a4562af3577c 100644
--- a/Documentation/devicetree/bindings/pwm/pwm.txt
+++ b/Documentation/devicetree/bindings/pwm/pwm.txt
@@ -58,15 +58,21 @@ Example with optional PWM specifier for inverse polarity
 - PWM_MODE_NORMAL: for all PWM controllers
 - PWM_MODE_COMPLEMENTARY: for PWM controllers with more than one output per
 PWM channel
+- PWM_MODE_PUSH_PULL: for PWM controllers with more than one output per channel,
+in push-pull mode
 
 Example with PWM modes:
 
 	bl: blacklight {
 		pwms = <&pwm 0 5000000 PWM_POLARITY_INVERTED
-			PWM_DTMODE_NORMAL | PWM_DTMODE_COMPLEMENTARY>;
+			PWM_DTMODE_NORMAL | PWM_DTMODE_COMPLEMENTARY |
+			PWM_DTMODE_PUSH_PULL>;
 		pwm-names = "backlight";
 	};
 
+If all the available modes are given as argument of pwms binding only the first
+valid one will be considered (first valid LSB bit of mode field).
+
 2) PWM controller nodes
 -----------------------
 
diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
index 58c9bd55f021..71b538239519 100644
--- a/Documentation/pwm.txt
+++ b/Documentation/pwm.txt
@@ -135,6 +135,24 @@ channel that was exported. The following properties will then be available:
 
     Where T is the signal period.
 
+    Push-pull mode - for PWM chips with mode than one output per PWM channel;
+        output waveform for a PWM controller with 2 outputs per PWM channel, in
+	push-pull mode, with normal polarity looks like this:
+            __          __
+    PWMH __|  |________|  |________
+                  __          __
+    PWML ________|  |________|  |__
+           <--T-->
+
+    If polarity is inversed:
+         __    ________    ________
+    PWMH   |__|        |__|
+         ________    ________    __
+    PWML         |__|        |__|
+           <--T-->
+
+    Where T is the signal period.
+
 Implementing a PWM driver
 -------------------------
 
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 14/16] pwm: add push-pull mode
From: Claudiu Beznea @ 2018-01-12 14:23 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Add macro for push-pull mode to be used in DT.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 include/dt-bindings/pwm/pwm.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/include/dt-bindings/pwm/pwm.h b/include/dt-bindings/pwm/pwm.h
index b8617431f8ec..c6365807c30d 100644
--- a/include/dt-bindings/pwm/pwm.h
+++ b/include/dt-bindings/pwm/pwm.h
@@ -14,5 +14,6 @@
 
 #define PWM_DTMODE_NORMAL			(1 << 0)
 #define PWM_DTMODE_COMPLEMENTARY		(1 << 1)
+#define PWM_DTMODE_PUSH_PULL			(1 << 2)
 
 #endif
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 13/16] drivers: pwm: core: add push-pull mode support
From: Claudiu Beznea @ 2018-01-12 14:23 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Add push-pull mode support in PWM code. In push-pull mode the channels
outputs has same polarity and the edges are complementary delayed for one
period.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/pwm/sysfs.c | 2 ++
 include/linux/pwm.h | 9 +++++++--
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c
index 7d111ab17e43..5052bdec7ad6 100644
--- a/drivers/pwm/sysfs.c
+++ b/drivers/pwm/sysfs.c
@@ -249,6 +249,8 @@ static ssize_t mode_store(struct device *child,
 		mode = PWM_MODE_NORMAL;
 	else if (sysfs_streq(buf, pwm_get_mode_desc(PWM_MODE_COMPLEMENTARY)))
 		mode = PWM_MODE_COMPLEMENTARY;
+	else if (sysfs_streq(buf, pwm_get_mode_desc(PWM_MODE_PUSH_PULL)))
+		mode = PWM_MODE_PUSH_PULL;
 	else
 		return -EINVAL;
 
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index 2e8dfc3ea516..a4ad3b7a5317 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -29,13 +29,16 @@ enum pwm_polarity {
  * enum pwm_mode - PWM working modes
  * PWM_MODE_NORMAL: PWM has one output per channel
  * PWM_MODE_COMPLEMENTARY: PWM has 2 outputs per channel with opposite polarity
+ * PWM_MODE_PUSH_PULL: PWM has 2 outputs per channel with same polarity and
+ * the edges are complementary delayed for one period
  * PWM_MODE_MAX: Used to get the defined PWM modes mask (PWM_MODE_MAX - 1)
  * phase-shifted
  */
 enum pwm_mode {
 	PWM_MODE_NORMAL		= BIT(0),
 	PWM_MODE_COMPLEMENTARY	= BIT(1),
-	PWM_MODE_MAX		= BIT(2),
+	PWM_MODE_PUSH_PULL	= BIT(2),
+	PWM_MODE_MAX		= BIT(3),
 };
 
 /**
@@ -478,7 +481,9 @@ static inline void pwm_disable(struct pwm_device *pwm)
 
 static inline const char * const pwm_get_mode_desc(enum pwm_mode mode)
 {
-	static const char * const modes[] = { "normal", "complementary" };
+	static const char * const modes[] = {
+		"normal", "complementary", "push-pull"
+	};
 
 	return mode ? modes[ffs(mode) - 1] : "invalid";
 }
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 12/16] pwm: atmel: add pwm capabilities
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Add pwm capabilities to Atmel PWM controllers.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/pwm/pwm-atmel.c | 58 +++++++++++++++++++++++++++++++------------------
 1 file changed, 37 insertions(+), 21 deletions(-)

diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c
index 32427d2b7877..e879d5459b55 100644
--- a/drivers/pwm/pwm-atmel.c
+++ b/drivers/pwm/pwm-atmel.c
@@ -65,6 +65,11 @@ struct atmel_pwm_registers {
 	u8 duty_upd;
 };
 
+struct atmel_pwm_data {
+	struct atmel_pwm_registers regs;
+	struct pwm_caps caps;
+};
+
 struct atmel_pwm_chip {
 	struct pwm_chip chip;
 	struct clk *clk;
@@ -277,27 +282,37 @@ static const struct pwm_ops atmel_pwm_ops = {
 	.owner = THIS_MODULE,
 };
 
-static const struct atmel_pwm_registers atmel_pwm_regs_v1 = {
-	.period		= PWMV1_CPRD,
-	.period_upd	= PWMV1_CUPD,
-	.duty		= PWMV1_CDTY,
-	.duty_upd	= PWMV1_CUPD,
+static const struct atmel_pwm_data atmel_pwm_data_v1 = {
+	.regs = {
+		.period		= PWMV1_CPRD,
+		.period_upd	= PWMV1_CUPD,
+		.duty		= PWMV1_CDTY,
+		.duty_upd	= PWMV1_CUPD,
+	},
+	.caps = {
+		.modes = PWM_MODE_NORMAL,
+	},
 };
 
-static const struct atmel_pwm_registers atmel_pwm_regs_v2 = {
-	.period		= PWMV2_CPRD,
-	.period_upd	= PWMV2_CPRDUPD,
-	.duty		= PWMV2_CDTY,
-	.duty_upd	= PWMV2_CDTYUPD,
+static const struct atmel_pwm_data atmel_pwm_data_v2 = {
+	.regs = {
+		.period		= PWMV2_CPRD,
+		.period_upd	= PWMV2_CPRDUPD,
+		.duty		= PWMV2_CDTY,
+		.duty_upd	= PWMV2_CDTYUPD,
+	},
+	.caps = {
+		.modes = PWM_MODE_NORMAL | PWM_MODE_COMPLEMENTARY,
+	},
 };
 
 static const struct platform_device_id atmel_pwm_devtypes[] = {
 	{
 		.name = "at91sam9rl-pwm",
-		.driver_data = (kernel_ulong_t)&atmel_pwm_regs_v1,
+		.driver_data = (kernel_ulong_t)&atmel_pwm_data_v1,
 	}, {
 		.name = "sama5d3-pwm",
-		.driver_data = (kernel_ulong_t)&atmel_pwm_regs_v2,
+		.driver_data = (kernel_ulong_t)&atmel_pwm_data_v2,
 	}, {
 		/* sentinel */
 	},
@@ -307,20 +322,20 @@ MODULE_DEVICE_TABLE(platform, atmel_pwm_devtypes);
 static const struct of_device_id atmel_pwm_dt_ids[] = {
 	{
 		.compatible = "atmel,at91sam9rl-pwm",
-		.data = &atmel_pwm_regs_v1,
+		.data = &atmel_pwm_data_v1,
 	}, {
 		.compatible = "atmel,sama5d3-pwm",
-		.data = &atmel_pwm_regs_v2,
+		.data = &atmel_pwm_data_v2,
 	}, {
 		.compatible = "atmel,sama5d2-pwm",
-		.data = &atmel_pwm_regs_v2,
+		.data = &atmel_pwm_data_v2,
 	}, {
 		/* sentinel */
 	},
 };
 MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids);
 
-static inline const struct atmel_pwm_registers *
+static inline const struct atmel_pwm_data *
 atmel_pwm_get_driver_data(struct platform_device *pdev)
 {
 	const struct platform_device_id *id;
@@ -330,18 +345,18 @@ atmel_pwm_get_driver_data(struct platform_device *pdev)
 
 	id = platform_get_device_id(pdev);
 
-	return (struct atmel_pwm_registers *)id->driver_data;
+	return (struct atmel_pwm_data *)id->driver_data;
 }
 
 static int atmel_pwm_probe(struct platform_device *pdev)
 {
-	const struct atmel_pwm_registers *regs;
+	const struct atmel_pwm_data *data;
 	struct atmel_pwm_chip *atmel_pwm;
 	struct resource *res;
 	int ret;
 
-	regs = atmel_pwm_get_driver_data(pdev);
-	if (!regs)
+	data = atmel_pwm_get_driver_data(pdev);
+	if (!data)
 		return -ENODEV;
 
 	atmel_pwm = devm_kzalloc(&pdev->dev, sizeof(*atmel_pwm), GFP_KERNEL);
@@ -365,9 +380,10 @@ static int atmel_pwm_probe(struct platform_device *pdev)
 
 	atmel_pwm->chip.dev = &pdev->dev;
 	atmel_pwm->chip.ops = &atmel_pwm_ops;
+	atmel_pwm->chip.caps = &data->caps;
 	atmel_pwm->chip.base = -1;
 	atmel_pwm->chip.npwm = 4;
-	atmel_pwm->regs = regs;
+	atmel_pwm->regs = &data->regs;
 	atmel_pwm->updated_pwms = 0;
 	mutex_init(&atmel_pwm->isr_lock);
 
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 11/16] pwm: add documentation for PWM modes
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Add documentation for PWM normal and complementary modes.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 Documentation/devicetree/bindings/pwm/pwm.txt | 17 ++++++++++++++--
 Documentation/pwm.txt                         | 29 +++++++++++++++++++++++++--
 2 files changed, 42 insertions(+), 4 deletions(-)

diff --git a/Documentation/devicetree/bindings/pwm/pwm.txt b/Documentation/devicetree/bindings/pwm/pwm.txt
index 8556263b8502..fdff25bad1db 100644
--- a/Documentation/devicetree/bindings/pwm/pwm.txt
+++ b/Documentation/devicetree/bindings/pwm/pwm.txt
@@ -43,8 +43,8 @@ because the name "backlight" would be used as fallback anyway.
 pwm-specifier typically encodes the chip-relative PWM number and the PWM
 period in nanoseconds.
 
-Optionally, the pwm-specifier can encode a number of flags (defined in
-<dt-bindings/pwm/pwm.h>) in a third cell:
+Optionally, the pwm-specifier can encode:
+1. a number of flags (defined in <dt-bindings/pwm/pwm.h>) in a third cell:
 - PWM_POLARITY_INVERTED: invert the PWM signal polarity
 
 Example with optional PWM specifier for inverse polarity
@@ -54,6 +54,19 @@ Example with optional PWM specifier for inverse polarity
 		pwm-names = "backlight";
 	};
 
+2. PWM working modes (defined in <dt-bindings/pwm/pwm.h>) in the 4th cell:
+- PWM_MODE_NORMAL: for all PWM controllers
+- PWM_MODE_COMPLEMENTARY: for PWM controllers with more than one output per
+PWM channel
+
+Example with PWM modes:
+
+	bl: blacklight {
+		pwms = <&pwm 0 5000000 PWM_POLARITY_INVERTED
+			PWM_DTMODE_NORMAL | PWM_DTMODE_COMPLEMENTARY>;
+		pwm-names = "backlight";
+	};
+
 2) PWM controller nodes
 -----------------------
 
diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
index 8fbf0aa3ba2d..58c9bd55f021 100644
--- a/Documentation/pwm.txt
+++ b/Documentation/pwm.txt
@@ -61,8 +61,8 @@ In addition to the PWM state, the PWM API also exposes PWM arguments, which
 are the reference PWM config one should use on this PWM.
 PWM arguments are usually platform-specific and allows the PWM user to only
 care about dutycycle relatively to the full period (like, duty = 50% of the
-period). struct pwm_args contains 2 fields (period and polarity) and should
-be used to set the initial PWM config (usually done in the probe function
+period). struct pwm_args contains 3 fields (period, polarity and mode) and
+should be used to set the initial PWM config (usually done in the probe function
 of the PWM user). PWM arguments are retrieved with pwm_get_args().
 
 Using PWMs with the sysfs interface
@@ -83,6 +83,9 @@ will find:
   unexport
    Unexports a PWM channel from sysfs (write-only).
 
+  mode
+   PWM chip supported modes.
+
 The PWM channels are numbered using a per-chip index from 0 to npwm-1.
 
 When a PWM channel is exported a pwmX directory will be created in the
@@ -110,6 +113,28 @@ channel that was exported. The following properties will then be available:
 	- 0 - disabled
 	- 1 - enabled
 
+  mode
+    Set PWM channel working mode (normal and complementary). PWM chip with
+    complementary mode could also work in normal mode by using only one physical
+    output.
+
+    Normal mode - for PWM chips with one output per PWM channel; output
+        waveforms looks like this:
+             __    __    __    __
+    PWM   __|  |__|  |__|  |__|  |__
+            <--T-->
+
+    Complementary mode - for PWM chips with more than one output per PWM
+        channel; output waveforms for a PWM controller with 2 outputs per PWM
+        channel looks line this:
+             __    __    __    __
+    PWMH1 __|  |__|  |__|  |__|  |__
+          __    __    __    __    __
+    PWML1   |__|  |__|  |__|  |__|
+            <--T-->
+
+    Where T is the signal period.
+
 Implementing a PWM driver
 -------------------------
 
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 10/16] pwm: Add PWM modes
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Define a macros for PWM modes to be used by device tree sources.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 include/dt-bindings/pwm/pwm.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/include/dt-bindings/pwm/pwm.h b/include/dt-bindings/pwm/pwm.h
index ab9a077e3c7d..b8617431f8ec 100644
--- a/include/dt-bindings/pwm/pwm.h
+++ b/include/dt-bindings/pwm/pwm.h
@@ -12,4 +12,7 @@
 
 #define PWM_POLARITY_INVERTED			(1 << 0)
 
+#define PWM_DTMODE_NORMAL			(1 << 0)
+#define PWM_DTMODE_COMPLEMENTARY		(1 << 1)
+
 #endif
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 09/16] drivers: pwm: core: add PWM mode to pwm_config()
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Add PWM mode to pwm_config() function. The drivers which uses pwm_config()
were adapted to this change.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 arch/arm/mach-s3c24xx/mach-rx1950.c  | 5 +++--
 drivers/bus/ts-nbus.c                | 2 +-
 drivers/clk/clk-pwm.c                | 3 ++-
 drivers/gpu/drm/i915/intel_panel.c   | 8 +++++---
 drivers/hwmon/pwm-fan.c              | 2 +-
 drivers/input/misc/max77693-haptic.c | 2 +-
 drivers/input/misc/max8997_haptic.c  | 3 ++-
 drivers/leds/leds-pwm.c              | 2 +-
 drivers/media/rc/ir-rx51.c           | 2 +-
 drivers/media/rc/pwm-ir-tx.c         | 2 +-
 drivers/video/backlight/lm3630a_bl.c | 2 +-
 drivers/video/backlight/lp855x_bl.c  | 2 +-
 drivers/video/backlight/lp8788_bl.c  | 2 +-
 drivers/video/backlight/pwm_bl.c     | 4 ++--
 drivers/video/fbdev/ssd1307fb.c      | 3 ++-
 include/linux/pwm.h                  | 4 +++-
 16 files changed, 28 insertions(+), 20 deletions(-)

diff --git a/arch/arm/mach-s3c24xx/mach-rx1950.c b/arch/arm/mach-s3c24xx/mach-rx1950.c
index e86ad6a68a0b..9ee0ed9ff37e 100644
--- a/arch/arm/mach-s3c24xx/mach-rx1950.c
+++ b/arch/arm/mach-s3c24xx/mach-rx1950.c
@@ -433,14 +433,15 @@ static void rx1950_lcd_power(int enable)
 
 		/* GPB1->OUTPUT, GPB1->0 */
 		gpio_direction_output(S3C2410_GPB(1), 0);
-		pwm_config(lcd_pwm, 0, LCD_PWM_PERIOD);
+		pwm_config(lcd_pwm, 0, LCD_PWM_PERIOD, PWM_MODE_NORMAL);
 		pwm_disable(lcd_pwm);
 
 		/* GPC0->0, GPC10->0 */
 		gpio_direction_output(S3C2410_GPC(0), 0);
 		gpio_direction_output(S3C2410_GPC(10), 0);
 	} else {
-		pwm_config(lcd_pwm, LCD_PWM_DUTY, LCD_PWM_PERIOD);
+		pwm_config(lcd_pwm, LCD_PWM_DUTY, LCD_PWM_PERIOD,
+			   PWM_MODE_NORMAL);
 		pwm_enable(lcd_pwm);
 
 		gpio_direction_output(S3C2410_GPC(0), 1);
diff --git a/drivers/bus/ts-nbus.c b/drivers/bus/ts-nbus.c
index 073fd9011154..dcd2ca3bcd99 100644
--- a/drivers/bus/ts-nbus.c
+++ b/drivers/bus/ts-nbus.c
@@ -316,7 +316,7 @@ static int ts_nbus_probe(struct platform_device *pdev)
 	 * the atomic PWM API.
 	 */
 	pwm_apply_args(pwm);
-	ret = pwm_config(pwm, pargs.period, pargs.period);
+	ret = pwm_config(pwm, pargs.period, pargs.period, pargs.mode);
 	if (ret < 0)
 		return ret;
 
diff --git a/drivers/clk/clk-pwm.c b/drivers/clk/clk-pwm.c
index 8cb9d117fdbf..605a6bffe893 100644
--- a/drivers/clk/clk-pwm.c
+++ b/drivers/clk/clk-pwm.c
@@ -92,7 +92,8 @@ static int clk_pwm_probe(struct platform_device *pdev)
 	 * atomic PWM API.
 	 */
 	pwm_apply_args(pwm);
-	ret = pwm_config(pwm, (pargs.period + 1) >> 1, pargs.period);
+	ret = pwm_config(pwm, (pargs.period + 1) >> 1, pargs.period,
+			 pargs.mode);
 	if (ret < 0)
 		return ret;
 
diff --git a/drivers/gpu/drm/i915/intel_panel.c b/drivers/gpu/drm/i915/intel_panel.c
index adc51e452e3e..1ea93ebd3e56 100644
--- a/drivers/gpu/drm/i915/intel_panel.c
+++ b/drivers/gpu/drm/i915/intel_panel.c
@@ -634,7 +634,8 @@ static void pwm_set_backlight(const struct drm_connector_state *conn_state, u32
 	struct intel_panel *panel = &to_intel_connector(conn_state->connector)->panel;
 	int duty_ns = DIV_ROUND_UP(level * CRC_PMIC_PWM_PERIOD_NS, 100);
 
-	pwm_config(panel->backlight.pwm, duty_ns, CRC_PMIC_PWM_PERIOD_NS);
+	pwm_config(panel->backlight.pwm, duty_ns, CRC_PMIC_PWM_PERIOD_NS,
+		   PWM_MODE_NORMAL);
 }
 
 static void
@@ -823,7 +824,8 @@ static void pwm_disable_backlight(const struct drm_connector_state *old_conn_sta
 	struct intel_panel *panel = &connector->panel;
 
 	/* Disable the backlight */
-	pwm_config(panel->backlight.pwm, 0, CRC_PMIC_PWM_PERIOD_NS);
+	pwm_config(panel->backlight.pwm, 0, CRC_PMIC_PWM_PERIOD_NS,
+		   PWM_MODE_NORMAL);
 	usleep_range(2000, 3000);
 	pwm_disable(panel->backlight.pwm);
 }
@@ -1771,7 +1773,7 @@ static int pwm_setup_backlight(struct intel_connector *connector,
 	pwm_apply_args(panel->backlight.pwm);
 
 	retval = pwm_config(panel->backlight.pwm, CRC_PMIC_PWM_PERIOD_NS,
-			    CRC_PMIC_PWM_PERIOD_NS);
+			    CRC_PMIC_PWM_PERIOD_NS, PWM_MODE_NORMAL);
 	if (retval < 0) {
 		DRM_ERROR("Failed to configure the pwm chip\n");
 		pwm_put(panel->backlight.pwm);
diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c
index 70cc0d134f3c..bd05cd81d3d5 100644
--- a/drivers/hwmon/pwm-fan.c
+++ b/drivers/hwmon/pwm-fan.c
@@ -308,7 +308,7 @@ static int pwm_fan_resume(struct device *dev)
 
 	pwm_get_args(ctx->pwm, &pargs);
 	duty = DIV_ROUND_UP(ctx->pwm_value * (pargs.period - 1), MAX_PWM);
-	ret = pwm_config(ctx->pwm, duty, pargs.period);
+	ret = pwm_config(ctx->pwm, duty, pargs.period, pargs.mode);
 	if (ret)
 		return ret;
 	return pwm_enable(ctx->pwm);
diff --git a/drivers/input/misc/max77693-haptic.c b/drivers/input/misc/max77693-haptic.c
index 46b0f48fbf49..5fe2ff2b408b 100644
--- a/drivers/input/misc/max77693-haptic.c
+++ b/drivers/input/misc/max77693-haptic.c
@@ -76,7 +76,7 @@ static int max77693_haptic_set_duty_cycle(struct max77693_haptic *haptic)
 
 	pwm_get_args(haptic->pwm_dev, &pargs);
 	delta = (pargs.period + haptic->pwm_duty) / 2;
-	error = pwm_config(haptic->pwm_dev, delta, pargs.period);
+	error = pwm_config(haptic->pwm_dev, delta, pargs.period, pargs.mode);
 	if (error) {
 		dev_err(haptic->dev, "failed to configure pwm: %d\n", error);
 		return error;
diff --git a/drivers/input/misc/max8997_haptic.c b/drivers/input/misc/max8997_haptic.c
index 99bc762881d5..c16be1e410c6 100644
--- a/drivers/input/misc/max8997_haptic.c
+++ b/drivers/input/misc/max8997_haptic.c
@@ -73,7 +73,8 @@ static int max8997_haptic_set_duty_cycle(struct max8997_haptic *chip)
 
 	if (chip->mode == MAX8997_EXTERNAL_MODE) {
 		unsigned int duty = chip->pwm_period * chip->level / 100;
-		ret = pwm_config(chip->pwm, duty, chip->pwm_period);
+		ret = pwm_config(chip->pwm, duty, chip->pwm_period,
+				 PWM_MODE_NORMAL);
 	} else {
 		int i;
 		u8 duty_index = 0;
diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c
index 8d456dc6c5bf..4d7a55db046a 100644
--- a/drivers/leds/leds-pwm.c
+++ b/drivers/leds/leds-pwm.c
@@ -40,7 +40,7 @@ static void __led_pwm_set(struct led_pwm_data *led_dat)
 {
 	int new_duty = led_dat->duty;
 
-	pwm_config(led_dat->pwm, new_duty, led_dat->period);
+	pwm_config(led_dat->pwm, new_duty, led_dat->period, PWM_MODE_NORMAL);
 
 	if (new_duty == 0)
 		pwm_disable(led_dat->pwm);
diff --git a/drivers/media/rc/ir-rx51.c b/drivers/media/rc/ir-rx51.c
index 49265f02e772..0667aa9f1566 100644
--- a/drivers/media/rc/ir-rx51.c
+++ b/drivers/media/rc/ir-rx51.c
@@ -58,7 +58,7 @@ static int init_timing_params(struct ir_rx51 *ir_rx51)
 
 	duty = DIV_ROUND_CLOSEST(ir_rx51->duty_cycle * period, 100);
 
-	pwm_config(pwm, duty, period);
+	pwm_config(pwm, duty, period, PWM_MODE_NORMAL);
 
 	return 0;
 }
diff --git a/drivers/media/rc/pwm-ir-tx.c b/drivers/media/rc/pwm-ir-tx.c
index 27d0f5837a76..4829b09f3a0b 100644
--- a/drivers/media/rc/pwm-ir-tx.c
+++ b/drivers/media/rc/pwm-ir-tx.c
@@ -68,7 +68,7 @@ static int pwm_ir_tx(struct rc_dev *dev, unsigned int *txbuf,
 	period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, pwm_ir->carrier);
 	duty = DIV_ROUND_CLOSEST(pwm_ir->duty_cycle * period, 100);
 
-	pwm_config(pwm, duty, period);
+	pwm_config(pwm, duty, period, PWM_MODE_NORMAL);
 
 	edge = ktime_get();
 
diff --git a/drivers/video/backlight/lm3630a_bl.c b/drivers/video/backlight/lm3630a_bl.c
index 2030a6b77a09..9992aa9c4cf5 100644
--- a/drivers/video/backlight/lm3630a_bl.c
+++ b/drivers/video/backlight/lm3630a_bl.c
@@ -166,7 +166,7 @@ static void lm3630a_pwm_ctrl(struct lm3630a_chip *pchip, int br, int br_max)
 	unsigned int period = pchip->pdata->pwm_period;
 	unsigned int duty = br * period / br_max;
 
-	pwm_config(pchip->pwmd, duty, period);
+	pwm_config(pchip->pwmd, duty, period, PWM_MODE_NORMAL);
 	if (duty)
 		pwm_enable(pchip->pwmd);
 	else
diff --git a/drivers/video/backlight/lp855x_bl.c b/drivers/video/backlight/lp855x_bl.c
index 939f057836e1..018be55d762c 100644
--- a/drivers/video/backlight/lp855x_bl.c
+++ b/drivers/video/backlight/lp855x_bl.c
@@ -256,7 +256,7 @@ static void lp855x_pwm_ctrl(struct lp855x *lp, int br, int max_br)
 		pwm_apply_args(pwm);
 	}
 
-	pwm_config(lp->pwm, duty, period);
+	pwm_config(lp->pwm, duty, period, PWM_MODE_NORMAL);
 	if (duty)
 		pwm_enable(lp->pwm);
 	else
diff --git a/drivers/video/backlight/lp8788_bl.c b/drivers/video/backlight/lp8788_bl.c
index cf869ec90cce..cb49f34a7f2e 100644
--- a/drivers/video/backlight/lp8788_bl.c
+++ b/drivers/video/backlight/lp8788_bl.c
@@ -153,7 +153,7 @@ static void lp8788_pwm_ctrl(struct lp8788_bl *bl, int br, int max_br)
 		pwm_apply_args(pwm);
 	}
 
-	pwm_config(bl->pwm, duty, period);
+	pwm_config(bl->pwm, duty, period, PWM_MODE_NORMAL);
 	if (duty)
 		pwm_enable(bl->pwm);
 	else
diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c
index 1c2289ddd555..ec5215c4c937 100644
--- a/drivers/video/backlight/pwm_bl.c
+++ b/drivers/video/backlight/pwm_bl.c
@@ -66,7 +66,7 @@ static void pwm_backlight_power_off(struct pwm_bl_data *pb)
 	if (!pb->enabled)
 		return;
 
-	pwm_config(pb->pwm, 0, pb->period);
+	pwm_config(pb->pwm, 0, pb->period, PWM_MODE_NORMAL);
 	pwm_disable(pb->pwm);
 
 	if (pb->enable_gpio)
@@ -108,7 +108,7 @@ static int pwm_backlight_update_status(struct backlight_device *bl)
 
 	if (brightness > 0) {
 		duty_cycle = compute_duty_cycle(pb, brightness);
-		pwm_config(pb->pwm, duty_cycle, pb->period);
+		pwm_config(pb->pwm, duty_cycle, pb->period, PWM_MODE_NORMAL);
 		pwm_backlight_power_on(pb, brightness);
 	} else
 		pwm_backlight_power_off(pb);
diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c
index f599520374dd..9cf17721e6e2 100644
--- a/drivers/video/fbdev/ssd1307fb.c
+++ b/drivers/video/fbdev/ssd1307fb.c
@@ -308,7 +308,8 @@ static int ssd1307fb_init(struct ssd1307fb_par *par)
 
 		par->pwm_period = pargs.period;
 		/* Enable the PWM */
-		pwm_config(par->pwm, par->pwm_period / 2, par->pwm_period);
+		pwm_config(par->pwm, par->pwm_period / 2, par->pwm_period,
+			   PWM_MODE_NORMAL);
 		pwm_enable(par->pwm);
 
 		dev_dbg(&par->client->dev, "Using PWM%d with a %dns period.\n",
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index 0fdc680651aa..2e8dfc3ea516 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -375,11 +375,12 @@ int pwm_adjust_config(struct pwm_device *pwm);
  * @pwm: PWM device
  * @duty_ns: "on" time (in nanoseconds)
  * @period_ns: duration (in nanoseconds) of one cycle
+ * @mode: PWM mode
  *
  * Returns: 0 on success or a negative error code on failure.
  */
 static inline int pwm_config(struct pwm_device *pwm, int duty_ns,
-			     int period_ns)
+			     int period_ns, enum pwm_mode mode)
 {
 	struct pwm_state state;
 
@@ -395,6 +396,7 @@ static inline int pwm_config(struct pwm_device *pwm, int duty_ns,
 
 	state.duty_cycle = duty_ns;
 	state.period = period_ns;
+	state.mode = mode;
 	return pwm_apply_state(pwm, &state);
 }
 
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 08/16] drivers: pwm: core: extend PWM framework with PWM modes
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Add basic PWM modes: normal and complementary. These modes should
differentiate the single output per channel PWM controllers from multiple
outputs per channel PWM controllers. These modes whould be set as follow:
1. PWM controllers with only one output per channel:
- normal mode
2. PWM controllers with more than one output per channel:
- normal mode
- complementary mode
Since users could use the PWM channel of a multiple output per channel PWM
controller, he could set the channel in normal mode and use only one
physical output.
The PWM modes were implemented as capabilities of PWM chip. In the probe
function of PWM driver the PWM capabilities (which currently contains only
PWM modes) should be provided in the structure of PWM chip. If no
capabilities are provided by the probe function, the default capabilities
will be used (the default capabilities involves PWM normal mode).
Every PWM channel will have associated a mode in the PWM state. Proper
helper functions were added to get/set PWM mode. The mode could also be set
from DT. Only modes registered for PWM chip could be set for a PWM channel.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/pwm/core.c  | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 drivers/pwm/sysfs.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/pwm.h | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 182 insertions(+), 2 deletions(-)

diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 7208b95e8d2f..99126127a467 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -41,6 +41,10 @@ static LIST_HEAD(pwm_chips);
 static DECLARE_BITMAP(allocated_pwms, MAX_PWMS);
 static RADIX_TREE(pwm_tree, GFP_KERNEL);
 
+static const struct pwm_caps pwm_chip_default_caps = {
+	.modes = PWM_MODE_NORMAL,
+};
+
 static struct pwm_device *pwm_to_device(unsigned int pwm)
 {
 	return radix_tree_lookup(&pwm_tree, pwm);
@@ -142,6 +146,31 @@ static inline void of_pwm_xlate_flags(struct pwm_device *pwm,
 		pwm->args.polarity = PWM_POLARITY_NORMAL;
 }
 
+static inline bool pwm_mode_valid(const struct pwm_chip *chip,
+				  const enum pwm_mode mode)
+{
+	return !!(chip->caps->modes & mode);
+}
+
+static inline void of_pwm_xlate_mode(struct pwm_device *pwm,
+				     const struct of_phandle_args *args)
+{
+	unsigned int first = 0, last = 0;
+
+	if (args->args_count >= PWM_ARGS_CNT_XLATE_MODE) {
+		first = ffs(args->args[PWM_ARGS_CNT_XLATE_MODE - 1]);
+		last = fls(args->args[PWM_ARGS_CNT_XLATE_MODE - 1]);
+	}
+
+	/* At least one valid mode provided from DT: use one valid mode */
+	if (first && pwm_mode_valid(pwm->chip, BIT(first - 1)))
+		pwm->args.mode = BIT(first - 1);
+	else if (last && pwm_mode_valid(pwm->chip, BIT(last - 1)))
+		pwm->args.mode = BIT(last - 1);
+	else /* Invalid modes provided from DT: use first available chip mode */
+		pwm->args.mode = BIT(ffs(pwm->chip->caps->modes) - 1);
+}
+
 struct pwm_device *of_pwm_xlate(struct pwm_chip *pc,
 				const struct of_phandle_args *args)
 {
@@ -161,6 +190,7 @@ struct pwm_device *of_pwm_xlate(struct pwm_chip *pc,
 	pwm->args.period = args->args[PWM_ARGS_CNT_XLATE_PERIOD - 1];
 
 	of_pwm_xlate_flags(pwm, args);
+	of_pwm_xlate_mode(pwm, args);
 
 	return pwm;
 }
@@ -223,6 +253,18 @@ static bool pwm_ops_check(const struct pwm_ops *ops)
 	return false;
 }
 
+static inline bool pwm_caps_valid(const struct pwm_caps *caps)
+{
+	unsigned long modes = PWM_MODE_MAX - 1;
+
+	return !!((modes & caps->modes) == caps->modes);
+}
+
+static inline bool pwm_caps_zero(const struct pwm_caps *caps)
+{
+	return !!(caps->modes == 0);
+}
+
 /**
  * pwmchip_add_with_polarity() - register a new PWM chip
  * @chip: the PWM chip to add
@@ -247,8 +289,14 @@ int pwmchip_add_with_polarity(struct pwm_chip *chip,
 	if (!pwm_ops_check(chip->ops))
 		return -EINVAL;
 
+	if (chip->caps && !pwm_caps_valid(chip->caps))
+		return -EINVAL;
+
 	mutex_lock(&pwm_lock);
 
+	if (!chip->caps || (chip->caps && pwm_caps_zero(chip->caps)))
+		chip->caps = &pwm_chip_default_caps;
+
 	ret = alloc_pwms(chip->base, chip->npwm);
 	if (ret < 0)
 		goto out;
@@ -268,6 +316,7 @@ int pwmchip_add_with_polarity(struct pwm_chip *chip,
 		pwm->pwm = chip->base + i;
 		pwm->hwpwm = i;
 		pwm->state.polarity = polarity;
+		pwm->state.mode = BIT(ffs(chip->caps->modes) - 1);
 
 		if (chip->ops->get_state)
 			chip->ops->get_state(chip, pwm, &pwm->state);
@@ -443,7 +492,11 @@ int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state)
 	int err;
 
 	if (!pwm || !state || !state->period ||
-	    state->duty_cycle > state->period)
+	    state->duty_cycle > state->period ||
+	    !pwm->chip || !pwm->chip->caps ||
+	    !pwm_mode_valid(pwm->chip, state->mode) ||
+	    /* Only one active mode at a time. */
+	    fls(state->mode) != ffs(state->mode))
 		return -EINVAL;
 
 	if (!memcmp(state, &pwm->state, sizeof(*state)))
@@ -504,6 +557,9 @@ int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state)
 
 			pwm->state.enabled = state->enabled;
 		}
+
+		/* No mode support for non-atomic PWM. */
+		pwm->state.mode = state->mode;
 	}
 
 	return 0;
@@ -553,6 +609,8 @@ int pwm_adjust_config(struct pwm_device *pwm)
 	pwm_get_args(pwm, &pargs);
 	pwm_get_state(pwm, &state);
 
+	state.mode = pargs.mode;
+
 	/*
 	 * If the current period is zero it means that either the PWM driver
 	 * does not support initial state retrieval or the PWM has not yet
@@ -824,6 +882,7 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
 
 	pwm->args.period = chosen->period;
 	pwm->args.polarity = chosen->polarity;
+	pwm->args.mode = BIT(ffs(chip->caps->modes) - 1);
 
 	return pwm;
 }
@@ -973,6 +1032,7 @@ static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)
 		seq_printf(s, " duty: %u ns", state.duty_cycle);
 		seq_printf(s, " polarity: %s",
 			   state.polarity ? "inverse" : "normal");
+		seq_printf(s, " mode: %s", pwm_get_mode_desc(state.mode));
 
 		seq_puts(s, "\n");
 	}
diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c
index 83f2b0b15712..7d111ab17e43 100644
--- a/drivers/pwm/sysfs.c
+++ b/drivers/pwm/sysfs.c
@@ -223,11 +223,50 @@ static ssize_t capture_show(struct device *child,
 	return sprintf(buf, "%u %u\n", result.period, result.duty_cycle);
 }
 
+static ssize_t mode_show(struct device *child,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	const struct pwm_device *pwm = child_to_pwm_device(child);
+	struct pwm_state state;
+
+	pwm_get_state(pwm, &state);
+
+	return sprintf(buf, "%s\n", pwm_get_mode_desc(state.mode));
+}
+
+static ssize_t mode_store(struct device *child,
+			  struct device_attribute *attr,
+			  const char *buf, size_t size)
+{
+	struct pwm_export *export = child_to_pwm_export(child);
+	struct pwm_device *pwm = export->pwm;
+	struct pwm_state state;
+	enum pwm_mode mode;
+	int ret;
+
+	if (sysfs_streq(buf, pwm_get_mode_desc(PWM_MODE_NORMAL)))
+		mode = PWM_MODE_NORMAL;
+	else if (sysfs_streq(buf, pwm_get_mode_desc(PWM_MODE_COMPLEMENTARY)))
+		mode = PWM_MODE_COMPLEMENTARY;
+	else
+		return -EINVAL;
+
+	mutex_lock(&export->lock);
+	pwm_get_state(pwm, &state);
+	state.mode = mode;
+	ret = pwm_apply_state(pwm, &state);
+	mutex_unlock(&export->lock);
+
+	return ret ? : size;
+}
+
 static DEVICE_ATTR_RW(period);
 static DEVICE_ATTR_RW(duty_cycle);
 static DEVICE_ATTR_RW(enable);
 static DEVICE_ATTR_RW(polarity);
 static DEVICE_ATTR_RO(capture);
+static DEVICE_ATTR_RW(mode);
 
 static struct attribute *pwm_attrs[] = {
 	&dev_attr_period.attr,
@@ -235,6 +274,7 @@ static struct attribute *pwm_attrs[] = {
 	&dev_attr_enable.attr,
 	&dev_attr_polarity.attr,
 	&dev_attr_capture.attr,
+	&dev_attr_mode.attr,
 	NULL
 };
 ATTRIBUTE_GROUPS(pwm);
@@ -362,10 +402,32 @@ static ssize_t npwm_show(struct device *parent, struct device_attribute *attr,
 }
 static DEVICE_ATTR_RO(npwm);
 
+static ssize_t modes_show(struct device *parent,
+			  struct device_attribute *attr,
+			  char *buf)
+{
+	const struct pwm_chip *chip = dev_get_drvdata(parent);
+	enum pwm_mode mode;
+	int i, len = 0;
+
+	for (i = 0; i < PWM_MODE_MAX - 1; i++) {
+		mode = BIT(i);
+		if (chip->caps->modes & mode)
+			len += scnprintf(buf + len, PAGE_SIZE - len, "%s ",
+					 pwm_get_mode_desc(mode));
+	}
+
+	len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
+
+	return len;
+}
+static DEVICE_ATTR_RO(modes);
+
 static struct attribute *pwm_chip_attrs[] = {
 	&dev_attr_export.attr,
 	&dev_attr_unexport.attr,
 	&dev_attr_npwm.attr,
+	&dev_attr_modes.attr,
 	NULL,
 };
 ATTRIBUTE_GROUPS(pwm_chip);
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index 4bb628b94d88..0fdc680651aa 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -26,9 +26,23 @@ enum pwm_polarity {
 };
 
 /**
+ * enum pwm_mode - PWM working modes
+ * PWM_MODE_NORMAL: PWM has one output per channel
+ * PWM_MODE_COMPLEMENTARY: PWM has 2 outputs per channel with opposite polarity
+ * PWM_MODE_MAX: Used to get the defined PWM modes mask (PWM_MODE_MAX - 1)
+ * phase-shifted
+ */
+enum pwm_mode {
+	PWM_MODE_NORMAL		= BIT(0),
+	PWM_MODE_COMPLEMENTARY	= BIT(1),
+	PWM_MODE_MAX		= BIT(2),
+};
+
+/**
  * struct pwm_args - board-dependent PWM arguments
  * @period: reference period
  * @polarity: reference polarity
+ * @mode: reference mode
  *
  * This structure describes board-dependent arguments attached to a PWM
  * device. These arguments are usually retrieved from the PWM lookup table or
@@ -41,6 +55,7 @@ enum pwm_polarity {
 struct pwm_args {
 	unsigned int period;
 	enum pwm_polarity polarity;
+	enum pwm_mode mode;
 };
 
 enum {
@@ -52,12 +67,22 @@ enum {
  * enum pwm_args_xlate_options - options for translating PWM options
  * @PWM_ARGS_CNT_XLATE_PERIOD: translate period
  * @PWM_ARGS_CNT_XLATE_FLAGS: translate flags (polarity flags)
+ * @PWM_ARGS_CNT_XLATE_MODE: translate with flags and mode
  * @PWM_ARGS_CNT_XLATE_MAX: maximum number of translate options
  */
 enum pwm_args_xlate_options {
 	PWM_ARGS_CNT_XLATE_PERIOD = 2,
 	PWM_ARGS_CNT_XLATE_FLAGS,
-	PWM_ARGS_CNT_XLATE_MAX = PWM_ARGS_CNT_XLATE_FLAGS,
+	PWM_ARGS_CNT_XLATE_MODE,
+	PWM_ARGS_CNT_XLATE_MAX = PWM_ARGS_CNT_XLATE_MODE,
+};
+
+/**
+ * struct pwm_caps - PWM capabilities
+ * @modes: PWM chip supported modes
+ */
+struct pwm_caps {
+	unsigned long modes;
 };
 
 /*
@@ -65,12 +90,14 @@ enum pwm_args_xlate_options {
  * @period: PWM period (in nanoseconds)
  * @duty_cycle: PWM duty cycle (in nanoseconds)
  * @polarity: PWM polarity
+ * @mode: PWM mode
  * @enabled: PWM enabled status
  */
 struct pwm_state {
 	unsigned int period;
 	unsigned int duty_cycle;
 	enum pwm_polarity polarity;
+	enum pwm_mode mode;
 	bool enabled;
 };
 
@@ -156,6 +183,21 @@ static inline enum pwm_polarity pwm_get_polarity(const struct pwm_device *pwm)
 	return state.polarity;
 }
 
+static inline enum pwm_mode pwm_get_mode(const struct pwm_device *pwm)
+{
+	struct pwm_state state;
+
+	pwm_get_state(pwm, &state);
+
+	return state.mode;
+}
+
+static inline void pwm_set_mode(struct pwm_device *pwm, enum pwm_mode mode)
+{
+	if (pwm)
+		pwm->state.mode = mode;
+}
+
 static inline void pwm_get_args(const struct pwm_device *pwm,
 				struct pwm_args *args)
 {
@@ -193,6 +235,7 @@ static inline void pwm_init_state(const struct pwm_device *pwm,
 	state->period = args.period;
 	state->polarity = args.polarity;
 	state->duty_cycle = 0;
+	state->mode = args.mode;
 }
 
 /**
@@ -295,6 +338,7 @@ struct pwm_ops {
  * @dev: device providing the PWMs
  * @list: list node for internal use
  * @ops: callbacks for this PWM controller
+ * @caps: capabilities for this PWM controller
  * @base: number of first PWM controlled by this chip
  * @npwm: number of PWMs controlled by this chip
  * @pwms: array of PWM devices allocated by the framework
@@ -303,6 +347,7 @@ struct pwm_chip {
 	struct device *dev;
 	struct list_head list;
 	const struct pwm_ops *ops;
+	const struct pwm_caps *caps;
 	int base;
 	unsigned int npwm;
 	struct pwm_device *pwms;
@@ -429,6 +474,13 @@ static inline void pwm_disable(struct pwm_device *pwm)
 	pwm_apply_state(pwm, &state);
 }
 
+static inline const char * const pwm_get_mode_desc(enum pwm_mode mode)
+{
+	static const char * const modes[] = { "normal", "complementary" };
+
+	return mode ? modes[ffs(mode) - 1] : "invalid";
+}
+
 /* PWM provider APIs */
 int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
 		unsigned long timeout);
@@ -503,6 +555,11 @@ static inline void pwm_disable(struct pwm_device *pwm)
 {
 }
 
+static inline const char * const pwm_get_mode_desc(enum pwm_mode mode)
+{
+	return NULL;
+}
+
 static inline int pwm_set_chip_data(struct pwm_device *pwm, void *data)
 {
 	return -EINVAL;
@@ -597,6 +654,7 @@ static inline void pwm_apply_args(struct pwm_device *pwm)
 	state.enabled = false;
 	state.polarity = pwm->args.polarity;
 	state.period = pwm->args.period;
+	state.mode = pwm->args.mode;
 
 	pwm_apply_state(pwm, &state);
 }
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 07/16] arm64: dts: rockchip: update pwm-cells
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Update pwm-cells to 2 to allow initialization of channel number an period.

Cc: Brian Norris <briannorris@chromium.org>
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 arch/arm64/boot/dts/rockchip/rk3399-gru-kevin.dts | 2 +-
 arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/boot/dts/rockchip/rk3399-gru-kevin.dts b/arch/arm64/boot/dts/rockchip/rk3399-gru-kevin.dts
index 0384e3121f18..0c790ec387eb 100644
--- a/arch/arm64/boot/dts/rockchip/rk3399-gru-kevin.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3399-gru-kevin.dts
@@ -77,7 +77,7 @@
 
 	backlight: backlight {
 		compatible = "pwm-backlight";
-		pwms = <&cros_ec_pwm 1>;
+		pwms = <&cros_ec_pwm 1 65535>;
 		brightness-levels = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
 				     17 18 19 20 21 22 23 24 25 26 27 28 29 30
 				     31 32 33 34 35 36 37 38 39 40 41 42 43 44
diff --git a/arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi b/arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi
index 5772c52fbfd3..aa377f9ae6ad 100644
--- a/arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi
@@ -853,7 +853,7 @@ ap_i2c_audio: &i2c8 {
 
 		cros_ec_pwm: ec-pwm {
 			compatible = "google,cros-ec-pwm";
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 		};
 	};
 };
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 06/16] ARM: dts: pxa: update pwm-cells
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Update pwm-cells to 2 to allow initialization of channel number an period.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 arch/arm/boot/dts/pxa25x.dtsi | 4 ++--
 arch/arm/boot/dts/pxa27x.dtsi | 8 ++++----
 arch/arm/boot/dts/pxa3xx.dtsi | 8 ++++----
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/arch/arm/boot/dts/pxa25x.dtsi b/arch/arm/boot/dts/pxa25x.dtsi
index 95d59be97213..8c55b3552c42 100644
--- a/arch/arm/boot/dts/pxa25x.dtsi
+++ b/arch/arm/boot/dts/pxa25x.dtsi
@@ -70,14 +70,14 @@
 		pwm0: pwm at 40b00000 {
 			compatible = "marvell,pxa250-pwm";
 			reg = <0x40b00000 0x10>;
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 			clocks = <&clks CLK_PWM0>;
 		};
 
 		pwm1: pwm at 40b00010 {
 			compatible = "marvell,pxa250-pwm";
 			reg = <0x40b00010 0x10>;
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 			clocks = <&clks CLK_PWM1>;
 		};
 	};
diff --git a/arch/arm/boot/dts/pxa27x.dtsi b/arch/arm/boot/dts/pxa27x.dtsi
index 747f750f675d..e3db171cfeb1 100644
--- a/arch/arm/boot/dts/pxa27x.dtsi
+++ b/arch/arm/boot/dts/pxa27x.dtsi
@@ -46,28 +46,28 @@
 		pwm0: pwm at 40b00000 {
 			compatible = "marvell,pxa270-pwm", "marvell,pxa250-pwm";
 			reg = <0x40b00000 0x10>;
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 			clocks = <&clks CLK_PWM0>;
 		};
 
 		pwm1: pwm at 40b00010 {
 			compatible = "marvell,pxa270-pwm", "marvell,pxa250-pwm";
 			reg = <0x40b00010 0x10>;
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 			clocks = <&clks CLK_PWM1>;
 		};
 
 		pwm2: pwm at 40c00000 {
 			compatible = "marvell,pxa270-pwm", "marvell,pxa250-pwm";
 			reg = <0x40c00000 0x10>;
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 			clocks = <&clks CLK_PWM0>;
 		};
 
 		pwm3: pwm at 40c00010 {
 			compatible = "marvell,pxa270-pwm", "marvell,pxa250-pwm";
 			reg = <0x40c00010 0x10>;
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 			clocks = <&clks CLK_PWM1>;
 		};
 
diff --git a/arch/arm/boot/dts/pxa3xx.dtsi b/arch/arm/boot/dts/pxa3xx.dtsi
index 55c75b67351c..1f37295b05a3 100644
--- a/arch/arm/boot/dts/pxa3xx.dtsi
+++ b/arch/arm/boot/dts/pxa3xx.dtsi
@@ -200,7 +200,7 @@
 		pwm0: pwm at 40b00000 {
 			compatible = "marvell,pxa270-pwm";
 			reg = <0x40b00000 0x10>;
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 			clocks = <&clks CLK_PWM0>;
 			status = "disabled";
 		};
@@ -208,7 +208,7 @@
 		pwm1: pwm at 40b00010 {
 			compatible = "marvell,pxa270-pwm";
 			reg = <0x40b00010 0x10>;
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 			clocks = <&clks CLK_PWM1>;
 			status = "disabled";
 		};
@@ -216,7 +216,7 @@
 		pwm2: pwm at 40c00000 {
 			compatible = "marvell,pxa270-pwm";
 			reg = <0x40c00000 0x10>;
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 			clocks = <&clks CLK_PWM0>;
 			status = "disabled";
 		};
@@ -224,7 +224,7 @@
 		pwm3: pwm at 40c00010 {
 			compatible = "marvell,pxa270-pwm";
 			reg = <0x40c00010 0x10>;
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 			clocks = <&clks CLK_PWM1>;
 			status = "disabled";
 		};
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 05/16] ARM: dts: clps711x: update pwm-cells
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Update pwm-cells to 2 to allow initialization of channel number an period.

Cc: Alexander Shiyan <shc_work@mail.ru>
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 arch/arm/boot/dts/ep7209.dtsi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm/boot/dts/ep7209.dtsi b/arch/arm/boot/dts/ep7209.dtsi
index aaf1261d2ee4..fdfe6ab31569 100644
--- a/arch/arm/boot/dts/ep7209.dtsi
+++ b/arch/arm/boot/dts/ep7209.dtsi
@@ -133,7 +133,7 @@
 			compatible = "cirrus,ep7209-pwm";
 			reg = <0x80000400 0x4>;
 			clocks = <&clks CLPS711X_CLK_PWM>;
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 		};
 
 		uart1: uart at 80000480 {
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 04/16] pwm: clps711x: update documentation regarding pwm-cells
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

pwm-cells should be at least 2 to provide channel number and period value.

Cc: Alexander Shiyan <shc_work@mail.ru>
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 Documentation/devicetree/bindings/pwm/cirrus,clps711x-pwm.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/pwm/cirrus,clps711x-pwm.txt b/Documentation/devicetree/bindings/pwm/cirrus,clps711x-pwm.txt
index c0b2028238d6..57f480a872e3 100644
--- a/Documentation/devicetree/bindings/pwm/cirrus,clps711x-pwm.txt
+++ b/Documentation/devicetree/bindings/pwm/cirrus,clps711x-pwm.txt
@@ -4,12 +4,12 @@ Required properties:
 - compatible: Shall contain "cirrus,ep7209-pwm".
 - reg: Physical base address and length of the controller's registers.
 - clocks: phandle + clock specifier pair of the PWM reference clock.
-- #pwm-cells: Should be 1. The cell specifies the index of the channel.
+- #pwm-cells: Should be 2. The cell specifies the index of the channel.
 
 Example:
 	pwm: pwm at 80000400 {
 		compatible = "cirrus,ep7312-pwm", "cirrus,ep7209-pwm";
 		reg = <0x80000400 0x4>;
 		clocks = <&clks 8>;
-		#pwm-cells = <1>;
+		#pwm-cells = <2>;
 	};
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 03/16] pwm: cros-ec: update documentation regarding pwm-cells
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

pwm-cells should be at least 2 to provide channel number and period value.

Cc: Brian Norris <briannorris@chromium.org>
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt b/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt
index 472bd46ab5a4..03347fd302b5 100644
--- a/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt
+++ b/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt
@@ -8,7 +8,7 @@ Documentation/devicetree/bindings/mfd/cros-ec.txt).
 
 Required properties:
 - compatible: Must contain "google,cros-ec-pwm"
-- #pwm-cells: Should be 1. The cell specifies the PWM index.
+- #pwm-cells: Should be 2. The cell specifies the PWM index.
 
 Example:
 	cros-ec at 0 {
@@ -18,6 +18,6 @@ Example:
 
 		cros_ec_pwm: ec-pwm {
 			compatible = "google,cros-ec-pwm";
-			#pwm-cells = <1>;
+			#pwm-cells = <2>;
 		};
 	};
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 02/16] pwm: pxa: update documentation regarding pwm-cells
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

pwm-cells should be at least 2 to provide channel number and period value.

Cc: Mike Dunn <mikedunn@newsguy.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 Documentation/devicetree/bindings/pwm/pxa-pwm.txt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/pwm/pxa-pwm.txt b/Documentation/devicetree/bindings/pwm/pxa-pwm.txt
index 5ae9f1e3c338..a0e20edeeebc 100644
--- a/Documentation/devicetree/bindings/pwm/pxa-pwm.txt
+++ b/Documentation/devicetree/bindings/pwm/pxa-pwm.txt
@@ -10,7 +10,7 @@ Required properties:
   Note that one device instance must be created for each PWM that is used, so the
   length covers only the register window for one PWM output, not that of the
   entire PWM controller.  Currently length is 0x10 for all supported devices.
-- #pwm-cells: Should be 1.  This cell is used to specify the period in
+- #pwm-cells: Should be 2.  This cell is used to specify the period in
   nanoseconds.
 
 Example PWM device node:
@@ -18,13 +18,13 @@ Example PWM device node:
 pwm0: pwm at 40b00000 {
 	compatible = "marvell,pxa250-pwm";
 	reg = <0x40b00000 0x10>;
-	#pwm-cells = <1>;
+	#pwm-cells = <2>;
 };
 
 Example PWM client node:
 
 backlight {
 	compatible = "pwm-backlight";
-	pwms = <&pwm0 5000000>;
+	pwms = <&pwm0 0 5000000>;
 	...
 }
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 01/16] drivers: pwm: core: use a single of xlate function
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1515766983-15151-1-git-send-email-claudiu.beznea@microchip.com>

Remove of_pwm_simple_xlate() and of_pwm_xlate_with_flags() functions
and add of_pwm_xlate() which is used in all cases no mather if the OF
bindings are with PWM flags or not. This should not affect the old
behavior since the xlate will be based on #pwm-cells property of the
PWM controller. Based on #pwm-cells property the xlate will consider
the flags or not. This will permit the addition of other inputs to OF
xlate by just adding proper code at the end of of_pwm_xlate() and a new
input to enum pwm_args_xlate_options. With this changes there will be
no need to fill of_xlate and of_pwm_n_cells of struct pwm_chip from
the drivers probe methods. References in drives to references to of_xlate
and of_pwm_n_cells were removed. Drivers which used private of_xlate
functions switched to the generic of_pwm_xlate() function which fits
for it but with little changes in device trees (these drivers translated
differently the "pwms" bindings; the "pwms" bindings now are generic to
all drivers and all drivers should provide them in the format described
in pwm documentation).

Cc: Thierry Reding <thierry.reding@gmail.com>
Cc: Mike Dunn <mikedunn@newsguy.com>
Cc: Brian Norris <briannorris@chromium.org>
Cc: Alexander Shiyan <shc_work@mail.ru>
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---

This patch (and the next 7) could be applied independetly by this series, if
any, but I choosed to have it here since it makes easy the PWM modes parsing.
If you feel it could be independently of this series I could send a new version.

Also, Thierry, Mike, Brian, Shiyan, please take an extra look over pwm-pxa.c,
pwm-cros-ec.c and pwm-clps711x.c since these were moved to use the generic
pwms (minimum 2 pwm-cells).

 drivers/pwm/core.c             | 56 +++++++++++-------------------------------
 drivers/pwm/pwm-atmel-hlcdc.c  |  2 --
 drivers/pwm/pwm-atmel-tcb.c    |  2 --
 drivers/pwm/pwm-atmel.c        |  6 -----
 drivers/pwm/pwm-bcm-iproc.c    |  2 --
 drivers/pwm/pwm-bcm-kona.c     |  2 --
 drivers/pwm/pwm-bcm2835.c      |  2 --
 drivers/pwm/pwm-berlin.c       |  2 --
 drivers/pwm/pwm-clps711x.c     | 11 ---------
 drivers/pwm/pwm-cros-ec.c      | 20 ---------------
 drivers/pwm/pwm-fsl-ftm.c      |  2 --
 drivers/pwm/pwm-hibvt.c        |  2 --
 drivers/pwm/pwm-imx.c          |  8 ------
 drivers/pwm/pwm-lpc18xx-sct.c  |  2 --
 drivers/pwm/pwm-meson.c        |  2 --
 drivers/pwm/pwm-omap-dmtimer.c |  2 --
 drivers/pwm/pwm-pxa.c          | 19 --------------
 drivers/pwm/pwm-renesas-tpu.c  |  2 --
 drivers/pwm/pwm-rockchip.c     |  5 ----
 drivers/pwm/pwm-samsung.c      |  3 ---
 drivers/pwm/pwm-sun4i.c        |  2 --
 drivers/pwm/pwm-tiecap.c       |  2 --
 drivers/pwm/pwm-tiehrpwm.c     |  2 --
 drivers/pwm/pwm-vt8500.c       |  2 --
 drivers/pwm/pwm-zx.c           |  2 --
 include/linux/pwm.h            | 23 ++++++++++-------
 26 files changed, 29 insertions(+), 156 deletions(-)

diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 1581f6ab1b1f..7208b95e8d2f 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -132,47 +132,23 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
 	return 0;
 }
 
-struct pwm_device *
-of_pwm_xlate_with_flags(struct pwm_chip *pc, const struct of_phandle_args *args)
+static inline void of_pwm_xlate_flags(struct pwm_device *pwm,
+				      const struct of_phandle_args *args)
 {
-	struct pwm_device *pwm;
-
-	/* check, whether the driver supports a third cell for flags */
-	if (pc->of_pwm_n_cells < 3)
-		return ERR_PTR(-EINVAL);
-
-	/* flags in the third cell are optional */
-	if (args->args_count < 2)
-		return ERR_PTR(-EINVAL);
-
-	if (args->args[0] >= pc->npwm)
-		return ERR_PTR(-EINVAL);
-
-	pwm = pwm_request_from_chip(pc, args->args[0], NULL);
-	if (IS_ERR(pwm))
-		return pwm;
-
-	pwm->args.period = args->args[1];
-	pwm->args.polarity = PWM_POLARITY_NORMAL;
-
-	if (args->args_count > 2 && args->args[2] & PWM_POLARITY_INVERTED)
+	if (args->args_count >= PWM_ARGS_CNT_XLATE_FLAGS &&
+	    args->args[PWM_ARGS_CNT_XLATE_FLAGS - 1] & PWM_POLARITY_INVERTED)
 		pwm->args.polarity = PWM_POLARITY_INVERSED;
-
-	return pwm;
+	else
+		pwm->args.polarity = PWM_POLARITY_NORMAL;
 }
-EXPORT_SYMBOL_GPL(of_pwm_xlate_with_flags);
 
-static struct pwm_device *
-of_pwm_simple_xlate(struct pwm_chip *pc, const struct of_phandle_args *args)
+struct pwm_device *of_pwm_xlate(struct pwm_chip *pc,
+				const struct of_phandle_args *args)
 {
 	struct pwm_device *pwm;
 
-	/* sanity check driver support */
-	if (pc->of_pwm_n_cells < 2)
-		return ERR_PTR(-EINVAL);
-
-	/* all cells are required */
-	if (args->args_count != pc->of_pwm_n_cells)
+	if (args->args_count < PWM_ARGS_CNT_XLATE_PERIOD ||
+	    args->args_count > PWM_ARGS_CNT_XLATE_MAX)
 		return ERR_PTR(-EINVAL);
 
 	if (args->args[0] >= pc->npwm)
@@ -182,21 +158,19 @@ of_pwm_simple_xlate(struct pwm_chip *pc, const struct of_phandle_args *args)
 	if (IS_ERR(pwm))
 		return pwm;
 
-	pwm->args.period = args->args[1];
+	pwm->args.period = args->args[PWM_ARGS_CNT_XLATE_PERIOD - 1];
+
+	of_pwm_xlate_flags(pwm, args);
 
 	return pwm;
 }
+EXPORT_SYMBOL_GPL(of_pwm_xlate);
 
 static void of_pwmchip_add(struct pwm_chip *chip)
 {
 	if (!chip->dev || !chip->dev->of_node)
 		return;
 
-	if (!chip->of_xlate) {
-		chip->of_xlate = of_pwm_simple_xlate;
-		chip->of_pwm_n_cells = 2;
-	}
-
 	of_node_get(chip->dev->of_node);
 }
 
@@ -685,7 +659,7 @@ struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id)
 		goto put;
 	}
 
-	pwm = pc->of_xlate(pc, &args);
+	pwm = of_pwm_xlate(pc, &args);
 	if (IS_ERR(pwm))
 		goto put;
 
diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c
index 54c6633d9b5d..b574e9e639f1 100644
--- a/drivers/pwm/pwm-atmel-hlcdc.c
+++ b/drivers/pwm/pwm-atmel-hlcdc.c
@@ -277,8 +277,6 @@ static int atmel_hlcdc_pwm_probe(struct platform_device *pdev)
 	chip->chip.dev = dev;
 	chip->chip.base = -1;
 	chip->chip.npwm = 1;
-	chip->chip.of_xlate = of_pwm_xlate_with_flags;
-	chip->chip.of_pwm_n_cells = 3;
 
 	ret = pwmchip_add_with_polarity(&chip->chip, PWM_POLARITY_INVERSED);
 	if (ret) {
diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c
index acd3ce8ecf3f..e75bef50b831 100644
--- a/drivers/pwm/pwm-atmel-tcb.c
+++ b/drivers/pwm/pwm-atmel-tcb.c
@@ -407,8 +407,6 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev)
 
 	tcbpwm->chip.dev = &pdev->dev;
 	tcbpwm->chip.ops = &atmel_tcb_pwm_ops;
-	tcbpwm->chip.of_xlate = of_pwm_xlate_with_flags;
-	tcbpwm->chip.of_pwm_n_cells = 3;
 	tcbpwm->chip.base = -1;
 	tcbpwm->chip.npwm = NPWM;
 	tcbpwm->tc = tc;
diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c
index 530d7dc5f1b5..32427d2b7877 100644
--- a/drivers/pwm/pwm-atmel.c
+++ b/drivers/pwm/pwm-atmel.c
@@ -365,12 +365,6 @@ static int atmel_pwm_probe(struct platform_device *pdev)
 
 	atmel_pwm->chip.dev = &pdev->dev;
 	atmel_pwm->chip.ops = &atmel_pwm_ops;
-
-	if (pdev->dev.of_node) {
-		atmel_pwm->chip.of_xlate = of_pwm_xlate_with_flags;
-		atmel_pwm->chip.of_pwm_n_cells = 3;
-	}
-
 	atmel_pwm->chip.base = -1;
 	atmel_pwm->chip.npwm = 4;
 	atmel_pwm->regs = regs;
diff --git a/drivers/pwm/pwm-bcm-iproc.c b/drivers/pwm/pwm-bcm-iproc.c
index d961a8207b1c..773c53b6c13d 100644
--- a/drivers/pwm/pwm-bcm-iproc.c
+++ b/drivers/pwm/pwm-bcm-iproc.c
@@ -207,8 +207,6 @@ static int iproc_pwmc_probe(struct platform_device *pdev)
 	ip->chip.ops = &iproc_pwm_ops;
 	ip->chip.base = -1;
 	ip->chip.npwm = 4;
-	ip->chip.of_xlate = of_pwm_xlate_with_flags;
-	ip->chip.of_pwm_n_cells = 3;
 
 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	ip->base = devm_ioremap_resource(&pdev->dev, res);
diff --git a/drivers/pwm/pwm-bcm-kona.c b/drivers/pwm/pwm-bcm-kona.c
index 09a95aeb3a70..e3418e9b7828 100644
--- a/drivers/pwm/pwm-bcm-kona.c
+++ b/drivers/pwm/pwm-bcm-kona.c
@@ -274,8 +274,6 @@ static int kona_pwmc_probe(struct platform_device *pdev)
 	kp->chip.ops = &kona_pwm_ops;
 	kp->chip.base = -1;
 	kp->chip.npwm = 6;
-	kp->chip.of_xlate = of_pwm_xlate_with_flags;
-	kp->chip.of_pwm_n_cells = 3;
 
 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	kp->base = devm_ioremap_resource(&pdev->dev, res);
diff --git a/drivers/pwm/pwm-bcm2835.c b/drivers/pwm/pwm-bcm2835.c
index db001cba937f..c5dbf16d810b 100644
--- a/drivers/pwm/pwm-bcm2835.c
+++ b/drivers/pwm/pwm-bcm2835.c
@@ -167,8 +167,6 @@ static int bcm2835_pwm_probe(struct platform_device *pdev)
 	pc->chip.dev = &pdev->dev;
 	pc->chip.ops = &bcm2835_pwm_ops;
 	pc->chip.npwm = 2;
-	pc->chip.of_xlate = of_pwm_xlate_with_flags;
-	pc->chip.of_pwm_n_cells = 3;
 
 	platform_set_drvdata(pdev, pc);
 
diff --git a/drivers/pwm/pwm-berlin.c b/drivers/pwm/pwm-berlin.c
index 771859aca4be..ffe75a891d35 100644
--- a/drivers/pwm/pwm-berlin.c
+++ b/drivers/pwm/pwm-berlin.c
@@ -206,8 +206,6 @@ static int berlin_pwm_probe(struct platform_device *pdev)
 	pwm->chip.ops = &berlin_pwm_ops;
 	pwm->chip.base = -1;
 	pwm->chip.npwm = 4;
-	pwm->chip.of_xlate = of_pwm_xlate_with_flags;
-	pwm->chip.of_pwm_n_cells = 3;
 
 	ret = pwmchip_add(&pwm->chip);
 	if (ret < 0) {
diff --git a/drivers/pwm/pwm-clps711x.c b/drivers/pwm/pwm-clps711x.c
index 26ec24e457b1..432b6ff174e9 100644
--- a/drivers/pwm/pwm-clps711x.c
+++ b/drivers/pwm/pwm-clps711x.c
@@ -106,15 +106,6 @@ static const struct pwm_ops clps711x_pwm_ops = {
 	.owner = THIS_MODULE,
 };
 
-static struct pwm_device *clps711x_pwm_xlate(struct pwm_chip *chip,
-					     const struct of_phandle_args *args)
-{
-	if (args->args[0] >= chip->npwm)
-		return ERR_PTR(-EINVAL);
-
-	return pwm_request_from_chip(chip, args->args[0], NULL);
-}
-
 static int clps711x_pwm_probe(struct platform_device *pdev)
 {
 	struct clps711x_chip *priv;
@@ -137,8 +128,6 @@ static int clps711x_pwm_probe(struct platform_device *pdev)
 	priv->chip.dev = &pdev->dev;
 	priv->chip.base = -1;
 	priv->chip.npwm = 2;
-	priv->chip.of_xlate = clps711x_pwm_xlate;
-	priv->chip.of_pwm_n_cells = 1;
 
 	spin_lock_init(&priv->lock);
 
diff --git a/drivers/pwm/pwm-cros-ec.c b/drivers/pwm/pwm-cros-ec.c
index 9c13694eaa24..692298693768 100644
--- a/drivers/pwm/pwm-cros-ec.c
+++ b/drivers/pwm/pwm-cros-ec.c
@@ -133,24 +133,6 @@ static void cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
 	state->duty_cycle = ret;
 }
 
-static struct pwm_device *
-cros_ec_pwm_xlate(struct pwm_chip *pc, const struct of_phandle_args *args)
-{
-	struct pwm_device *pwm;
-
-	if (args->args[0] >= pc->npwm)
-		return ERR_PTR(-EINVAL);
-
-	pwm = pwm_request_from_chip(pc, args->args[0], NULL);
-	if (IS_ERR(pwm))
-		return pwm;
-
-	/* The EC won't let us change the period */
-	pwm->args.period = EC_PWM_MAX_DUTY;
-
-	return pwm;
-}
-
 static const struct pwm_ops cros_ec_pwm_ops = {
 	.get_state	= cros_ec_pwm_get_state,
 	.apply		= cros_ec_pwm_apply,
@@ -207,8 +189,6 @@ static int cros_ec_pwm_probe(struct platform_device *pdev)
 	/* PWM chip */
 	chip->dev = dev;
 	chip->ops = &cros_ec_pwm_ops;
-	chip->of_xlate = cros_ec_pwm_xlate;
-	chip->of_pwm_n_cells = 1;
 	chip->base = -1;
 	ret = cros_ec_num_pwms(ec);
 	if (ret < 0) {
diff --git a/drivers/pwm/pwm-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c
index 557b4ea16796..7b9d59796ebd 100644
--- a/drivers/pwm/pwm-fsl-ftm.c
+++ b/drivers/pwm/pwm-fsl-ftm.c
@@ -442,8 +442,6 @@ static int fsl_pwm_probe(struct platform_device *pdev)
 		return PTR_ERR(fpc->clk[FSL_PWM_CLK_CNTEN]);
 
 	fpc->chip.ops = &fsl_pwm_ops;
-	fpc->chip.of_xlate = of_pwm_xlate_with_flags;
-	fpc->chip.of_pwm_n_cells = 3;
 	fpc->chip.base = -1;
 	fpc->chip.npwm = 8;
 
diff --git a/drivers/pwm/pwm-hibvt.c b/drivers/pwm/pwm-hibvt.c
index 27c107e78d59..afc74ef76551 100644
--- a/drivers/pwm/pwm-hibvt.c
+++ b/drivers/pwm/pwm-hibvt.c
@@ -196,8 +196,6 @@ static int hibvt_pwm_probe(struct platform_device *pdev)
 	pwm_chip->chip.dev = &pdev->dev;
 	pwm_chip->chip.base = -1;
 	pwm_chip->chip.npwm = soc->num_pwms;
-	pwm_chip->chip.of_xlate = of_pwm_xlate_with_flags;
-	pwm_chip->chip.of_pwm_n_cells = 3;
 
 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	pwm_chip->base = devm_ioremap_resource(&pdev->dev, res);
diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c
index 2ba5c3a398ff..cdfb04f40583 100644
--- a/drivers/pwm/pwm-imx.c
+++ b/drivers/pwm/pwm-imx.c
@@ -240,7 +240,6 @@ static const struct pwm_ops imx_pwm_ops_v2 = {
 };
 
 struct imx_pwm_data {
-	bool polarity_supported;
 	const struct pwm_ops *ops;
 };
 
@@ -249,7 +248,6 @@ static struct imx_pwm_data imx_pwm_data_v1 = {
 };
 
 static struct imx_pwm_data imx_pwm_data_v2 = {
-	.polarity_supported = true,
 	.ops = &imx_pwm_ops_v2,
 };
 
@@ -290,12 +288,6 @@ static int imx_pwm_probe(struct platform_device *pdev)
 	imx->chip.base = -1;
 	imx->chip.npwm = 1;
 
-	if (data->polarity_supported) {
-		dev_dbg(&pdev->dev, "PWM supports output inversion\n");
-		imx->chip.of_xlate = of_pwm_xlate_with_flags;
-		imx->chip.of_pwm_n_cells = 3;
-	}
-
 	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);
 	if (IS_ERR(imx->mmio_base))
diff --git a/drivers/pwm/pwm-lpc18xx-sct.c b/drivers/pwm/pwm-lpc18xx-sct.c
index d7f5f7de030d..ea825ec28da9 100644
--- a/drivers/pwm/pwm-lpc18xx-sct.c
+++ b/drivers/pwm/pwm-lpc18xx-sct.c
@@ -380,8 +380,6 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
 	lpc18xx_pwm->chip.ops = &lpc18xx_pwm_ops;
 	lpc18xx_pwm->chip.base = -1;
 	lpc18xx_pwm->chip.npwm = 16;
-	lpc18xx_pwm->chip.of_xlate = of_pwm_xlate_with_flags;
-	lpc18xx_pwm->chip.of_pwm_n_cells = 3;
 
 	/* SCT counter must be in unify (32 bit) mode */
 	lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CONFIG,
diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c
index 0767deba8e62..6702fcc203ce 100644
--- a/drivers/pwm/pwm-meson.c
+++ b/drivers/pwm/pwm-meson.c
@@ -535,8 +535,6 @@ static int meson_pwm_probe(struct platform_device *pdev)
 	meson->chip.ops = &meson_pwm_ops;
 	meson->chip.base = -1;
 	meson->chip.npwm = 2;
-	meson->chip.of_xlate = of_pwm_xlate_with_flags;
-	meson->chip.of_pwm_n_cells = 3;
 
 	meson->data = of_device_get_match_data(&pdev->dev);
 	meson->inverter_mask = BIT(meson->chip.npwm) - 1;
diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c
index 5ad42f33e70c..6bd32ae6dd3e 100644
--- a/drivers/pwm/pwm-omap-dmtimer.c
+++ b/drivers/pwm/pwm-omap-dmtimer.c
@@ -317,8 +317,6 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev)
 	omap->chip.ops = &pwm_omap_dmtimer_ops;
 	omap->chip.base = -1;
 	omap->chip.npwm = 1;
-	omap->chip.of_xlate = of_pwm_xlate_with_flags;
-	omap->chip.of_pwm_n_cells = 3;
 
 	mutex_init(&omap->mutex);
 
diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c
index 4143a46684d2..d66816037e87 100644
--- a/drivers/pwm/pwm-pxa.c
+++ b/drivers/pwm/pwm-pxa.c
@@ -151,20 +151,6 @@ static const struct platform_device_id *pxa_pwm_get_id_dt(struct device *dev)
 	return id ? id->data : NULL;
 }
 
-static struct pwm_device *
-pxa_pwm_of_xlate(struct pwm_chip *pc, const struct of_phandle_args *args)
-{
-	struct pwm_device *pwm;
-
-	pwm = pwm_request_from_chip(pc, 0, NULL);
-	if (IS_ERR(pwm))
-		return pwm;
-
-	pwm->args.period = args->args[0];
-
-	return pwm;
-}
-
 static int pwm_probe(struct platform_device *pdev)
 {
 	const struct platform_device_id *id = platform_get_device_id(pdev);
@@ -191,11 +177,6 @@ static int pwm_probe(struct platform_device *pdev)
 	pwm->chip.base = -1;
 	pwm->chip.npwm = (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1;
 
-	if (IS_ENABLED(CONFIG_OF)) {
-		pwm->chip.of_xlate = pxa_pwm_of_xlate;
-		pwm->chip.of_pwm_n_cells = 1;
-	}
-
 	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r);
 	if (IS_ERR(pwm->mmio_base))
diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c
index 29267d12fb4c..a4606957e7e5 100644
--- a/drivers/pwm/pwm-renesas-tpu.c
+++ b/drivers/pwm/pwm-renesas-tpu.c
@@ -418,8 +418,6 @@ static int tpu_probe(struct platform_device *pdev)
 
 	tpu->chip.dev = &pdev->dev;
 	tpu->chip.ops = &tpu_pwm_ops;
-	tpu->chip.of_xlate = of_pwm_xlate_with_flags;
-	tpu->chip.of_pwm_n_cells = 3;
 	tpu->chip.base = -1;
 	tpu->chip.npwm = TPU_CHANNEL_MAX;
 
diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c
index 4d99d468df09..c754c57118be 100644
--- a/drivers/pwm/pwm-rockchip.c
+++ b/drivers/pwm/pwm-rockchip.c
@@ -363,11 +363,6 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
 	pc->chip.base = -1;
 	pc->chip.npwm = 1;
 
-	if (pc->data->supports_polarity) {
-		pc->chip.of_xlate = of_pwm_xlate_with_flags;
-		pc->chip.of_pwm_n_cells = 3;
-	}
-
 	ret = pwmchip_add(&pc->chip);
 	if (ret < 0) {
 		clk_unprepare(pc->clk);
diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c
index 062f2cfc45ec..de171bfa4903 100644
--- a/drivers/pwm/pwm-samsung.c
+++ b/drivers/pwm/pwm-samsung.c
@@ -532,9 +532,6 @@ static int pwm_samsung_probe(struct platform_device *pdev)
 		ret = pwm_samsung_parse_dt(chip);
 		if (ret)
 			return ret;
-
-		chip->chip.of_xlate = of_pwm_xlate_with_flags;
-		chip->chip.of_pwm_n_cells = 3;
 	} else {
 		if (!pdev->dev.platform_data) {
 			dev_err(&pdev->dev, "no platform data specified\n");
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 334199c58f1d..b8476ce713a2 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -390,8 +390,6 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
 	pwm->chip.ops = &sun4i_pwm_ops;
 	pwm->chip.base = -1;
 	pwm->chip.npwm = pwm->data->npwm;
-	pwm->chip.of_xlate = of_pwm_xlate_with_flags;
-	pwm->chip.of_pwm_n_cells = 3;
 
 	spin_lock_init(&pwm->ctrl_lock);
 
diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c
index 34b228626bd5..4210f3ad87e1 100644
--- a/drivers/pwm/pwm-tiecap.c
+++ b/drivers/pwm/pwm-tiecap.c
@@ -238,8 +238,6 @@ static int ecap_pwm_probe(struct platform_device *pdev)
 
 	pc->chip.dev = &pdev->dev;
 	pc->chip.ops = &ecap_pwm_ops;
-	pc->chip.of_xlate = of_pwm_xlate_with_flags;
-	pc->chip.of_pwm_n_cells = 3;
 	pc->chip.base = -1;
 	pc->chip.npwm = 1;
 
diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index 4c22cb395040..7b0715624282 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -469,8 +469,6 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 
 	pc->chip.dev = &pdev->dev;
 	pc->chip.ops = &ehrpwm_pwm_ops;
-	pc->chip.of_xlate = of_pwm_xlate_with_flags;
-	pc->chip.of_pwm_n_cells = 3;
 	pc->chip.base = -1;
 	pc->chip.npwm = NUM_PWM_CHANNEL;
 
diff --git a/drivers/pwm/pwm-vt8500.c b/drivers/pwm/pwm-vt8500.c
index 3a78dd09ac81..3a923c1d30d3 100644
--- a/drivers/pwm/pwm-vt8500.c
+++ b/drivers/pwm/pwm-vt8500.c
@@ -216,8 +216,6 @@ static int vt8500_pwm_probe(struct platform_device *pdev)
 
 	chip->chip.dev = &pdev->dev;
 	chip->chip.ops = &vt8500_pwm_ops;
-	chip->chip.of_xlate = of_pwm_xlate_with_flags;
-	chip->chip.of_pwm_n_cells = 3;
 	chip->chip.base = -1;
 	chip->chip.npwm = VT8500_NR_PWMS;
 
diff --git a/drivers/pwm/pwm-zx.c b/drivers/pwm/pwm-zx.c
index 5d27c16edfb1..201b6f540ad6 100644
--- a/drivers/pwm/pwm-zx.c
+++ b/drivers/pwm/pwm-zx.c
@@ -228,8 +228,6 @@ static int zx_pwm_probe(struct platform_device *pdev)
 	zpc->chip.ops = &zx_pwm_ops;
 	zpc->chip.base = -1;
 	zpc->chip.npwm = 4;
-	zpc->chip.of_xlate = of_pwm_xlate_with_flags;
-	zpc->chip.of_pwm_n_cells = 3;
 
 	/*
 	 * PWM devices may be enabled by firmware, and let's disable all of
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index 56518adc31dd..4bb628b94d88 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -48,6 +48,18 @@ enum {
 	PWMF_EXPORTED = 1 << 1,
 };
 
+/**
+ * enum pwm_args_xlate_options - options for translating PWM options
+ * @PWM_ARGS_CNT_XLATE_PERIOD: translate period
+ * @PWM_ARGS_CNT_XLATE_FLAGS: translate flags (polarity flags)
+ * @PWM_ARGS_CNT_XLATE_MAX: maximum number of translate options
+ */
+enum pwm_args_xlate_options {
+	PWM_ARGS_CNT_XLATE_PERIOD = 2,
+	PWM_ARGS_CNT_XLATE_FLAGS,
+	PWM_ARGS_CNT_XLATE_MAX = PWM_ARGS_CNT_XLATE_FLAGS,
+};
+
 /*
  * struct pwm_state - state of a PWM channel
  * @period: PWM period (in nanoseconds)
@@ -286,8 +298,6 @@ struct pwm_ops {
  * @base: number of first PWM controlled by this chip
  * @npwm: number of PWMs controlled by this chip
  * @pwms: array of PWM devices allocated by the framework
- * @of_xlate: request a PWM device given a device tree PWM specifier
- * @of_pwm_n_cells: number of cells expected in the device tree PWM specifier
  */
 struct pwm_chip {
 	struct device *dev;
@@ -295,12 +305,7 @@ struct pwm_chip {
 	const struct pwm_ops *ops;
 	int base;
 	unsigned int npwm;
-
 	struct pwm_device *pwms;
-
-	struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
-					const struct of_phandle_args *args);
-	unsigned int of_pwm_n_cells;
 };
 
 /**
@@ -438,8 +443,8 @@ struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
 					 unsigned int index,
 					 const char *label);
 
-struct pwm_device *of_pwm_xlate_with_flags(struct pwm_chip *pc,
-		const struct of_phandle_args *args);
+struct pwm_device *of_pwm_xlate(struct pwm_chip *pc,
+				const struct of_phandle_args *args);
 
 struct pwm_device *pwm_get(struct device *dev, const char *con_id);
 struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id);
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 00/16] extend PWM framework to support PWM modes
From: Claudiu Beznea @ 2018-01-12 14:22 UTC (permalink / raw)
  To: linux-arm-kernel

Hi all,

Please give feedback on these patches which extends the PWM framework in order
to support multiple PWM modes of operations. This series is a rework of [1].

The current patch series add the following PWM modes:
- PWM mode normal
- PWM mode complementary
- PWM mode push-pull

Normal mode - for PWM chips with one output per PWM channel; output
waveforms looks like this:
             __    __    __    __
    PWM   __|  |__|  |__|  |__|  |__
            <--T-->

Complementary mode - for PWM chips with more than one output per PWM
channel; output waveforms for a PWM controller with 2 outputs per PWM
channel may looks line this:
             __    __    __    __
    PWMH1 __|  |__|  |__|  |__|  |__
          __    __    __    __    __
    PWML1   |__|  |__|  |__|  |__|
            <--T-->

    Where T is the signal period.

Push-pull mode - for PWM chips with mode than one output per PWM channel;
output waveform for a PWM controller with 2 outputs per PWM channel, in
push-pull mode, with normal polarity looks like this:
            __          __
    PWMH __|  |________|  |________
                  __          __
    PWML ________|  |________|  |__
           <--T-->

    If polarity is inversed:
         __    ________    ________
    PWMH   |__|        |__|
         ________    ________    __
    PWML         |__|        |__|
           <--T-->

    Where T is the signal period.

Every PWM device from a PWM chip could be configured in the modes registered
by PWM chip. For this, the PWM chip structure contains a field called caps which
keeps the PWM modes. At probe time the PWM chip registers the supported modes
and PWM devices could switch to the modes registered by PWM chips. For the moment,
in my opinion, it is not neccessary to add a new hook in pwm_ops to go through
the driver to check the capabilities of a single PWM device (as was proposed
in [1]).

I choosed to consider that a PWM controller with 2 outputs to also be capable to
work in normal mode, since the outputs could be connected independently to other
devices.

If the drivers doesn't register any PWM capabilities the default capabilities
will be used (currently, these includes PWM mode normal). 
 
These PWM mode could be configured via sysfs, or pwm_set_mode() +
pwm_apply_state() (for driver clients), or via DT.

In sysfs, user could check the PWM controller capabilities by reading modes file
of PWM chip:
root at sama5d2-xplained:/sys/class/pwm/pwmchip0# ls -l
total 0
lrwxrwxrwx 1 root root    0 Oct  7 03:18 device -> ../../../f802c000.pwm
--w------- 1 root root 4096 Oct  7 03:18 export
-r--r--r-- 1 root root 4096 Oct  7 03:18 modes
-r--r--r-- 1 root root 4096 Oct  7 03:18 npwm
drwxr-xr-x 2 root root    0 Oct  7 03:18 power
lrwxrwxrwx 1 root root    0 Oct  7 03:18 subsystem -> ../../../../../../../class/pwm
-rw-r--r-- 1 root root 4096 Oct  7 01:20 uevent
--w------- 1 root root 4096 Oct  7 03:18 unexport
root at sama5d2-xplained:/sys/class/pwm/pwmchip0# cat modes
normal complementary push-pull 

And the current capability of the PWM device could be checked as follows:
root at sama5d2-xplained:/sys/class/pwm/pwmchip0/pwm1# ls -l
-r--r--r--    1 root     root          4096 Feb  9 10:54 capture
-rw-r--r--    1 root     root          4096 Feb  9 10:54 duty_cycle
-rw-r--r--    1 root     root          4096 Feb  9 10:54 enable
-rw-r--r--    1 root     root          4096 Feb  9 10:54 mode
-rw-r--r--    1 root     root          4096 Feb  9 10:54 period
-rw-r--r--    1 root     root          4096 Feb  9 10:55 polarity
drwxr-xr-x    2 root     root             0 Feb  9 10:54 power
-rw-r--r--    1 root     root          4096 Feb  9 10:54 uevent

root at sama5d2-xplained:/sys/class/pwm/pwmchip0/pwm1# cat mode
normal

The PWM push-pull mode could be usefull in applications like
half bridge converters.

This series also add support for PWM modes on atmel SAMA5D2 SoC.

Thank you,
Claudiu Beznea

[1] https://www.spinics.net/lists/arm-kernel/msg580275.html

Changes in v2:
- remove of_xlate and of_pwm_n_cells and use generic functions to pharse DT
  inputs; this is done in patches 1, 2, 3, 4, 5, 6, 7 of this series; this will
  make easy the addition of PWM mode support from DT
- add PWM mode normal
- register PWM modes as capabilities of PWM chips at driver probe and, in case
  driver doesn't provide these capabilities use default ones
- change the way PWM mode is pharsed via DT by using a new input for pwms
  binding property

Claudiu Beznea (16):
  drivers: pwm: core: use a single of xlate function
  pwm: pxa: update documentation regarding pwm-cells
  pwm: cros-ec: update documentation regarding pwm-cells
  pwm: clps711x: update documentation regarding pwm-cells
  ARM: dts: clps711x: update pwm-cells
  ARM: dts: pxa: update pwm-cells
  arm64: dts: rockchip: update pwm-cells
  drivers: pwm: core: extend PWM framework with PWM modes
  drivers: pwm: core: add PWM mode to pwm_config()
  pwm: Add PWM modes
  pwm: add documentation for PWM modes
  pwm: atmel: add pwm capabilities
  drivers: pwm: core: add push-pull mode support
  pwm: add push-pull mode
  pwm: add documentation for pwm push-pull mode
  pwm: atmel: add push-pull mode support

 .../bindings/pwm/cirrus,clps711x-pwm.txt           |   4 +-
 .../devicetree/bindings/pwm/google,cros-ec-pwm.txt |   4 +-
 Documentation/devicetree/bindings/pwm/pwm.txt      |  23 ++++-
 Documentation/devicetree/bindings/pwm/pxa-pwm.txt  |   6 +-
 Documentation/pwm.txt                              |  47 ++++++++-
 arch/arm/boot/dts/ep7209.dtsi                      |   2 +-
 arch/arm/boot/dts/pxa25x.dtsi                      |   4 +-
 arch/arm/boot/dts/pxa27x.dtsi                      |   8 +-
 arch/arm/boot/dts/pxa3xx.dtsi                      |   8 +-
 arch/arm/mach-s3c24xx/mach-rx1950.c                |   5 +-
 arch/arm64/boot/dts/rockchip/rk3399-gru-kevin.dts  |   2 +-
 arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi       |   2 +-
 drivers/bus/ts-nbus.c                              |   2 +-
 drivers/clk/clk-pwm.c                              |   3 +-
 drivers/gpu/drm/i915/intel_panel.c                 |   8 +-
 drivers/hwmon/pwm-fan.c                            |   2 +-
 drivers/input/misc/max77693-haptic.c               |   2 +-
 drivers/input/misc/max8997_haptic.c                |   3 +-
 drivers/leds/leds-pwm.c                            |   2 +-
 drivers/media/rc/ir-rx51.c                         |   2 +-
 drivers/media/rc/pwm-ir-tx.c                       |   2 +-
 drivers/pwm/core.c                                 | 112 ++++++++++++++-------
 drivers/pwm/pwm-atmel-hlcdc.c                      |   2 -
 drivers/pwm/pwm-atmel-tcb.c                        |   2 -
 drivers/pwm/pwm-atmel.c                            | 100 ++++++++++++------
 drivers/pwm/pwm-bcm-iproc.c                        |   2 -
 drivers/pwm/pwm-bcm-kona.c                         |   2 -
 drivers/pwm/pwm-bcm2835.c                          |   2 -
 drivers/pwm/pwm-berlin.c                           |   2 -
 drivers/pwm/pwm-clps711x.c                         |  11 --
 drivers/pwm/pwm-cros-ec.c                          |  20 ----
 drivers/pwm/pwm-fsl-ftm.c                          |   2 -
 drivers/pwm/pwm-hibvt.c                            |   2 -
 drivers/pwm/pwm-imx.c                              |   8 --
 drivers/pwm/pwm-lpc18xx-sct.c                      |   2 -
 drivers/pwm/pwm-meson.c                            |   2 -
 drivers/pwm/pwm-omap-dmtimer.c                     |   2 -
 drivers/pwm/pwm-pxa.c                              |  19 ----
 drivers/pwm/pwm-renesas-tpu.c                      |   2 -
 drivers/pwm/pwm-rockchip.c                         |   5 -
 drivers/pwm/pwm-samsung.c                          |   3 -
 drivers/pwm/pwm-sun4i.c                            |   2 -
 drivers/pwm/pwm-tiecap.c                           |   2 -
 drivers/pwm/pwm-tiehrpwm.c                         |   2 -
 drivers/pwm/pwm-vt8500.c                           |   2 -
 drivers/pwm/pwm-zx.c                               |   2 -
 drivers/pwm/sysfs.c                                |  64 ++++++++++++
 drivers/video/backlight/lm3630a_bl.c               |   2 +-
 drivers/video/backlight/lp855x_bl.c                |   2 +-
 drivers/video/backlight/lp8788_bl.c                |   2 +-
 drivers/video/backlight/pwm_bl.c                   |   4 +-
 drivers/video/fbdev/ssd1307fb.c                    |   3 +-
 include/dt-bindings/pwm/pwm.h                      |   4 +
 include/linux/pwm.h                                |  90 +++++++++++++++--
 54 files changed, 402 insertions(+), 222 deletions(-)

-- 
2.7.4

^ permalink raw reply

* PM regression in next
From: Tony Lindgren @ 2018-01-12 14:14 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20180112135526.GC29734@lunn.ch>

* Andrew Lunn <andrew@lunn.ch> [180112 13:55]:
> > Thanks that fixes the suspend error. And I was able to confirm
> > that the suspend power consumption is OK.
> > 
> > That still leaves the mystery of the runtime idle power consumption
> > being much higher with commit e130bc1d00a4.
> 
> Did you re-measure the runtime power? Do you have an unused PHY? It
> could be it is not getting shut down. 1G PHYs can be quite power
> hungry.

Yes this is a different issue, the increase in runtime PM
consumption measurement I'm measuring is for a SoM board.
It contains the SoC + memory + PMIC and few devices. The
Ethernet controller is on a separate optional base board
and not related to the runtime PM issue.

Regards,

Tony

^ 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