Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 3/3] mm: fix double page fault on arm64 if PTE_AF is cleared
From: Jia He @ 2019-09-19 16:12 UTC (permalink / raw)
  To: Catalin Marinas, Will Deacon, Mark Rutland, James Morse,
	Marc Zyngier, Matthew Wilcox, Kirill A. Shutemov,
	linux-arm-kernel, linux-kernel, linux-mm, Suzuki Poulose
  Cc: Ralph Campbell, Jia He, Anshuman Khandual, Alex Van Brunt,
	Kaly Xin, Jérôme Glisse, Punit Agrawal, hejianet,
	Andrew Morton, Robin Murphy, Thomas Gleixner
In-Reply-To: <20190919161204.142796-1-justin.he@arm.com>

When we tested pmdk unit test [1] vmmalloc_fork TEST1 in arm64 guest, there
will be a double page fault in __copy_from_user_inatomic of cow_user_page.

Below call trace is from arm64 do_page_fault for debugging purpose
[  110.016195] Call trace:
[  110.016826]  do_page_fault+0x5a4/0x690
[  110.017812]  do_mem_abort+0x50/0xb0
[  110.018726]  el1_da+0x20/0xc4
[  110.019492]  __arch_copy_from_user+0x180/0x280
[  110.020646]  do_wp_page+0xb0/0x860
[  110.021517]  __handle_mm_fault+0x994/0x1338
[  110.022606]  handle_mm_fault+0xe8/0x180
[  110.023584]  do_page_fault+0x240/0x690
[  110.024535]  do_mem_abort+0x50/0xb0
[  110.025423]  el0_da+0x20/0x24

The pte info before __copy_from_user_inatomic is (PTE_AF is cleared):
[ffff9b007000] pgd=000000023d4f8003, pud=000000023da9b003, pmd=000000023d4b3003, pte=360000298607bd3

As told by Catalin: "On arm64 without hardware Access Flag, copying from
user will fail because the pte is old and cannot be marked young. So we
always end up with zeroed page after fork() + CoW for pfn mappings. we
don't always have a hardware-managed access flag on arm64."

This patch fix it by calling pte_mkyoung. Also, the parameter is
changed because vmf should be passed to cow_user_page()

Add a WARN_ON_ONCE when __copy_from_user_inatomic() returns error
in case there can be some obscure use-case.(by Kirill)

[1] https://github.com/pmem/pmdk/tree/master/src/test/vmmalloc_fork

Reported-by: Yibo Cai <Yibo.Cai@arm.com>
Signed-off-by: Jia He <justin.he@arm.com>
---
 mm/memory.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 54 insertions(+), 5 deletions(-)

diff --git a/mm/memory.c b/mm/memory.c
index e2bb51b6242e..cf681963b2f5 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -118,6 +118,13 @@ int randomize_va_space __read_mostly =
 					2;
 #endif
 
+#ifndef arch_faults_on_old_pte
+static inline bool arch_faults_on_old_pte(void)
+{
+	return false;
+}
+#endif
+
 static int __init disable_randmaps(char *s)
 {
 	randomize_va_space = 0;
@@ -2140,8 +2147,12 @@ static inline int pte_unmap_same(struct mm_struct *mm, pmd_t *pmd,
 	return same;
 }
 
-static inline void cow_user_page(struct page *dst, struct page *src, unsigned long va, struct vm_area_struct *vma)
+static inline int cow_user_page(struct page *dst, struct page *src,
+				struct vm_fault *vmf)
 {
+	struct vm_area_struct *vma = vmf->vma;
+	unsigned long addr = vmf->address;
+
 	debug_dma_assert_idle(src);
 
 	/*
@@ -2152,7 +2163,29 @@ static inline void cow_user_page(struct page *dst, struct page *src, unsigned lo
 	 */
 	if (unlikely(!src)) {
 		void *kaddr = kmap_atomic(dst);
-		void __user *uaddr = (void __user *)(va & PAGE_MASK);
+		void __user *uaddr = (void __user *)(addr & PAGE_MASK);
+		pte_t entry;
+
+		/* On architectures with software "accessed" bits, we would
+		 * take a double page fault, so mark it accessed here.
+		 */
+		if (arch_faults_on_old_pte() && !pte_young(vmf->orig_pte)) {
+			spin_lock(vmf->ptl);
+			if (likely(pte_same(*vmf->pte, vmf->orig_pte))) {
+				entry = pte_mkyoung(vmf->orig_pte);
+				if (ptep_set_access_flags(vma, addr,
+							  vmf->pte, entry, 0))
+					update_mmu_cache(vma, addr, vmf->pte);
+			} else {
+				/* Other thread has already handled the fault
+				 * and we don't need to do anything. If it's
+				 * not the case, the fault will be triggered
+				 * again on the same address.
+				 */
+				return -1;
+			}
+			spin_unlock(vmf->ptl);
+		}
 
 		/*
 		 * This really shouldn't fail, because the page is there
@@ -2160,12 +2193,17 @@ static inline void cow_user_page(struct page *dst, struct page *src, unsigned lo
 		 * in which case we just give up and fill the result with
 		 * zeroes.
 		 */
-		if (__copy_from_user_inatomic(kaddr, uaddr, PAGE_SIZE))
+		if (__copy_from_user_inatomic(kaddr, uaddr, PAGE_SIZE)) {
+			/* In case there can be some obscure use-case */
+			WARN_ON_ONCE(1);
 			clear_page(kaddr);
+		}
 		kunmap_atomic(kaddr);
 		flush_dcache_page(dst);
 	} else
-		copy_user_highpage(dst, src, va, vma);
+		copy_user_highpage(dst, src, addr, vma);
+
+	return 0;
 }
 
 static gfp_t __get_fault_gfp_mask(struct vm_area_struct *vma)
@@ -2318,7 +2356,16 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf)
 				vmf->address);
 		if (!new_page)
 			goto oom;
-		cow_user_page(new_page, old_page, vmf->address, vma);
+
+		if (cow_user_page(new_page, old_page, vmf)) {
+			/* COW failed, if the fault was solved by other,
+			 * it's fine. If not, userspace would re-fault on
+			 * the same address and we will handle the fault
+			 * from the second attempt.
+			 */
+			put_page(new_page);
+			goto normal;
+		}
 	}
 
 	if (mem_cgroup_try_charge_delay(new_page, mm, GFP_KERNEL, &memcg, false))
@@ -2420,6 +2467,8 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf)
 		}
 		put_page(old_page);
 	}
+
+normal:
 	return page_copied ? VM_FAULT_WRITE : 0;
 oom_free_new:
 	put_page(new_page);
-- 
2.17.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related

* mt76x2e hardware restart
From: Oleksandr Natalenko @ 2019-09-19 16:24 UTC (permalink / raw)
  To: linux-mediatek
  Cc: Ryder Lee, Stanislaw Gruszka, netdev, linux-wireless,
	linux-kernel, Matthias Brugger, linux-arm-kernel, Roy Luo,
	Lorenzo Bianconi, Lorenzo Bianconi, David S. Miller, Kalle Valo,
	Felix Fietkau

Hi.

Recently, I've got the following card:

01:00.0 Network controller: MEDIATEK Corp. Device 7612
         Subsystem: MEDIATEK Corp. Device 7612
         Flags: bus master, fast devsel, latency 0, IRQ 16
         Memory at 81200000 (64-bit, non-prefetchable) [size=1M]
         Expansion ROM at 81300000 [disabled] [size=64K]
         Capabilities: [40] Power Management version 3
         Capabilities: [50] MSI: Enable- Count=1/1 Maskable- 64bit+
         Capabilities: [70] Express Endpoint, MSI 00
         Capabilities: [100] Advanced Error Reporting
         Capabilities: [148] Device Serial Number 00-00-00-00-00-00-00-00
         Capabilities: [158] Latency Tolerance Reporting
         Capabilities: [160] L1 PM Substates
         Kernel driver in use: mt76x2e
         Kernel modules: mt76x2e

I try to use it as an access point with the following configuration:

interface=wlp1s0
driver=nl80211
ssid=someap
channel=36
noscan=1
hw_mode=a
ieee80211n=1
require_ht=1
ieee80211ac=1
require_vht=1
vht_oper_chwidth=1
vht_capab=[SHORT-GI-80][RX-STBC-1][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]
vht_oper_centr_freq_seg0_idx=42
auth_algs=1
wpa=2
wpa_passphrase=somepswd
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP
macaddr_acl=1
accept_mac_file=/etc/hostapd/hostapd.allow
ctrl_interface=/run/hostapd
ctrl_interface_group=0
country_code=CZ
ieee80211d=1
ieee80211h=1
wmm_enabled=1
ht_capab=[GF][HT40+][SHORT-GI-20][SHORT-GI-40][RX-STBC1][DSSS_CCK-40]

The hostapd daemon starts, and the AP broadcasts the beacons:

zář 19 17:50:04 srv hostapd[13251]: Configuration file: 
/etc/hostapd/ap_5ghz.conf
zář 19 17:50:05 srv hostapd[13251]: wlp1s0: interface state 
UNINITIALIZED->COUNTRY_UPDATE
zář 19 17:50:05 srv hostapd[13251]: Using interface wlp1s0 with hwaddr 
xx:xx:xx:xx:xx:xx and ssid "someap"
zář 19 17:50:05 srv hostapd[13251]: wlp1s0: interface state 
COUNTRY_UPDATE->ENABLED
zář 19 17:50:05 srv hostapd[13251]: wlp1s0: AP-ENABLED
zář 19 17:50:17 srv hostapd[13251]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: authenticated
zář 19 17:50:17 srv hostapd[13251]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: authenticated
zář 19 17:50:17 srv hostapd[13251]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: associated (aid 1)
zář 19 17:50:17 srv hostapd[13251]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: associated (aid 1)
zář 19 17:50:17 srv hostapd[13251]: wlp1s0: AP-STA-CONNECTED 
xx:xx:xx:xx:xx:xx
zář 19 17:50:17 srv hostapd[13251]: wlp1s0: STA xx:xx:xx:xx:xx:xx 
RADIUS: starting accounting session 07E311195378B570
zář 19 17:50:17 srv hostapd[13251]: wlp1s0: STA xx:xx:xx:xx:xx:xx WPA: 
pairwise key handshake completed (RSN)
zář 19 17:50:17 srv hostapd[13251]: wlp1s0: STA xx:xx:xx:xx:xx:xx 
RADIUS: starting accounting session 07E311195378B570
zář 19 17:50:17 srv hostapd[13251]: wlp1s0: STA xx:xx:xx:xx:xx:xx WPA: 
pairwise key handshake completed (RSN)

The client is able to see it and connect to it, but after a couple of 
seconds the following happens on the AP:

[  +9,979664] mt76x2e 0000:01:00.0: Firmware Version: 0.0.00
[  +0,000014] mt76x2e 0000:01:00.0: Build: 1
[  +0,000010] mt76x2e 0000:01:00.0: Build Time: 201507311614____
[  +0,018017] mt76x2e 0000:01:00.0: Firmware running!
[  +0,001101] ieee80211 phy4: Hardware restart was requested

and the AP dies. The client cannot reconnect to it, although hostapd 
logs show that it tries:

zář 19 17:51:15 srv hostapd[13504]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: authenticated
zář 19 17:51:15 srv hostapd[13504]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: authenticated
zář 19 17:51:19 srv hostapd[13504]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: authenticated
zář 19 17:51:19 srv hostapd[13504]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: authenticated
zář 19 17:52:54 srv hostapd[13504]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: authenticated
zář 19 17:52:54 srv hostapd[13504]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: authenticated
zář 19 17:52:59 srv hostapd[13504]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: authenticated
zář 19 17:52:59 srv hostapd[13504]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: authenticated
zář 19 17:56:14 srv hostapd[13504]: wlp1s0: STA xx:xx:xx:xx:xx:xx IEEE 
802.11: deauthenticated due to inactivity (timer DEAUTH/REMOVE)

AP stays completely unusable until I remove and modprobe mt76x2e module 
again. And then everything begins from scratch, and the AP dies within 
seconds.

I observe this on a fresh v5.3 kernel. I haven't tried anything older.

The only somewhat relevant thread I was able to found is [1], but it's 
not clear what's the resolution if any.

Could you please suggest how to deal with this issue?

Thanks.

[1] https://forum.openwrt.org/t/wifi-issues-with-18-06-4-on-mt76/40537

-- 
   Oleksandr Natalenko (post-factum)

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH] i2c: at91: Send bus clear command if SCL or SDA is down
From: Codrin.Ciubotariu @ 2019-09-19 16:25 UTC (permalink / raw)
  To: kamel.bouhara, linux-i2c, linux-arm-kernel, linux-kernel
  Cc: Ludovic.Desroches, alexandre.belloni, wsa
In-Reply-To: <1ed845e5-3835-f1aa-099a-b67c3bc16076@bootlin.com>

On 19.09.2019 18:06, kbouhara wrote:
> 
> On 9/11/19 11:58 AM, Codrin Ciubotariu wrote:
>> After a transfer timeout, some faulty I2C slave devices might hold down
>> the SCL or the SDA pins. We can generate a bus clear command, hoping that
>> the slave might release the pins.
>>
>> Signed-off-by: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
>> ---
>>   drivers/i2c/busses/i2c-at91-master.c | 20 ++++++++++++++++++++
>>   drivers/i2c/busses/i2c-at91.h        |  6 +++++-
>>   2 files changed, 25 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/i2c/busses/i2c-at91-master.c 
>> b/drivers/i2c/busses/i2c-at91-master.c
>> index a3fcc35ffd3b..5f544a16db96 100644
>> --- a/drivers/i2c/busses/i2c-at91-master.c
>> +++ b/drivers/i2c/busses/i2c-at91-master.c
>> @@ -599,6 +599,26 @@ static int at91_do_twi_transfer(struct 
>> at91_twi_dev *dev)
>>           at91_twi_write(dev, AT91_TWI_CR,
>>                      AT91_TWI_THRCLR | AT91_TWI_LOCKCLR);
>>       }
>> +
>> +    /*
>> +     * After timeout, some faulty I2C slave devices might hold 
>> SCL/SDA down;
>> +     * we can send a bus clear command, hoping that the pins will be
>> +     * released
>> +     */
>> +    if (!(dev->transfer_status & AT91_TWI_SDA) ||
>> +        !(dev->transfer_status & AT91_TWI_SCL)) {
>> +        dev_dbg(dev->dev,
>> +            "SDA/SCL are down; sending bus clear command\n");
>> +        if (dev->use_alt_cmd) {
>> +            unsigned int acr;
>> +
>> +            acr = at91_twi_read(dev, AT91_TWI_ACR);
>> +            acr &= ~AT91_TWI_ACR_DATAL_MASK;
>> +            at91_twi_write(dev, AT91_TWI_ACR, acr);
>> +        }
>> +        at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_CLEAR);
> 
> This bit is not documented on SoCs before SAMA5D2/D4, this write 
> shouldn't be done unconditionally.
> 
> 

Indeed, they are not present on SAMA5D4 or earlier SoCs. It is supported 
on SAMA5D2 though. I will make a new version and implement the CLEAR 
command only for the SoCs that support it.

Thank you for your review.

Best regards,
Codrin
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 2/3] pinctrl: meson-a1: add pinctrl driver for Meson A1 Soc
From: Jerome Brunet @ 2019-09-19 16:26 UTC (permalink / raw)
  To: Qianggui Song
  Cc: Mark Rutland, Hanjie Lin, Jianxin Pan, Neil Armstrong,
	Martin Blumenstingl, Kevin Hilman, Linus Walleij, linux-kernel,
	linux-gpio, Rob Herring, linux-arm-kernel, Carlo Caione,
	linux-amlogic, Xingyu Chen
In-Reply-To: <45b97927-c771-808a-b214-509af6c16931@amlogic.com>

On Wed 18 Sep 2019 at 14:36, Qianggui Song <qianggui.song@amlogic.com> wrote:

> On 2019/9/17 22:07, Jerome Brunet wrote:
>> 
>> On Tue 17 Sep 2019 at 13:51, Qianggui Song <qianggui.song@amlogic.com> wrote:
>>>>> diff --git a/drivers/pinctrl/meson/pinctrl-meson.c b/drivers/pinctrl/meson/pinctrl-meson.c
>>>>> index 8bba9d0..885b89d 100644
>>>>> --- a/drivers/pinctrl/meson/pinctrl-meson.c
>>>>> +++ b/drivers/pinctrl/meson/pinctrl-meson.c
>>>>> @@ -688,8 +688,12 @@ static int meson_pinctrl_parse_dt(struct meson_pinctrl *pc,
>>>>>  
>>>>>  	pc->reg_ds = meson_map_resource(pc, gpio_np, "ds");
>>>>>  	if (IS_ERR(pc->reg_ds)) {
>>>>> -		dev_dbg(pc->dev, "ds registers not found - skipping\n");
>>>>> -		pc->reg_ds = NULL;
>>>>> +		if (pc->data->reg_layout == A1_LAYOUT) {
>>>>> +			pc->reg_ds = pc->reg_pullen;
>>>>
>>>> IMO, this kind of ID based init fixup is not going to scale and will
>>>> lead to something difficult to maintain in the end.
>>>>
>>>> The way the different register sets interract with each other is already
>>>> pretty complex to follow.
>>>>
>>>> You could rework this in 2 different ways:
>>>> #1 - Have the generic function parse all the register sets and have all
>>>> drivers provide a specific (as in gxbb, gxl, axg, etc ...)  function to :
>>>>  - Verify the expected sets have been provided
>>>>  - Make assignement fixup as above if necessary
>>>>
>>>> #2 - Rework the driver to have only one single register region
>>>>  I think one of your colleague previously mentionned this was not
>>>>  possible. It is still unclear to me why ...
>>>>
>>> Appreciate your advice.  I have an idea based on #1, how about providing
>>> only two dt parse function, one is for chips before A1(the old one),
>>> another is for A1 and later chips that share the same layout. Assign
>>> these two functions to their own driver.
>> 
>> That's roughly the same thing as your initial proposition with function
>> pointer instead of IDs ... IMO, this would still be a quick fix to
>> address your immediate topic instead of dealing with the driver as
>> whole, which is my concern here.
>> 
> For #1. It would be like
> generic_parse_dt()
> {
> 	1. parse all register regions (mux gpio pull pull_en ds)
> 	
> 	2. call  specific function through function pointer in
>  	   meson_pinctrl_data.(each platform should have AO and EE two
>            specific functions for they are not the same)
> 	{
> 		do work you mentioned above
> 	}
> }
> right ?
> If that so, maybe there are a lot of duplicated codes

Only if you make it so. Providing a callback and duplicating code are
not the same thing

> for most Socs share the same reg layout.

That's not really accurate:

So far they all have the "mux" and "gpio" region but

gxbb, gxl, axg, meson8 EE:
 has: pull, pull-en
 remap: non
 unsupported: ds

gxbb, gxl, axg, meson8 AO:
 has: pull
 remap: pull-en -> pull
 unsupported: ds

g12 and sm1 EE:
 has: pull, pull-en, ds
 remap: none

g12 and sm1 AO:
 has: ds
 remap: pull->gpio, pull_en->gpio

And now a1 chip remaps "ds" to "pull_en" ...

As said previouly all this is getting pretty difficult to follow and
maintain. Adding a proper callback for each meson pinctrl would make the
above explicit in the code ... which helps maintain thing, at least for
a while ...

Judging by the offsets between those regions, I still think one single
region would make things a whole lot simpler. If it is not possible to
map it with one single region, could you tell us why ? What non-pinctrl
related device do we have there ?

> So I guess five specific functions are
> enough: AXG and before(ao,ee), G12A(ao,ee) and A1(will place them in
> pinctrl_meson.c). Since m8 to AXG are the same register layout for both
> ee and ao, G12A with new feature ds and new ao register layout.
>
> Or I misunderstood the #1 ?
>>>>> +		} else {
>>>>> +			dev_dbg(pc->dev, "ds registers not found - skipping\n");
>>>>> +			pc->reg_ds = NULL;
>>>>> +		}
>>>>>  	}
>>>>>  
>>>>>  	return 0;
>>>>> diff --git a/drivers/pinctrl/meson/pinctrl-meson.h b/drivers/pinctrl/meson/pinctrl-meson.h
>>>>> index c696f32..3d0c58d 100644
>>>>> --- a/drivers/pinctrl/meson/pinctrl-meson.h
>>>>> +++ b/drivers/pinctrl/meson/pinctrl-meson.h
>>>>> @@ -80,6 +80,14 @@ enum meson_pinconf_drv {
>>>>>  };
>>>>>  
>>>>>  /**
>>>>> + * enum meson_reg_layout - identify two types of reg layout
>>>>> + */
>>>>> +enum meson_reg_layout {
>>>>> +	LEGACY_LAYOUT,
>>>>> +	A1_LAYOUT,
>>>>> +};
>>>>> +
>>>>> +/**
>>>>>   * struct meson bank
>>>>>   *
>>>>>   * @name:	bank name
>>>>> @@ -114,6 +122,7 @@ struct meson_pinctrl_data {
>>>>>  	unsigned int num_banks;
>>>>>  	const struct pinmux_ops *pmx_ops;
>>>>>  	void *pmx_data;
>>>>> +	unsigned int reg_layout;
>>>>>  };
>>>>>  
>>>>>  struct meson_pinctrl {
>>>>
>>>> .
>>>>
>> 
>> .
>> 

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH v5 1/3] arm64: cpufeature: introduce helper cpu_has_hw_af()
From: Catalin Marinas @ 2019-09-19 16:36 UTC (permalink / raw)
  To: Jia He
  Cc: Mark Rutland, Kaly Xin, Ralph Campbell, Andrew Morton,
	Suzuki Poulose, Marc Zyngier, Anshuman Khandual, linux-kernel,
	Matthew Wilcox, linux-mm, Jérôme Glisse, James Morse,
	linux-arm-kernel, Punit Agrawal, hejianet, Thomas Gleixner,
	Will Deacon, Alex Van Brunt, Kirill A. Shutemov, Robin Murphy
In-Reply-To: <20190919161204.142796-2-justin.he@arm.com>

On Fri, Sep 20, 2019 at 12:12:02AM +0800, Jia He wrote:
> diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
> index b1fdc486aed8..fb0e9425d286 100644
> --- a/arch/arm64/kernel/cpufeature.c
> +++ b/arch/arm64/kernel/cpufeature.c
> @@ -1141,6 +1141,16 @@ static bool has_hw_dbm(const struct arm64_cpu_capabilities *cap,
>  	return true;
>  }
>  
> +/* Decouple AF from AFDBM. */
> +bool cpu_has_hw_af(void)
> +{
> +	return (read_cpuid(ID_AA64MMFR1_EL1) & 0xf);
> +}
> +#else /* CONFIG_ARM64_HW_AFDBM */
> +bool cpu_has_hw_af(void)
> +{
> +	return false;
> +}
>  #endif

Please place this function in cpufeature.h directly, no need for an
additional function call. Something like:

static inline bool cpu_has_hw_af(void)
{
	if (IS_ENABLED(CONFIG_ARM64_HW_AFDBM))
		return read_cpuid(ID_AA64MMFR1_EL1) & 0xf;
	return false;
}

-- 
Catalin

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH v5 3/3] mm: fix double page fault on arm64 if PTE_AF is cleared
From: Catalin Marinas @ 2019-09-19 16:42 UTC (permalink / raw)
  To: Jia He
  Cc: Mark Rutland, Kaly Xin, Ralph Campbell, Andrew Morton,
	Suzuki Poulose, Marc Zyngier, Anshuman Khandual, linux-kernel,
	Matthew Wilcox, linux-mm, Jérôme Glisse, James Morse,
	linux-arm-kernel, Punit Agrawal, hejianet, Thomas Gleixner,
	Will Deacon, Alex Van Brunt, Kirill A. Shutemov, Robin Murphy
In-Reply-To: <20190919161204.142796-4-justin.he@arm.com>

On Fri, Sep 20, 2019 at 12:12:04AM +0800, Jia He wrote:
> @@ -2152,7 +2163,29 @@ static inline void cow_user_page(struct page *dst, struct page *src, unsigned lo
>  	 */
>  	if (unlikely(!src)) {
>  		void *kaddr = kmap_atomic(dst);
> -		void __user *uaddr = (void __user *)(va & PAGE_MASK);
> +		void __user *uaddr = (void __user *)(addr & PAGE_MASK);
> +		pte_t entry;
> +
> +		/* On architectures with software "accessed" bits, we would
> +		 * take a double page fault, so mark it accessed here.
> +		 */
> +		if (arch_faults_on_old_pte() && !pte_young(vmf->orig_pte)) {
> +			spin_lock(vmf->ptl);
> +			if (likely(pte_same(*vmf->pte, vmf->orig_pte))) {
> +				entry = pte_mkyoung(vmf->orig_pte);
> +				if (ptep_set_access_flags(vma, addr,
> +							  vmf->pte, entry, 0))
> +					update_mmu_cache(vma, addr, vmf->pte);
> +			} else {
> +				/* Other thread has already handled the fault
> +				 * and we don't need to do anything. If it's
> +				 * not the case, the fault will be triggered
> +				 * again on the same address.
> +				 */
> +				return -1;
> +			}
> +			spin_unlock(vmf->ptl);

Returning with the spinlock held doesn't normally go very well ;).

-- 
Catalin

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH v2 2/2] crypto: sun4i-ss: enable pm_runtime
From: Maxime Ripard @ 2019-09-19 16:55 UTC (permalink / raw)
  To: Corentin Labbe
  Cc: herbert, linux-sunxi, linux-kernel, wens, linux-crypto, davem,
	linux-arm-kernel
In-Reply-To: <20190919051035.4111-3-clabbe.montjoie@gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 4286 bytes --]

Hi,

On Thu, Sep 19, 2019 at 07:10:35AM +0200, Corentin Labbe wrote:
> This patch enables power management on the Security System.
>
> Signed-off-by: Corentin Labbe <clabbe.montjoie@gmail.com>
> ---
>  drivers/crypto/sunxi-ss/sun4i-ss-cipher.c |  9 +++
>  drivers/crypto/sunxi-ss/sun4i-ss-core.c   | 94 +++++++++++++++++++----
>  drivers/crypto/sunxi-ss/sun4i-ss-hash.c   | 12 +++
>  drivers/crypto/sunxi-ss/sun4i-ss-prng.c   |  9 ++-
>  drivers/crypto/sunxi-ss/sun4i-ss.h        |  2 +
>  5 files changed, 110 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/crypto/sunxi-ss/sun4i-ss-cipher.c b/drivers/crypto/sunxi-ss/sun4i-ss-cipher.c
> index fa4b1b47822e..c9799cbe0530 100644
> --- a/drivers/crypto/sunxi-ss/sun4i-ss-cipher.c
> +++ b/drivers/crypto/sunxi-ss/sun4i-ss-cipher.c
> @@ -480,6 +480,7 @@ int sun4i_ss_cipher_init(struct crypto_tfm *tfm)
>  	struct sun4i_tfm_ctx *op = crypto_tfm_ctx(tfm);
>  	struct sun4i_ss_alg_template *algt;
>  	const char *name = crypto_tfm_alg_name(tfm);
> +	int err;
>
>  	memset(op, 0, sizeof(struct sun4i_tfm_ctx));
>
> @@ -497,13 +498,21 @@ int sun4i_ss_cipher_init(struct crypto_tfm *tfm)
>  		return PTR_ERR(op->fallback_tfm);
>  	}
>
> +	err = pm_runtime_get_sync(op->ss->dev);
> +	if (err < 0)
> +		goto error_pm;
>  	return 0;

Newline here

> +error_pm:
> +	crypto_free_sync_skcipher(op->fallback_tfm);
> +	return err;
>  }
>
>  void sun4i_ss_cipher_exit(struct crypto_tfm *tfm)
>  {
>  	struct sun4i_tfm_ctx *op = crypto_tfm_ctx(tfm);
> +
>  	crypto_free_sync_skcipher(op->fallback_tfm);
> +	pm_runtime_put(op->ss->dev);
>  }
>
>  /* check and set the AES key, prepare the mode to be used */
> diff --git a/drivers/crypto/sunxi-ss/sun4i-ss-core.c b/drivers/crypto/sunxi-ss/sun4i-ss-core.c
> index 6c2db5d83b06..311c2653a9c3 100644
> --- a/drivers/crypto/sunxi-ss/sun4i-ss-core.c
> +++ b/drivers/crypto/sunxi-ss/sun4i-ss-core.c
> @@ -44,7 +44,8 @@ static struct sun4i_ss_alg_template ss_algs[] = {
>  				.cra_blocksize = MD5_HMAC_BLOCK_SIZE,
>  				.cra_ctxsize = sizeof(struct sun4i_req_ctx),
>  				.cra_module = THIS_MODULE,
> -				.cra_init = sun4i_hash_crainit
> +				.cra_init = sun4i_hash_crainit,
> +				.cra_exit = sun4i_hash_craexit

You should add a comma at the end to prevent having to modify it again

>  			}
>  		}
>  	}
> @@ -70,7 +71,8 @@ static struct sun4i_ss_alg_template ss_algs[] = {
>  				.cra_blocksize = SHA1_BLOCK_SIZE,
>  				.cra_ctxsize = sizeof(struct sun4i_req_ctx),
>  				.cra_module = THIS_MODULE,
> -				.cra_init = sun4i_hash_crainit
> +				.cra_init = sun4i_hash_crainit,
> +				.cra_exit = sun4i_hash_craexit

Ditto

>  			}
>  		}
>  	}
> @@ -262,6 +264,61 @@ static int sun4i_ss_enable(struct sun4i_ss_ctx *ss)
>  	return err;
>  }
>
> +/*
> + * Power management strategy: The device is suspended unless a TFM exists for
> + * one of the algorithms proposed by this driver.
> + */
> +#if defined(CONFIG_PM)
> +static int sun4i_ss_pm_suspend(struct device *dev)
> +{
> +	struct sun4i_ss_ctx *ss = dev_get_drvdata(dev);
> +
> +	sun4i_ss_disable(ss);
> +	return 0;
> +}
> +
> +static int sun4i_ss_pm_resume(struct device *dev)
> +{
> +	struct sun4i_ss_ctx *ss = dev_get_drvdata(dev);
> +
> +	return sun4i_ss_enable(ss);
> +}
> +#endif
> +

Why not just have the suspend and resume function and the enable /
disable functions merged together, you're not using them directy as
far as I can see.

> +const struct dev_pm_ops sun4i_ss_pm_ops = {
> +	SET_RUNTIME_PM_OPS(sun4i_ss_pm_suspend, sun4i_ss_pm_resume, NULL)
> +};
> +
> +/*
> + * When power management is enabled, this function enables the PM and set the
> + * device as suspended
> + * When power management is disabled, this function just enables the device
> + */
> +static int sun4i_ss_pm_init(struct sun4i_ss_ctx *ss)
> +{
> +	int err;
> +
> +	pm_runtime_use_autosuspend(ss->dev);
> +	pm_runtime_set_autosuspend_delay(ss->dev, 2000);
> +
> +	err = pm_runtime_set_suspended(ss->dev);
> +	if (err)
> +		return err;
> +	pm_runtime_enable(ss->dev);
> +#if !defined(CONFIG_PM)
> +	err = sun4i_ss_enable(ss);
> +#endif
> +	return err;
> +}

This looks nicer:
https://elixir.bootlin.com/linux/latest/source/drivers/spi/spi-sun4i.c#L492

Or, just make it depend on CONFIG_PM, we should probably do it anyway
at the ARCH level anyway.

Maxime

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 3/3] clk: meson: clk-pll: always enable a critical PLL when setting the rate
From: Stephen Boyd @ 2019-09-19 17:06 UTC (permalink / raw)
  To: Jerome Brunet, Neil Armstrong
  Cc: linux-amlogic, linux-kernel, linux-clk, linux-arm-kernel,
	Neil Armstrong
In-Reply-To: <1j1rwce8yf.fsf@starbuckisacylon.baylibre.com>

Quoting Jerome Brunet (2019-09-19 06:01:28)
> On Thu 19 Sep 2019 at 11:38, Neil Armstrong <narmstrong@baylibre.com> wrote:
> 
> > Make sure we always enable a PLL on a set_rate() when the PLL is
> > flagged as critical.
> >
> > This fixes the case when the Amlogic G12A SYS_PLL gets disabled by the
> > PSCI firmware when resuming from suspend-to-memory, in the case
> > where the CPU was not clocked by the SYS_PLL, but by the fixed PLL
> > fixed divisors.
> > In this particular case, when changing the PLL rate, CCF doesn't handle
> > the fact the PLL could have been disabled in the meantime and set_rate()
> > only changes the rate and never enables it again.
> >
> > Fixes: d6e81845b7d9 ("clk: meson: clk-pll: check if the clock is already enabled')
> > Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
> > ---
> >  drivers/clk/meson/clk-pll.c | 2 +-
> >  1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c
> > index ddb1e5634739..8c5adccb7959 100644
> > --- a/drivers/clk/meson/clk-pll.c
> > +++ b/drivers/clk/meson/clk-pll.c
> > @@ -379,7 +379,7 @@ static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> >       }
> >  
> >       /* If the pll is stopped, bail out now */
> > -     if (!enabled)
> > +     if (!(hw->init->flags & CLK_IS_CRITICAL) && !enabled)
> 
> This is surely a work around to the issue at hand but:
> 
> * Enabling the clock, critical or not, should not be done but the
> set_rate() callback. This is not the purpose of this callback.
> 
> * Enabling the clock in such way does not walk the tree. So, if there is
> ever another PSCI Fw which disable we would get into the same issue
> again. IOW, This is not specific to the PLL driver so it should not have
> to deal with this.

Exactly.

> 
> Since this clock can change out of CCF maybe it should be marked with
> CLK_GET_RATE_NOCACHE ?

Yes, or figure out a way to make the clk state match what PSCI leaves it
in on resume from suspend.


> 
> When CCF hits a clock with CLK_GET_RATE_NOCACHE while walking the tree,
> in addition to to calling get_rate(), CCF could also call is_enabled()
> if the clock has CLK_IS_CRITICAL and possibly .enable() ?

This logic should go under a new flag. The CLK_GET_RATE_NOCACHE flag
specifically means get rate shouldn't be a cached operation. It doesn't
relate to the enable state. I hope that you can implement some sort of
resume hook that synchronizes the state though so that you don't need to
rely on clk_set_rate() or clk_get_rate() to trigger a sync.


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH] spi: atmel: Fix crash when using more than 4 gpio CS
From: Alexandre Belloni @ 2019-09-19 17:23 UTC (permalink / raw)
  To: Mark Brown
  Cc: Gregory CLEMENT, stable, linux-kernel, Ludovic Desroches,
	Thomas Petazzoni, linux-spi, linux-arm-kernel
In-Reply-To: <20190919160315.GQ3642@sirena.co.uk>

On 19/09/2019 17:03:15+0100, Mark Brown wrote:
> On Thu, Sep 19, 2019 at 05:38:47PM +0200, Gregory CLEMENT wrote:
> 
> > With this patch, when using a gpio CS, the hardware CS0 is always
> > used. Thanks to this, there is no more limitation for the number of
> > gpio CS we can use.
> 
> This is going to break any system where we use both a GPIO chip select
> and chip select 0.  Ideally we'd try to figure out an unused chip select
> to use here...

The point is that this use case is already broken and this patch fixes
the crash and is easily backportable.

Fixing the CS + gpio CS should probably be done in a separate patch.

-- 
Alexandre Belloni, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [REGRESSION] sdhci no longer detects SD cards on LX2160A
From: Russell King - ARM Linux admin @ 2019-09-19 17:23 UTC (permalink / raw)
  To: Robin Murphy
  Cc: dann frazier, Will Deacon, Adrian Hunter, Nicolin Chen, Y.b. Lu,
	linux-mmc, Christoph Hellwig, Linux ARM
In-Reply-To: <20fe58a0-f0ed-733b-87fb-47d667094491@arm.com>

On Thu, Sep 19, 2019 at 03:02:39PM +0100, Robin Murphy wrote:
> On 19/09/2019 10:16, Russell King - ARM Linux admin wrote:
> > On Tue, Sep 17, 2019 at 03:03:29PM +0100, Robin Murphy wrote:
> > > On 17/09/2019 14:49, Russell King - ARM Linux admin wrote:
> > > > As already replied, v4 mode is not documented as being available on
> > > > the LX2160A - the bit in the control register is marked as "reserved".
> > > > This is as expected as it is documented that it is using a v3.00 of
> > > > the SDHCI standard, rather than v4.00.
> > > > 
> > > > So, sorry, enabling "v4 mode" isn't a workaround in this scenario.
> > > > 
> > > > Given that v4 mode is not mandatory, this shouldn't be a work-around.
> > > > 
> > > > Given that it _does_ work some of the time with the table >4GB, then
> > > > this is not an addressing limitation.
> > > 
> > > Yes, that's what "something totally different" usually means.
> > > 
> > > > > However, the other difference between getting a single page directly from
> > > > > the page allocator vs. the CMA area is that accesses to the linear mapping
> > > > > of the CMA area are probably pretty rare, whereas for the single-page case
> > > > > it's much more likely that kernel tasks using adjacent pages could lead to
> > > > > prefetching of the descriptor page's cacheable alias. That could certainly
> > > > > explain how reverting that commit manages to hide an apparent coherency
> > > > > issue.
> > > > 
> > > > Right, so how do we fix this?
> > > 
> > > By describing the hardware correctly in the DT.
> > 
> > It would appear that it _is_ correctly described given the default
> > hardware configuration, but the driver sets a bit in a control
> > register that enables cache snooping.
> 
> Oh, fun. FWIW, the more general form of that statement would be "by ensuring
> that the device behaviour and the DT description are consistent", it's just
> rare to have both degrees of freedom.
> 
> Even in these cases, though, it tends to be ultimately necessary to defer to
> what the DT says, because there can be situations where the IP believes
> itself capable of enabling snoops, but the integration failed to wire things
> up correctly for them to actually work. I know we have to deal with that in
> arm-smmu, for one example.
> 
> > Adding "dma-coherent" to the DT description does not seem to be the
> > correct solution, as we are reliant on the DT description and driver
> > implementation both agreeing, which is fragile.
> > 
> >  From what I can see, there isn't a way for a driver to say "I've made
> > this device is coherent now" and I suspect making the driver set the
> > DMA snoop bit depending on whether "dma-coherent" is present in DT or
> > not will cause data-corrupting regressions for other people.
> > 
> > So, we're back to where we started - what is the right solution to
> > this problem?
> > 
> > The only thing I can think is that the driver needs to do something
> > like:
> > 
> > 	WARN_ON(!dev_is_dma_coherent(dev));
> > 
> > in esdhc_of_enable_dma() as a first step, and ensuring that the snoop
> > bit matches the state of dev_is_dma_coherent(dev)?  Is it permitted to
> > use dev_is_dma_coherent() in drivers - it doesn't seem to be part of
> > the normal DMA API?
> 
> The safest option would be to query the firmware property layer via
> device_get_dma_attr() - or potentially short-cut to of_dma_is_coherent() for
> a pure DT driver. Even disregarding API purity, I don't think the DMA API
> internals are really generic enough yet to reliably poke at (although FWIW,
> *certain* cases like dma_direct_ops would now actually work as expected if
> one did the unspeakable and flipped dev->dma_coherent from a driver, but
> that would definitely not win any friends).

So, I prepared a few options, and option 2 was:

 drivers/mmc/host/sdhci-of-esdhc.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c
index 4dd43b1adf2c..8076a1322499 100644
--- a/drivers/mmc/host/sdhci-of-esdhc.c
+++ b/drivers/mmc/host/sdhci-of-esdhc.c
@@ -19,6 +19,7 @@
 #include <linux/clk.h>
 #include <linux/ktime.h>
 #include <linux/dma-mapping.h>
+#include <linux/dma-noncoherent.h>
 #include <linux/mmc/host.h>
 #include <linux/mmc/mmc.h>
 #include "sdhci-pltfm.h"
@@ -495,7 +496,12 @@ static int esdhc_of_enable_dma(struct sdhci_host *host)
 		dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40));
 
 	value = sdhci_readl(host, ESDHC_DMA_SYSCTL);
-	value |= ESDHC_DMA_SNOOP;
+
+	if (dev_is_dma_coherent(dev))
+		value |= ESDHC_DMA_SNOOP;
+	else
+		value &= ~ESDHC_DMA_SNOOP;
+
 	sdhci_writel(host, value, ESDHC_DMA_SYSCTL);
 	return 0;
 }

The dev_is_dma_coherent() could be changed to something like
device_get_dma_attr() if that's the correct thing to base this
off of.  However, if it returns DEV_DMA_NOT_SUPPORTED, then what?
Assume non-coherent or assume coherent?  What will the DMA API
layer assume?

It seems to me that we want the DMA API layer and the driver to
both agree whether the device is to be coherent or not, and for
the sake of data integrity, we do not want any possibility for
them to deviate in that decision making process.

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTC broadband for 0.8mile line in suburbia: sync at 12.1Mbps down 622kbps up
According to speedtest.net: 11.9Mbps down 500kbps up

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related

* Re: [PATCH] spi: atmel: Remove AVR32 leftover
From: Alexandre Belloni @ 2019-09-19 17:24 UTC (permalink / raw)
  To: Gregory CLEMENT
  Cc: linux-kernel, Ludovic Desroches, Mark Brown, Thomas Petazzoni,
	linux-spi, linux-arm-kernel
In-Reply-To: <20190919154034.7489-1-gregory.clement@bootlin.com>

On 19/09/2019 17:40:34+0200, Gregory CLEMENT wrote:
> AV32 support has been from the kernel a few release ago, but there was
AVR32 and  missing word^

> still some specific macro for this architecture in this driver. Lets
> remove it.
> 
> Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
> ---
>  drivers/spi/spi-atmel.c | 24 ------------------------
>  1 file changed, 24 deletions(-)
> 
> diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c
> index bb94f5927819..de1e1861a70c 100644
> --- a/drivers/spi/spi-atmel.c
> +++ b/drivers/spi/spi-atmel.c
> @@ -222,37 +222,13 @@
>  	  | SPI_BF(name, value))
>  
>  /* Register access macros */
> -#ifdef CONFIG_AVR32
> -#define spi_readl(port, reg) \
> -	__raw_readl((port)->regs + SPI_##reg)
> -#define spi_writel(port, reg, value) \
> -	__raw_writel((value), (port)->regs + SPI_##reg)
> -
> -#define spi_readw(port, reg) \
> -	__raw_readw((port)->regs + SPI_##reg)
> -#define spi_writew(port, reg, value) \
> -	__raw_writew((value), (port)->regs + SPI_##reg)
> -
> -#define spi_readb(port, reg) \
> -	__raw_readb((port)->regs + SPI_##reg)
> -#define spi_writeb(port, reg, value) \
> -	__raw_writeb((value), (port)->regs + SPI_##reg)
> -#else
>  #define spi_readl(port, reg) \
>  	readl_relaxed((port)->regs + SPI_##reg)
>  #define spi_writel(port, reg, value) \
>  	writel_relaxed((value), (port)->regs + SPI_##reg)
> -
> -#define spi_readw(port, reg) \
> -	readw_relaxed((port)->regs + SPI_##reg)
>  #define spi_writew(port, reg, value) \
>  	writew_relaxed((value), (port)->regs + SPI_##reg)
>  
> -#define spi_readb(port, reg) \
> -	readb_relaxed((port)->regs + SPI_##reg)
> -#define spi_writeb(port, reg, value) \
> -	writeb_relaxed((value), (port)->regs + SPI_##reg)
> -#endif
>  /* use PIO for small transfers, avoiding DMA setup/teardown overhead and
>   * cache operations; better heuristics consider wordsize and bitrate.
>   */
> -- 
> 2.23.0
> 

-- 
Alexandre Belloni, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 19/23] mtd: spi-nor: Rework spansion(_no)_read_cr_quad_enable()
From: Vignesh Raghavendra @ 2019-09-19 17:34 UTC (permalink / raw)
  To: Tudor.Ambarus, boris.brezillon, marek.vasut, miquel.raynal,
	richard, linux-mtd
  Cc: linux-aspeed, andrew, linux-kernel, vz, linux-mediatek, joel,
	matthias.bgg, computersforpeace, dwmw2, linux-arm-kernel
In-Reply-To: <20190917155426.7432-20-tudor.ambarus@microchip.com>



On 17-Sep-19 9:25 PM, Tudor.Ambarus@microchip.com wrote:
> From: Tudor Ambarus <tudor.ambarus@microchip.com>
> 
> Merge:
> spansion_no_read_cr_quad_enable()
> spansion_read_cr_quad_enable()
> 
> in spi_nor_sr2_bit1_quad_enable().
> 
> Avoid duplication of code by using spi_nor_write_16bit_sr_and_check(),
> the SNOR_F_NO_READ_CR case is treated there.
> 
> We now do the Read Back test even for the old
> spansion_no_read_cr_quad_enable() case.
> 
> Signed-off-by: Tudor Ambarus <tudor.ambarus@microchip.com>
> ---
>  drivers/mtd/spi-nor/spi-nor.c | 89 ++++++++++---------------------------------
>  include/linux/mtd/spi-nor.h   |  4 +-
>  2 files changed, 22 insertions(+), 71 deletions(-)
> 
> diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
> index 2f79923e7db5..8648666fb9bd 100644
> --- a/drivers/mtd/spi-nor/spi-nor.c
> +++ b/drivers/mtd/spi-nor/spi-nor.c
> @@ -907,7 +907,7 @@ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 status_new,
>  		 * Write Status (01h) command is available just for the cases
>  		 * in which the QE bit is described in SR2 at BIT(1).
>  		 */
> -		sr_cr[1] = CR_QUAD_EN_SPAN;
> +		sr_cr[1] = SR2_QUAD_EN_BIT1;
>  	} else {
>  		sr_cr[1] = 0;
>  	}
> @@ -1963,81 +1963,34 @@ static int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor)
>  }
>  
>  /**
> - * spansion_no_read_cr_quad_enable() - set QE bit in Configuration Register.
> + * spi_nor_sr2_bit1_quad_enable() - set the Quad Enable BIT(1) in the Status
> + * Register 2.
>   * @nor:	pointer to a 'struct spi_nor'
>   *
> - * Set the Quad Enable (QE) bit in the Configuration Register.
> - * This function should be used with QSPI memories not supporting the Read
> - * Configuration Register (35h) instruction.
> - *
> - * bit 1 of the Configuration Register is the QE bit for Spansion like QSPI
> - * memories.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -static int spansion_no_read_cr_quad_enable(struct spi_nor *nor)
> -{
> -	u8 *sr_cr = nor->bouncebuf;
> -	int ret;
> -
> -	/* Keep the current value of the Status Register. */
> -	ret = spi_nor_read_sr(nor, &sr_cr[0]);
> -	if (ret)
> -		return ret;
> -
> -	sr_cr[1] = CR_QUAD_EN_SPAN;
> -
> -	return spi_nor_write_sr(nor, sr_cr, 2);
> -}
> -
> -/**
> - * spansion_read_cr_quad_enable() - set QE bit in Configuration Register.
> - * @nor:	pointer to a 'struct spi_nor'
> - *
> - * Set the Quad Enable (QE) bit in the Configuration Register.
> - * This function should be used with QSPI memories supporting the Read
> - * Configuration Register (35h) instruction.
> - *
> - * bit 1 of the Configuration Register is the QE bit for Spansion like QSPI
> - * memories.
> + * Bit 1 of the Status Register 2 is the QE bit for Spansion like QSPI memories.
>   *
>   * Return: 0 on success, -errno otherwise.
>   */
> -static int spansion_read_cr_quad_enable(struct spi_nor *nor)
> +static int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor)
>  {
> -	u8 *sr_cr = nor->bouncebuf;
>  	int ret;
>  
> -	/* Check current Quad Enable bit value. */
> -	ret = spi_nor_read_cr(nor, &sr_cr[1]);
> -	if (ret)
> -		return ret;
> -
> -	if (sr_cr[1] & CR_QUAD_EN_SPAN)
> -		return 0;
> +	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
> +		/* Check current Quad Enable bit value. */
> +		ret = spi_nor_read_cr(nor, &nor->bouncebuf[0]);
> +		if (ret)
> +			return ret;
>  
> -	sr_cr[1] |= CR_QUAD_EN_SPAN;
> +		if (nor->bouncebuf[0] & SR2_QUAD_EN_BIT1)
> +			return 0;
> +	}
>  
>  	/* Keep the current value of the Status Register. */
> -	ret = spi_nor_read_sr(nor, &sr_cr[0]);
> -	if (ret)
> -		return ret;
> -
> -	ret = spi_nor_write_sr(nor, sr_cr, 2);
> -	if (ret)
> -		return ret;
> -
> -	/* Read back and check it. */
> -	ret = spi_nor_read_cr(nor, &sr_cr[1]);
> +	ret = spi_nor_read_sr(nor, &nor->bouncebuf[0]);
>  	if (ret)
>  		return ret;
>  
> -	if (!(sr_cr[1] & CR_QUAD_EN_SPAN)) {
> -		dev_err(nor->dev, "Spansion Quad bit not set\n");
> -		return -EIO;
> -	}
> -
> -	return 0;

You need to set QE bit here before writing to CR register. This function
does not do that.

> +	return spi_nor_write_16bit_sr_and_check(nor, nor->bouncebuf[0], 0xFF);

Neither does spi_nor_write_16bit_sr_and_check().
We need a function that allows to modify SR2/CR register content as well
so as to set QE bit right?

Regards
Vignesh

>  }
>  
>  /**
> @@ -2117,7 +2070,7 @@ static int spi_nor_clear_sr_bp(struct spi_nor *nor)
>   *
>   * Read-modify-write function that clears the Block Protection bits from the
>   * Status Register without affecting other bits. The function is tightly
> - * coupled with the spansion_read_cr_quad_enable() function. Both assume that
> + * coupled with the spi_nor_sr2_bit1_quad_enable() function. Both assume that
>   * the Write Register with 16 bits, together with the Read Configuration
>   * Register (35h) instructions are supported.
>   *
> @@ -2138,7 +2091,7 @@ static int spi_nor_spansion_clear_sr_bp(struct spi_nor *nor)
>  	 * When the configuration register Quad Enable bit is one, only the
>  	 * Write Status (01h) command with two data bytes may be used.
>  	 */
> -	if (sr_cr[1] & CR_QUAD_EN_SPAN) {
> +	if (sr_cr[1] & SR2_QUAD_EN_BIT1) {
>  		ret = spi_nor_read_sr(nor, &sr_cr[0]);
>  		if (ret)
>  			return ret;
> @@ -3642,7 +3595,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
>  		 * supported.
>  		 */
>  		nor->flags |= SNOR_F_NO_READ_CR;
> -		flash->quad_enable = spansion_no_read_cr_quad_enable;
> +		flash->quad_enable = spi_nor_sr2_bit1_quad_enable;
>  		break;
>  
>  	case BFPT_DWORD15_QER_SR1_BIT6:
> @@ -3663,7 +3616,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
>  		 * assumption of a 16-bit Write Status (01h) command.
>  		 */
>  		nor->flags |= SNOR_F_HAS_16BIT_SR;
> -		flash->quad_enable = spansion_read_cr_quad_enable;
> +		flash->quad_enable = spi_nor_sr2_bit1_quad_enable;
>  		break;
>  
>  	default:
> @@ -4626,7 +4579,7 @@ static void spi_nor_info_init_flash_params(struct spi_nor *nor)
>  	u8 i, erase_mask;
>  
>  	/* Initialize legacy flash parameters and settings. */
> -	flash->quad_enable = spansion_read_cr_quad_enable;
> +	flash->quad_enable = spi_nor_sr2_bit1_quad_enable;
>  	flash->set_4byte = spansion_set_4byte;
>  	flash->setup = spi_nor_default_setup;
>  	/* Default to 16-bit Write Status (01h) Command */
> @@ -4844,7 +4797,7 @@ static int spi_nor_init(struct spi_nor *nor)
>  	int err;
>  
>  	if (nor->clear_sr_bp) {
> -		if (nor->flash.quad_enable == spansion_read_cr_quad_enable)
> +		if (nor->flash.quad_enable == spi_nor_sr2_bit1_quad_enable)
>  			nor->clear_sr_bp = spi_nor_spansion_clear_sr_bp;
>  
>  		err = nor->clear_sr_bp(nor);
> diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
> index 3a835de90b6a..5590a36eb43e 100644
> --- a/include/linux/mtd/spi-nor.h
> +++ b/include/linux/mtd/spi-nor.h
> @@ -144,10 +144,8 @@
>  #define FSR_P_ERR		BIT(4)	/* Program operation status */
>  #define FSR_PT_ERR		BIT(1)	/* Protection error bit */
>  
> -/* Configuration Register bits. */
> -#define CR_QUAD_EN_SPAN		BIT(1)	/* Spansion Quad I/O */
> -
>  /* Status Register 2 bits. */
> +#define SR2_QUAD_EN_BIT1	BIT(1)
>  #define SR2_QUAD_EN_BIT7	BIT(7)
>  
>  /* Supported SPI protocols */
> 

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH v5 2/2] drm/bridge: Add NWL MIPI DSI host controller support
From: Guido Günther @ 2019-09-19 17:43 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Mark Rutland, devicetree, Jernej Skrabec, Pengutronix Kernel Team,
	Sam Ravnborg, Neil Armstrong, David Airlie, Fabio Estevam,
	Sascha Hauer, Jonas Karlman, linux-kernel, dri-devel, Rob Herring,
	Arnd Bergmann, NXP Linux Team, Daniel Vetter, Robert Chiras,
	Lee Jones, Shawn Guo, linux-arm-kernel, Laurent Pinchart
In-Reply-To: <d728ea6f-f0c8-b7f4-7f68-c528485e3cc2@samsung.com>

Hi Andrzej,

thanks for your comments!

On Thu, Sep 19, 2019 at 03:56:45PM +0200, Andrzej Hajda wrote:
> On 14.09.2019 18:11, Guido Günther wrote:
> > Hi Andrzej,
> > thanks for having a look!
> >
> > On Fri, Sep 13, 2019 at 11:31:43AM +0200, Andrzej Hajda wrote:
> >> On 09.09.2019 04:25, Guido Günther wrote:
> >>> This adds initial support for the NWL MIPI DSI Host controller found on
> >>> i.MX8 SoCs.
> >>>
> >>> It adds support for the i.MX8MQ but the same IP can be found on
> >>> e.g. the i.MX8QXP.
> >>>
> >>> It has been tested on the Librem 5 devkit using mxsfb.
> >>>
> >>> Signed-off-by: Guido Günther <agx@sigxcpu.org>
> >>> Co-developed-by: Robert Chiras <robert.chiras@nxp.com>
> >>> Signed-off-by: Robert Chiras <robert.chiras@nxp.com>
> >>> Tested-by: Robert Chiras <robert.chiras@nxp.com>
> >>> ---
> >>>  drivers/gpu/drm/bridge/Kconfig           |   2 +
> >>>  drivers/gpu/drm/bridge/Makefile          |   1 +
> >>>  drivers/gpu/drm/bridge/nwl-dsi/Kconfig   |  16 +
> >>>  drivers/gpu/drm/bridge/nwl-dsi/Makefile  |   4 +
> >>>  drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c | 499 ++++++++++++++++
> >>>  drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h |  65 +++
> >>>  drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c | 696 +++++++++++++++++++++++
> >>>  drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h | 112 ++++
> >>
> >> Why do you need separate files nwl-drv.[ch] and nwl-dsi.[ch] ? I guess
> >> you can merge all into one file, maybe with separate file for NWL
> >> register definitions.
> > Idea is to have driver setup, soc specific hooks and revision specific
> > quirks in one file and the dsi specific parts in another. If that
> > doesn't fly I can merge into one if that's a requirement.
> 
> 
> One file looks saner to me :), but more importantly it follows current
> practice.

O.k., I'll merge things for v6 then.

> 
> 
> >
> >>>  8 files changed, 1395 insertions(+)
> >>>  create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/Kconfig
> >>>  create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/Makefile
> >>>  create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
> >>>  create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
> >>>  create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
> >>>  create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
> >>>
> >>> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> >>> index 1cc9f502c1f2..7980b5c2156f 100644
> >>> --- a/drivers/gpu/drm/bridge/Kconfig
> >>> +++ b/drivers/gpu/drm/bridge/Kconfig
> >>> @@ -154,6 +154,8 @@ source "drivers/gpu/drm/bridge/analogix/Kconfig"
> >>>  
> >>>  source "drivers/gpu/drm/bridge/adv7511/Kconfig"
> >>>  
> >>> +source "drivers/gpu/drm/bridge/nwl-dsi/Kconfig"
> >>> +
> >>>  source "drivers/gpu/drm/bridge/synopsys/Kconfig"
> >>>  
> >>>  endmenu
> >>> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> >>> index 4934fcf5a6f8..d9f6c0f77592 100644
> >>> --- a/drivers/gpu/drm/bridge/Makefile
> >>> +++ b/drivers/gpu/drm/bridge/Makefile
> >>> @@ -16,4 +16,5 @@ obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
> >>>  obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
> >>>  obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
> >>>  obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
> >>> +obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi/
> >>>  obj-y += synopsys/
> >>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/Kconfig b/drivers/gpu/drm/bridge/nwl-dsi/Kconfig
> >>> new file mode 100644
> >>> index 000000000000..7fa678e3b5e2
> >>> --- /dev/null
> >>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/Kconfig
> >>> @@ -0,0 +1,16 @@
> >>> +config DRM_NWL_MIPI_DSI
> >>> +	tristate "Northwest Logic MIPI DSI Host controller"
> >>> +	depends on DRM
> >>> +	depends on COMMON_CLK
> >>> +	depends on OF && HAS_IOMEM
> >>> +	select DRM_KMS_HELPER
> >>> +	select DRM_MIPI_DSI
> >>> +	select DRM_PANEL_BRIDGE
> >>> +	select GENERIC_PHY_MIPI_DPHY
> >>> +	select MFD_SYSCON
> >>> +	select MULTIPLEXER
> >>> +	select REGMAP_MMIO
> >>> +	help
> >>> +	  This enables the Northwest Logic MIPI DSI Host controller as
> >>> +	  for example found on NXP's i.MX8 Processors.
> >>> +
> >>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/Makefile b/drivers/gpu/drm/bridge/nwl-dsi/Makefile
> >>> new file mode 100644
> >>> index 000000000000..804baf2f1916
> >>> --- /dev/null
> >>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/Makefile
> >>> @@ -0,0 +1,4 @@
> >>> +# SPDX-License-Identifier: GPL-2.0
> >>> +nwl-mipi-dsi-y := nwl-drv.o nwl-dsi.o
> >>> +obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-mipi-dsi.o
> >>> +header-test-y += nwl-drv.h nwl-dsi.h
> >>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
> >>> new file mode 100644
> >>> index 000000000000..9ff43d2de127
> >>> --- /dev/null
> >>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
> >>> @@ -0,0 +1,499 @@
> >>> +// SPDX-License-Identifier: GPL-2.0+
> >>> +/*
> >>> + * i.MX8 NWL MIPI DSI host driver
> >>> + *
> >>> + * Copyright (C) 2017 NXP
> >>> + * Copyright (C) 2019 Purism SPC
> >>> + */
> >>> +
> >>> +#include <linux/clk.h>
> >>> +#include <linux/irq.h>
> >>> +#include <linux/mfd/syscon.h>
> >>> +#include <linux/module.h>
> >>> +#include <linux/mux/consumer.h>
> >>> +#include <linux/of.h>
> >>> +#include <linux/of_platform.h>
> >>> +#include <linux/phy/phy.h>
> >>> +#include <linux/reset.h>
> >>> +#include <linux/regmap.h>
> >>
> >> Alphabetic order
> > Fixed for v6.
> >
> >>> +#include <linux/sys_soc.h>
> >>> +
> >>> +#include <drm/drm_atomic_helper.h>
> >>> +#include <drm/drm_of.h>
> >>> +#include <drm/drm_print.h>
> >>> +#include <drm/drm_probe_helper.h>
> >>> +
> >>> +#include "nwl-drv.h"
> >>> +#include "nwl-dsi.h"
> >>> +
> >>> +#define DRV_NAME "nwl-dsi"
> >>> +
> >>> +/* Possible platform specific clocks */
> >>> +#define NWL_DSI_CLK_CORE	"core"
> >>> +
> >>> +static const struct regmap_config nwl_dsi_regmap_config = {
> >>> +	.reg_bits = 16,
> >>> +	.val_bits = 32,
> >>> +	.reg_stride = 4,
> >>> +	.max_register = NWL_DSI_IRQ_MASK2,
> >>> +	.name = DRV_NAME,
> >>> +};
> >>
> >> What is the point in using regmap here, why not simple writel/readl.
> > For me
> >
> >     cat /sys/kernel/debug/regmap/30a00000.mipi_dsi-imx-nwl-dsi/registers
> >
> > justifies it's use to help debugging problems when e.g. having it
> > connected to panels I don't own, so I think it's worth keeping if
> > possible.
> 
> 
> It still sounds for me like a sledgehammer to crack a nut, but it seems
> for many developers it is OK, so up to you.
> 
> 
> >
> >>> +
> >>> +struct nwl_dsi_platform_data {
> >>> +	int (*poweron)(struct nwl_dsi *dsi);
> >>> +	int (*poweroff)(struct nwl_dsi *dsi);
> >>> +	int (*select_input)(struct nwl_dsi *dsi);
> >>> +	int (*deselect_input)(struct nwl_dsi *dsi);
> >>> +	struct nwl_dsi_plat_clk_config clk_config[NWL_DSI_MAX_PLATFORM_CLOCKS];
> >>> +};
> >>
> >> Another construct which do not have justification, at least for now.
> >> Please simplify the driver, remove callbacks/intermediate
> >> structs/quirks
> >>
> >> - for now they are useless.
> >>
> >> Unless there is a serious reason - in such case please describe it in
> >> comments.
> > They're needed for i.mx 8QM SoC support (the current driver only
> > supports the i.mx 8MQ). It will be relatively easy to add with
> > these so I expect these to show up quickly. I'll add a comment. 
> 
> 
> That does not work well, it is impossible to review such code without
> looking at it's usage.
> 
> It would be better either to add 8QM driver in the same patchset showing
> the value of these callbacks, either remove them and add later together
> with 8QM driver.
> 
> Maybe these callbacks are not needed at all, but without 8QM code it is
> hard to decide.

I agree that it's hard to plan in advance until patches show up but let
me try to make a last argument for keeping them:

- The dsi select into imx8mq_dsi_select_input was suggested by Laurent
  since they're imx8 specific
- This would only leave poweron/off which is different between imx8mq
  and imx8qm:

  https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/gpu/drm/imx/nwl_dsi-imx.c?h=imx_4.14.78_1.0.0_ga#n419

if that's still not worth it, i can drop them making the driver smaller.

> Btw. naming different SoCs 8QM an 8MQ suggests i.mx engineers are quite
> nasty :)

It sure is.

> > The quirks on the other hand only apply to some i.mx8MQ mask revisions
> > so they need to be conditionalized. (or maybe I misunderstood you).
> 
> 
> I guess at the moment the driver is tested only on one platform - you
> have not tested platforms with different set of quirks, am I correct?

I have tested with two silicon mask revisions where one needs the quirks
and the other doesn't and it behaved as intended.

> If so, the quirk 'infrastructure' is not really tested, driver just
> anticipates other 8MQ SoCs/platforms. Practice shows that it does not
> work well - adding support for different revision usually require either
> more changes either no changes at all, so this pre-mature
> "diversification" serves nothing except unnecessary noise.

Does the above make the diversification o.k.?

> >>> +
> >>> +static inline struct nwl_dsi *bridge_to_dsi(struct drm_bridge *bridge)
> >>> +{
> >>> +	return container_of(bridge, struct nwl_dsi, bridge);
> >>> +}
> >>> +
> >>> +static int nwl_dsi_set_platform_clocks(struct nwl_dsi *dsi, bool enable)
> >>> +{
> >>> +	struct device *dev = dsi->dev;
> >>> +	const char *id;
> >>> +	struct clk *clk;
> >>> +	size_t i;
> >>> +	unsigned long rate;
> >>> +	int ret, result = 0;
> >>> +
> >>> +	DRM_DEV_DEBUG_DRIVER(dev, "%s platform clocks\n",
> >>> +			     enable ? "enabling" : "disabling");
> >>> +	for (i = 0; i < ARRAY_SIZE(dsi->pdata->clk_config); i++) {
> >>> +		if (!dsi->clk_config[i].present)
> >>> +			continue;
> >>> +		id = dsi->clk_config[i].id;
> >>> +		clk = dsi->clk_config[i].clk;
> >>> +
> >>> +		if (enable) {
> >>> +			ret = clk_prepare_enable(clk);
> >>> +			if (ret < 0) {
> >>> +				DRM_DEV_ERROR(dev,
> >>> +					      "Failed to enable %s clk: %d\n",
> >>> +					      id, ret);
> >>> +				result = result ?: ret;
> >>> +			}
> >>> +			rate = clk_get_rate(clk);
> >>> +			DRM_DEV_DEBUG_DRIVER(dev, "Enabled %s clk @%lu Hz\n",
> >>> +					     id, rate);
> >>> +		} else {
> >>> +			clk_disable_unprepare(clk);
> >>> +			DRM_DEV_DEBUG_DRIVER(dev, "Disabled %s clk\n", id);
> >>> +		}
> >>> +	}
> >>> +
> >>> +	return result;
> >>> +}
> >>> +
> >>> +static int nwl_dsi_plat_enable(struct nwl_dsi *dsi)
> >>> +{
> >>> +	struct device *dev = dsi->dev;
> >>> +	int ret;
> >>> +
> >>> +	if (dsi->pdata->select_input)
> >>> +		dsi->pdata->select_input(dsi);
> >>> +
> >>> +	ret = nwl_dsi_set_platform_clocks(dsi, true);
> >>> +	if (ret < 0)
> >>> +		return ret;
> >>> +
> >>> +	ret = dsi->pdata->poweron(dsi);
> >>> +	if (ret < 0)
> >>> +		DRM_DEV_ERROR(dev, "Failed to power on DSI: %d\n", ret);
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +static void nwl_dsi_plat_disable(struct nwl_dsi *dsi)
> >>> +{
> >>> +	dsi->pdata->poweroff(dsi);
> >>> +	nwl_dsi_set_platform_clocks(dsi, false);
> >>> +	if (dsi->pdata->deselect_input)
> >>> +		dsi->pdata->deselect_input(dsi);
> >>> +}
> >>> +
> >>> +static void nwl_dsi_bridge_disable(struct drm_bridge *bridge)
> >>> +{
> >>> +	struct nwl_dsi *dsi = bridge_to_dsi(bridge);
> >>> +
> >>> +	nwl_dsi_disable(dsi);
> >>> +	nwl_dsi_plat_disable(dsi);
> >>> +	pm_runtime_put(dsi->dev);
> >>> +}
> >>> +
> >>> +static int nwl_dsi_get_dphy_params(struct nwl_dsi *dsi,
> >>> +				   const struct drm_display_mode *mode,
> >>> +				   union phy_configure_opts *phy_opts)
> >>> +{
> >>> +	unsigned long rate;
> >>> +	int ret;
> >>> +
> >>> +	if (dsi->lanes < 1 || dsi->lanes > 4)
> >>> +		return -EINVAL;
> >>> +
> >>> +	/*
> >>> +	 * So far the DPHY spec minimal timings work for both mixel
> >>> +	 * dphy and nwl dsi host
> >>> +	 */
> >>> +	ret = phy_mipi_dphy_get_default_config(
> >>> +		mode->crtc_clock * 1000,
> >>> +		mipi_dsi_pixel_format_to_bpp(dsi->format), dsi->lanes,
> >>> +		&phy_opts->mipi_dphy);
> >>> +	if (ret < 0)
> >>> +		return ret;
> >>> +
> >>> +	rate = clk_get_rate(dsi->tx_esc_clk);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "LP clk is @%lu Hz\n", rate);
> >>> +	phy_opts->mipi_dphy.lp_clk_rate = rate;
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static bool nwl_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
> >>> +				      const struct drm_display_mode *mode,
> >>> +				      struct drm_display_mode *adjusted_mode)
> >>> +{
> >>> +	/* At least LCDIF + NWL needs active high sync */
> >>> +	adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC);
> >>> +	adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC);
> >>> +
> >>> +	return true;
> >>> +}
> >>> +
> >>> +static enum drm_mode_status
> >>> +nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge,
> >>> +			  const struct drm_display_mode *mode)
> >>> +{
> >>> +	struct nwl_dsi *dsi = bridge_to_dsi(bridge);
> >>> +	int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> >>> +
> >>> +	if (mode->clock * bpp > 15000000 * dsi->lanes)
> >>> +		return MODE_CLOCK_HIGH;
> >>> +
> >>> +	if (mode->clock * bpp < 80000 * dsi->lanes)
> >>> +		return MODE_CLOCK_LOW;
> >>> +
> >>> +	return MODE_OK;
> >>> +}
> >>> +
> >>> +static void
> >>> +nwl_dsi_bridge_mode_set(struct drm_bridge *bridge,
> >>> +			const struct drm_display_mode *mode,
> >>> +			const struct drm_display_mode *adjusted_mode)
> >>> +{
> >>> +	struct nwl_dsi *dsi = bridge_to_dsi(bridge);
> >>> +	struct device *dev = dsi->dev;
> >>> +	union phy_configure_opts new_cfg;
> >>> +	unsigned long phy_ref_rate;
> >>> +	int ret;
> >>> +
> >>> +	ret = nwl_dsi_get_dphy_params(dsi, adjusted_mode, &new_cfg);
> >>> +	if (ret < 0)
> >>> +		return;
> >>> +
> >>> +	/*
> >>> +	 * If hs clock is unchanged, we're all good - all parameters are
> >>> +	 * derived from it atm.
> >>> +	 */
> >>> +	if (new_cfg.mipi_dphy.hs_clk_rate == dsi->phy_cfg.mipi_dphy.hs_clk_rate)
> >>> +		return;
> >>> +
> >>> +	phy_ref_rate = clk_get_rate(dsi->phy_ref_clk);
> >>> +	DRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n", phy_ref_rate);
> >>> +	/* Save the new desired phy config */
> >>> +	memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg));
> >>> +
> >>> +	memcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode));
> >>> +	drm_mode_debug_printmodeline(adjusted_mode);
> >>> +}
> >>> +
> >>> +static void nwl_dsi_bridge_pre_enable(struct drm_bridge *bridge)
> >>> +{
> >>> +	struct nwl_dsi *dsi = bridge_to_dsi(bridge);
> >>> +
> >>> +	pm_runtime_get_sync(dsi->dev);
> >>> +	nwl_dsi_plat_enable(dsi);
> >>> +	nwl_dsi_enable(dsi);
> >>> +}
> >>> +
> >>> +static int nwl_dsi_bridge_attach(struct drm_bridge *bridge)
> >>> +{
> >>> +	struct nwl_dsi *dsi = bridge->driver_private;
> >>> +
> >>> +	return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge);
> >>> +}
> >>> +
> >>> +static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = {
> >>> +	.pre_enable = nwl_dsi_bridge_pre_enable,
> >>> +	.disable    = nwl_dsi_bridge_disable,
> >>> +	.mode_fixup = nwl_dsi_bridge_mode_fixup,
> >>> +	.mode_set   = nwl_dsi_bridge_mode_set,
> >>> +	.mode_valid = nwl_dsi_bridge_mode_valid,
> >>> +	.attach	    = nwl_dsi_bridge_attach,
> >>> +};
> >>> +
> >>> +static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
> >>> +{
> >>> +	struct platform_device *pdev = to_platform_device(dsi->dev);
> >>> +	struct clk *clk;
> >>> +	const char *clk_id;
> >>> +	void __iomem *base;
> >>> +	int i, ret;
> >>> +
> >>> +	dsi->phy = devm_phy_get(dsi->dev, "dphy");
> >>> +	if (IS_ERR(dsi->phy)) {
> >>> +		ret = PTR_ERR(dsi->phy);
> >>> +		if (ret != -EPROBE_DEFER)
> >>> +			DRM_DEV_ERROR(dsi->dev, "Could not get PHY: %d\n", ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	/* Platform dependent clocks */
> >>> +	memcpy(dsi->clk_config, dsi->pdata->clk_config,
> >>> +	       sizeof(dsi->pdata->clk_config));
> >>> +
> >>> +	for (i = 0; i < ARRAY_SIZE(dsi->pdata->clk_config); i++) {
> >>> +		if (!dsi->clk_config[i].present)
> >>> +			continue;
> >>> +
> >>> +		clk_id = dsi->clk_config[i].id;
> >>> +		clk = devm_clk_get(dsi->dev, clk_id);
> >>> +		if (IS_ERR(clk)) {
> >>> +			ret = PTR_ERR(clk);
> >>> +			DRM_DEV_ERROR(dsi->dev, "Failed to get %s clock: %d\n",
> >>> +				      clk_id, ret);
> >>> +			return ret;
> >>> +		}
> >>> +		DRM_DEV_DEBUG_DRIVER(dsi->dev, "Setup clk %s (rate: %lu)\n",
> >>> +				     clk_id, clk_get_rate(clk));
> >>> +		dsi->clk_config[i].clk = clk;
> >>> +	}
> >>> +
> >>> +	/* DSI clocks */
> >>> +	clk = devm_clk_get(dsi->dev, "phy_ref");
> >>> +	if (IS_ERR(clk)) {
> >>> +		ret = PTR_ERR(clk);
> >>> +		DRM_DEV_ERROR(dsi->dev, "Failed to get phy_ref clock: %d\n",
> >>> +			      ret);
> >>> +		return ret;
> >>> +	}
> >>> +	dsi->phy_ref_clk = clk;
> >>> +
> >>> +	clk = devm_clk_get(dsi->dev, "rx_esc");
> >>> +	if (IS_ERR(clk)) {
> >>> +		ret = PTR_ERR(clk);
> >>> +		DRM_DEV_ERROR(dsi->dev, "Failed to get rx_esc clock: %d\n",
> >>> +			      ret);
> >>> +		return ret;
> >>> +	}
> >>> +	dsi->rx_esc_clk = clk;
> >>> +
> >>> +	clk = devm_clk_get(dsi->dev, "tx_esc");
> >>> +	if (IS_ERR(clk)) {
> >>> +		ret = PTR_ERR(clk);
> >>> +		DRM_DEV_ERROR(dsi->dev, "Failed to get tx_esc clock: %d\n",
> >>> +			      ret);
> >>> +		return ret;
> >>> +	}
> >>> +	dsi->tx_esc_clk = clk;
> >>> +
> >>> +	dsi->mux = devm_mux_control_get(dsi->dev, NULL);
> >>> +	if (IS_ERR(dsi->mux)) {
> >>> +		ret = PTR_ERR(dsi->mux);
> >>> +		if (ret != -EPROBE_DEFER)
> >>> +			DRM_DEV_ERROR(dsi->dev, "Failed to get mux: %d\n", ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	base = devm_platform_ioremap_resource(pdev, 0);
> >>> +	if (IS_ERR(base))
> >>> +		return PTR_ERR(base);
> >>> +
> >>> +	dsi->regmap =
> >>> +		devm_regmap_init_mmio(dsi->dev, base, &nwl_dsi_regmap_config);
> >>> +	if (IS_ERR(dsi->regmap)) {
> >>> +		ret = PTR_ERR(dsi->regmap);
> >>> +		DRM_DEV_ERROR(dsi->dev, "Failed to create NWL DSI regmap: %d\n",
> >>> +			      ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	dsi->irq = platform_get_irq(pdev, 0);
> >>> +	if (dsi->irq < 0) {
> >>> +		DRM_DEV_ERROR(dsi->dev, "Failed to get device IRQ: %d\n",
> >>> +			      dsi->irq);
> >>> +		return dsi->irq;
> >>> +	}
> >>> +
> >>> +	dsi->rstc = devm_reset_control_array_get(dsi->dev, false, true);
> >>> +	if (IS_ERR(dsi->rstc)) {
> >>> +		DRM_DEV_ERROR(dsi->dev, "Failed to get resets: %ld\n",
> >>> +			      PTR_ERR(dsi->rstc));
> >>> +		return PTR_ERR(dsi->rstc);
> >>> +	}
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int imx8mq_dsi_select_input(struct nwl_dsi *dsi)
> >>> +{
> >>> +	struct device_node *remote;
> >>> +	u32 use_dcss = 1;
> >>> +	int ret;
> >>> +
> >>> +	remote = of_graph_get_remote_node(dsi->dev->of_node, 0, 0);
> >>> +	if (strcmp(remote->name, "lcdif") == 0)
> >>> +		use_dcss = 0;
> >>
> >> Relying on node name seems to me wrong. I am not sure if whole logic for
> >> input select should be here.
> >>
> >> My 1st impression is that selecting should be done rather in DCSS or
> >> LCDIF driver, why do you want to put it here?
> > Doing it in here keeps it at a single location where on the other hand
> > it would need to be done in mxsfb (which handles other SoCs as well) and
> > upcoming dcss. Also we can have in the dsi enable path which e.g. mxsfb
> > doesn't even know about at this point.
> 
> 
> But as I understand mux is not a part of this IP. It is always
> problematic what to do with such small pieces of hw.
> 
> Let's keep it here until someone find better solution.
> 
> Btw do you have public tree for testing platforms with this driver, I am
> curious about this mux device.

https://source.puri.sm/guido.gunther/linux-imx8/blob/forward-upstream/next-20190823/mxsfb+nwl/v4/arch/arm64/boot/dts/freescale/imx8mq.dtsi#L477

it was prompted by

https://lists.freedesktop.org/archives/dri-devel/2019-August/230868.html

Cheers and thanks again for your comments,
 -- Guido

> 
> 
> Regards
> 
> Andrzej
> 
> 
> >
> > Cheers,
> >  -- Guido
> >
> >> Regards
> >>
> >> Andrzej
> >>
> >>
> >>> +
> >>> +	DRM_DEV_INFO(dsi->dev, "Using %s as input source\n",
> >>> +		     (use_dcss) ? "DCSS" : "LCDIF");
> >>> +
> >>> +	ret = mux_control_try_select(dsi->mux, use_dcss);
> >>> +	if (ret < 0)
> >>> +		DRM_DEV_ERROR(dsi->dev, "Failed to select input: %d\n", ret);
> >>> +
> >>> +	of_node_put(remote);
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +
> >>> +static int imx8mq_dsi_deselect_input(struct nwl_dsi *dsi)
> >>> +{
> >>> +	int ret;
> >>> +
> >>> +	ret = mux_control_deselect(dsi->mux);
> >>> +	if (ret < 0)
> >>> +		DRM_DEV_ERROR(dsi->dev, "Failed to deselect input: %d\n", ret);
> >>> +
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +
> >>> +static int imx8mq_dsi_poweron(struct nwl_dsi *dsi)
> >>> +{
> >>> +	int ret = 0;
> >>> +
> >>> +	/* otherwise the display stays blank */
> >>> +	usleep_range(200, 300);
> >>> +
> >>> +	if (dsi->rstc)
> >>> +		ret = reset_control_deassert(dsi->rstc);
> >>> +
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +static int imx8mq_dsi_poweroff(struct nwl_dsi *dsi)
> >>> +{
> >>> +	int ret = 0;
> >>> +
> >>> +	if (dsi->quirks & SRC_RESET_QUIRK)
> >>> +		return 0;
> >>> +
> >>> +	if (dsi->rstc)
> >>> +		ret = reset_control_assert(dsi->rstc);
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +static const struct drm_bridge_timings nwl_dsi_timings = {
> >>> +	.input_bus_flags = DRM_BUS_FLAG_DE_LOW,
> >>> +};
> >>> +
> >>> +static const struct nwl_dsi_platform_data imx8mq_dev = {
> >>> +	.poweron = &imx8mq_dsi_poweron,
> >>> +	.poweroff = &imx8mq_dsi_poweroff,
> >>> +	.select_input = &imx8mq_dsi_select_input,
> >>> +	.deselect_input = &imx8mq_dsi_deselect_input,
> >>> +	.clk_config = {
> >>> +		{ .id = NWL_DSI_CLK_CORE, .present = true },
> >>> +	},
> >>> +};
> >>> +
> >>> +static const struct of_device_id nwl_dsi_dt_ids[] = {
> >>> +	{ .compatible = "fsl,imx8mq-nwl-dsi", .data = &imx8mq_dev, },
> >>> +	{ /* sentinel */ }
> >>> +};
> >>> +MODULE_DEVICE_TABLE(of, nwl_dsi_dt_ids);
> >>> +
> >>> +static const struct soc_device_attribute nwl_dsi_quirks_match[] = {
> >>> +	{ .soc_id = "i.MX8MQ", .revision = "2.0",
> >>> +	  .data = (void *)(E11418_HS_MODE_QUIRK | SRC_RESET_QUIRK) },
> >>> +	{ /* sentinel. */ },
> >>> +};
> >>> +
> >>> +static int nwl_dsi_probe(struct platform_device *pdev)
> >>> +{
> >>> +	struct device *dev = &pdev->dev;
> >>> +	const struct of_device_id *of_id = of_match_device(nwl_dsi_dt_ids, dev);
> >>> +	const struct nwl_dsi_platform_data *pdata = of_id->data;
> >>> +	const struct soc_device_attribute *attr;
> >>> +	struct nwl_dsi *dsi;
> >>> +	int ret;
> >>> +
> >>> +	dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
> >>> +	if (!dsi)
> >>> +		return -ENOMEM;
> >>> +
> >>> +	dsi->dev = dev;
> >>> +	dsi->pdata = pdata;
> >>> +
> >>> +	ret = nwl_dsi_parse_dt(dsi);
> >>> +	if (ret)
> >>> +		return ret;
> >>> +
> >>> +	ret = devm_request_irq(dev, dsi->irq, nwl_dsi_irq_handler, 0,
> >>> +			       dev_name(dev), dsi);
> >>> +	if (ret < 0) {
> >>> +		DRM_DEV_ERROR(dev, "Failed to request IRQ %d: %d\n", dsi->irq,
> >>> +			      ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	dsi->dsi_host.ops = &nwl_dsi_host_ops;
> >>> +	dsi->dsi_host.dev = dev;
> >>> +	ret = mipi_dsi_host_register(&dsi->dsi_host);
> >>> +	if (ret) {
> >>> +		DRM_DEV_ERROR(dev, "Failed to register MIPI host: %d\n", ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	attr = soc_device_match(nwl_dsi_quirks_match);
> >>> +	if (attr)
> >>> +		dsi->quirks = (uintptr_t)attr->data;
> >>> +
> >>> +	dsi->bridge.driver_private = dsi;
> >>> +	dsi->bridge.funcs = &nwl_dsi_bridge_funcs;
> >>> +	dsi->bridge.of_node = dev->of_node;
> >>> +	dsi->bridge.timings = &nwl_dsi_timings;
> >>> +
> >>> +	dev_set_drvdata(dev, dsi);
> >>> +	pm_runtime_enable(dev);
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int nwl_dsi_remove(struct platform_device *pdev)
> >>> +{
> >>> +	struct nwl_dsi *dsi = platform_get_drvdata(pdev);
> >>> +
> >>> +	mipi_dsi_host_unregister(&dsi->dsi_host);
> >>> +	pm_runtime_disable(&pdev->dev);
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static struct platform_driver nwl_dsi_driver = {
> >>> +	.probe		= nwl_dsi_probe,
> >>> +	.remove		= nwl_dsi_remove,
> >>> +	.driver		= {
> >>> +		.of_match_table = nwl_dsi_dt_ids,
> >>> +		.name	= DRV_NAME,
> >>> +	},
> >>> +};
> >>> +
> >>> +module_platform_driver(nwl_dsi_driver);
> >>> +
> >>> +MODULE_AUTHOR("NXP Semiconductor");
> >>> +MODULE_AUTHOR("Purism SPC");
> >>> +MODULE_DESCRIPTION("Northwest Logic MIPI-DSI driver");
> >>> +MODULE_LICENSE("GPL"); /* GPLv2 or later */
> >>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
> >>> new file mode 100644
> >>> index 000000000000..1e72a9221401
> >>> --- /dev/null
> >>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
> >>> @@ -0,0 +1,65 @@
> >>> +/* SPDX-License-Identifier: GPL-2.0+ */
> >>> +/*
> >>> + * NWL MIPI DSI host driver
> >>> + *
> >>> + * Copyright (C) 2017 NXP
> >>> + * Copyright (C) 2019 Purism SPC
> >>> + */
> >>> +
> >>> +#ifndef __NWL_DRV_H__
> >>> +#define __NWL_DRV_H__
> >>> +
> >>> +#include <linux/mux/consumer.h>
> >>> +#include <linux/phy/phy.h>
> >>> +
> >>> +#include <drm/drm_bridge.h>
> >>> +#include <drm/drm_mipi_dsi.h>
> >>> +
> >>> +struct nwl_dsi_platform_data;
> >>> +
> >>> +/* i.MX8 NWL quirks */
> >>> +/* i.MX8MQ errata E11418 */
> >>> +#define E11418_HS_MODE_QUIRK	    BIT(0)
> >>> +/* Skip DSI bits in SRC on disable to avoid blank display on enable */
> >>> +#define SRC_RESET_QUIRK		    BIT(1)
> >>> +
> >>> +#define NWL_DSI_MAX_PLATFORM_CLOCKS 1
> >>> +struct nwl_dsi_plat_clk_config {
> >>> +	const char *id;
> >>> +	struct clk *clk;
> >>> +	bool present;
> >>> +};
> >>> +
> >>> +struct nwl_dsi {
> >>> +	struct drm_bridge bridge;
> >>> +	struct mipi_dsi_host dsi_host;
> >>> +	struct drm_bridge *panel_bridge;
> >>> +	struct device *dev;
> >>> +	struct phy *phy;
> >>> +	union phy_configure_opts phy_cfg;
> >>> +	unsigned int quirks;
> >>> +
> >>> +	struct regmap *regmap;
> >>> +	int irq;
> >>> +	struct reset_control *rstc;
> >>> +	struct mux_control *mux;
> >>> +
> >>> +	/* DSI clocks */
> >>> +	struct clk *phy_ref_clk;
> >>> +	struct clk *rx_esc_clk;
> >>> +	struct clk *tx_esc_clk;
> >>> +	/* Platform dependent clocks */
> >>> +	struct nwl_dsi_plat_clk_config clk_config[NWL_DSI_MAX_PLATFORM_CLOCKS];
> >>> +
> >>> +	/* dsi lanes */
> >>> +	u32 lanes;
> >>> +	enum mipi_dsi_pixel_format format;
> >>> +	struct drm_display_mode mode;
> >>> +	unsigned long dsi_mode_flags;
> >>> +
> >>> +	struct nwl_dsi_transfer *xfer;
> >>> +
> >>> +	const struct nwl_dsi_platform_data *pdata;
> >>> +};
> >>> +
> >>> +#endif /* __NWL_DRV_H__ */
> >>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
> >>> new file mode 100644
> >>> index 000000000000..e6038cb4e849
> >>> --- /dev/null
> >>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
> >>> @@ -0,0 +1,696 @@
> >>> +// SPDX-License-Identifier: GPL-2.0+
> >>> +/*
> >>> + * NWL MIPI DSI host driver
> >>> + *
> >>> + * Copyright (C) 2017 NXP
> >>> + * Copyright (C) 2019 Purism SPC
> >>> + */
> >>> +
> >>> +#include <linux/bitfield.h>
> >>> +#include <linux/clk.h>
> >>> +#include <linux/irq.h>
> >>> +#include <linux/math64.h>
> >>> +#include <linux/regmap.h>
> >>> +#include <linux/time64.h>
> >>> +
> >>> +#include <video/mipi_display.h>
> >>> +#include <video/videomode.h>
> >>> +
> >>> +#include <drm/drm_atomic_helper.h>
> >>> +#include <drm/drm_crtc_helper.h>
> >>> +#include <drm/drm_of.h>
> >>> +#include <drm/drm_panel.h>
> >>> +#include <drm/drm_print.h>
> >>> +
> >>> +#include "nwl-drv.h"
> >>> +#include "nwl-dsi.h"
> >>> +
> >>> +#define NWL_DSI_MIPI_FIFO_TIMEOUT msecs_to_jiffies(500)
> >>> +
> >>> +/*
> >>> + * PKT_CONTROL format:
> >>> + * [15: 0] - word count
> >>> + * [17:16] - virtual channel
> >>> + * [23:18] - data type
> >>> + * [24]	   - LP or HS select (0 - LP, 1 - HS)
> >>> + * [25]	   - perform BTA after packet is sent
> >>> + * [26]	   - perform BTA only, no packet tx
> >>> + */
> >>> +#define NWL_DSI_WC(x)		FIELD_PREP(GENMASK(15, 0), (x))
> >>> +#define NWL_DSI_TX_VC(x)	FIELD_PREP(GENMASK(17, 16), (x))
> >>> +#define NWL_DSI_TX_DT(x)	FIELD_PREP(GENMASK(23, 18), (x))
> >>> +#define NWL_DSI_HS_SEL(x)	FIELD_PREP(GENMASK(24, 24), (x))
> >>> +#define NWL_DSI_BTA_TX(x)	FIELD_PREP(GENMASK(25, 25), (x))
> >>> +#define NWL_DSI_BTA_NO_TX(x)	FIELD_PREP(GENMASK(26, 26), (x))
> >>> +
> >>> +/*
> >>> + * RX_PKT_HEADER format:
> >>> + * [15: 0] - word count
> >>> + * [21:16] - data type
> >>> + * [23:22] - virtual channel
> >>> + */
> >>> +#define NWL_DSI_RX_DT(x)	FIELD_GET(GENMASK(21, 16), (x))
> >>> +#define NWL_DSI_RX_VC(x)	FIELD_GET(GENMASK(23, 22), (x))
> >>> +
> >>> +/* DSI Video mode */
> >>> +#define NWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSES		0
> >>> +#define NWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_EVENTS	BIT(0)
> >>> +#define NWL_DSI_VM_BURST_MODE				BIT(1)
> >>> +
> >>> +/* * DPI color coding */
> >>> +#define NWL_DSI_DPI_16_BIT_565_PACKED	0
> >>> +#define NWL_DSI_DPI_16_BIT_565_ALIGNED	1
> >>> +#define NWL_DSI_DPI_16_BIT_565_SHIFTED	2
> >>> +#define NWL_DSI_DPI_18_BIT_PACKED	3
> >>> +#define NWL_DSI_DPI_18_BIT_ALIGNED	4
> >>> +#define NWL_DSI_DPI_24_BIT		5
> >>> +
> >>> +/* * DPI Pixel format */
> >>> +#define NWL_DSI_PIXEL_FORMAT_16  0
> >>> +#define NWL_DSI_PIXEL_FORMAT_18  BIT(0)
> >>> +#define NWL_DSI_PIXEL_FORMAT_18L BIT(1)
> >>> +#define NWL_DSI_PIXEL_FORMAT_24  (BIT(0) | BIT(1))
> >>> +
> >>> +enum transfer_direction {
> >>> +	DSI_PACKET_SEND,
> >>> +	DSI_PACKET_RECEIVE,
> >>> +};
> >>> +
> >>> +struct nwl_dsi_transfer {
> >>> +	const struct mipi_dsi_msg *msg;
> >>> +	struct mipi_dsi_packet packet;
> >>> +	struct completion completed;
> >>> +
> >>> +	int status; /* status of transmission */
> >>> +	enum transfer_direction direction;
> >>> +	bool need_bta;
> >>> +	u8 cmd;
> >>> +	u16 rx_word_count;
> >>> +	size_t tx_len; /* in bytes */
> >>> +	size_t rx_len; /* in bytes */
> >>> +};
> >>> +
> >>> +static int nwl_dsi_write(struct nwl_dsi *dsi, unsigned int reg, u32 val)
> >>> +{
> >>> +	int ret;
> >>> +
> >>> +	ret = regmap_write(dsi->regmap, reg, val);
> >>> +	if (ret < 0)
> >>> +		DRM_DEV_ERROR(dsi->dev,
> >>> +			      "Failed to write NWL DSI reg 0x%x: %d\n", reg,
> >>> +			      ret);
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +static u32 nwl_dsi_read(struct nwl_dsi *dsi, u32 reg)
> >>> +{
> >>> +	unsigned int val;
> >>> +	int ret;
> >>> +
> >>> +	ret = regmap_read(dsi->regmap, reg, &val);
> >>> +	if (ret < 0)
> >>> +		DRM_DEV_ERROR(dsi->dev, "Failed to read NWL DSI reg 0x%x: %d\n",
> >>> +			      reg, ret);
> >>> +
> >>> +	return val;
> >>> +}
> >>> +
> >>> +static u32 nwl_dsi_get_dpi_pixel_format(enum mipi_dsi_pixel_format format)
> >>> +{
> >>> +	switch (format) {
> >>> +	case MIPI_DSI_FMT_RGB565:
> >>> +		return NWL_DSI_PIXEL_FORMAT_16;
> >>> +	case MIPI_DSI_FMT_RGB666:
> >>> +		return NWL_DSI_PIXEL_FORMAT_18L;
> >>> +	case MIPI_DSI_FMT_RGB666_PACKED:
> >>> +		return NWL_DSI_PIXEL_FORMAT_18;
> >>> +	case MIPI_DSI_FMT_RGB888:
> >>> +		return NWL_DSI_PIXEL_FORMAT_24;
> >>> +	default:
> >>> +		return -EINVAL;
> >>> +	}
> >>> +}
> >>> +
> >>> +/*
> >>> + * ps2bc - Picoseconds to byte clock cycles
> >>> + */
> >>> +static u32 ps2bc(struct nwl_dsi *dsi, unsigned long long ps)
> >>> +{
> >>> +	u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> >>> +
> >>> +	return DIV64_U64_ROUND_UP(ps * dsi->mode.clock * bpp,
> >>> +				  dsi->lanes * 8 * NSEC_PER_SEC);
> >>> +}
> >>> +
> >>> +/*
> >>> + * ui2bc - UI time periods to byte clock cycles
> >>> + */
> >>> +static u32 ui2bc(struct nwl_dsi *dsi, unsigned long long ui)
> >>> +{
> >>> +	u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> >>> +
> >>> +	return DIV64_U64_ROUND_UP(ui * dsi->lanes,
> >>> +				  dsi->mode.clock * 1000 * bpp);
> >>> +}
> >>> +
> >>> +/*
> >>> + * us2bc - micro seconds to lp clock cycles
> >>> + */
> >>> +static u32 us2lp(u32 lp_clk_rate, unsigned long us)
> >>> +{
> >>> +	return DIV_ROUND_UP(us * lp_clk_rate, USEC_PER_SEC);
> >>> +}
> >>> +
> >>> +static int nwl_dsi_config_host(struct nwl_dsi *dsi)
> >>> +{
> >>> +	u32 cycles;
> >>> +	struct phy_configure_opts_mipi_dphy *cfg = &dsi->phy_cfg.mipi_dphy;
> >>> +
> >>> +	if (dsi->lanes < 1 || dsi->lanes > 4)
> >>> +		return -EINVAL;
> >>> +
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "DSI Lanes %d\n", dsi->lanes);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_CFG_NUM_LANES, dsi->lanes - 1);
> >>> +
> >>> +	if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
> >>> +		nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x01);
> >>> +		nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x01);
> >>> +	} else {
> >>> +		nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x00);
> >>> +		nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x00);
> >>> +	}
> >>> +
> >>> +	/* values in byte clock cycles */
> >>> +	cycles = ui2bc(dsi, cfg->clk_pre);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_pre: 0x%x\n", cycles);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_CFG_T_PRE, cycles);
> >>> +	cycles = ps2bc(dsi, cfg->lpx + cfg->clk_prepare + cfg->clk_zero);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap (pre): 0x%x\n", cycles);
> >>> +	cycles += ui2bc(dsi, cfg->clk_pre);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_post: 0x%x\n", cycles);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_CFG_T_POST, cycles);
> >>> +	cycles = ps2bc(dsi, cfg->hs_exit);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap: 0x%x\n", cycles);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_CFG_TX_GAP, cycles);
> >>> +
> >>> +	nwl_dsi_write(dsi, NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTP, 0x01);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_CFG_HTX_TO_COUNT, 0x00);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_CFG_LRX_H_TO_COUNT, 0x00);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_CFG_BTA_H_TO_COUNT, 0x00);
> >>> +	/* In LP clock cycles */
> >>> +	cycles = us2lp(cfg->lp_clk_rate, cfg->wakeup);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_twakeup: 0x%x\n", cycles);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_CFG_TWAKEUP, cycles);
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int nwl_dsi_config_dpi(struct nwl_dsi *dsi)
> >>> +{
> >>> +	u32 color_format, mode;
> >>> +	bool burst_mode;
> >>> +	int hfront_porch, hback_porch, vfront_porch, vback_porch;
> >>> +	int hsync_len, vsync_len;
> >>> +
> >>> +	hfront_porch = dsi->mode.hsync_start - dsi->mode.hdisplay;
> >>> +	hsync_len = dsi->mode.hsync_end - dsi->mode.hsync_start;
> >>> +	hback_porch = dsi->mode.htotal - dsi->mode.hsync_end;
> >>> +
> >>> +	vfront_porch = dsi->mode.vsync_start - dsi->mode.vdisplay;
> >>> +	vsync_len = dsi->mode.vsync_end - dsi->mode.vsync_start;
> >>> +	vback_porch = dsi->mode.vtotal - dsi->mode.vsync_end;
> >>> +
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "hfront_porch = %d\n", hfront_porch);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "hback_porch = %d\n", hback_porch);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "hsync_len = %d\n", hsync_len);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "hdisplay = %d\n", dsi->mode.hdisplay);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "vfront_porch = %d\n", vfront_porch);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "vback_porch = %d\n", vback_porch);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "vsync_len = %d\n", vsync_len);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "vactive = %d\n", dsi->mode.vdisplay);
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "clock = %d kHz\n", dsi->mode.clock);
> >>> +
> >>> +	color_format = nwl_dsi_get_dpi_pixel_format(dsi->format);
> >>> +	if (color_format < 0) {
> >>> +		DRM_DEV_ERROR(dsi->dev, "Invalid color format 0x%x\n",
> >>> +			      dsi->format);
> >>> +		return color_format;
> >>> +	}
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "pixel fmt = %d\n", dsi->format);
> >>> +
> >>> +	nwl_dsi_write(dsi, NWL_DSI_INTERFACE_COLOR_CODING, NWL_DSI_DPI_24_BIT);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_PIXEL_FORMAT, color_format);
> >>> +	/*
> >>> +	 * Adjusting input polarity based on the video mode results in
> >>> +	 * a black screen so always pick active low:
> >>> +	 */
> >>> +	nwl_dsi_write(dsi, NWL_DSI_VSYNC_POLARITY,
> >>> +		      NWL_DSI_VSYNC_POLARITY_ACTIVE_LOW);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_HSYNC_POLARITY,
> >>> +		      NWL_DSI_HSYNC_POLARITY_ACTIVE_LOW);
> >>> +
> >>> +	burst_mode = (dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_BURST) &&
> >>> +		     !(dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE);
> >>> +
> >>> +	if (burst_mode) {
> >>> +		nwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE, NWL_DSI_VM_BURST_MODE);
> >>> +		nwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL, 256);
> >>> +	} else {
> >>> +		mode = ((dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) ?
> >>> +				NWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSES :
> >>> +				NWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_EVENTS);
> >>> +		nwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE, mode);
> >>> +		nwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL,
> >>> +			      dsi->mode.hdisplay);
> >>> +	}
> >>> +
> >>> +	nwl_dsi_write(dsi, NWL_DSI_HFP, hfront_porch);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_HBP, hback_porch);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_HSA, hsync_len);
> >>> +
> >>> +	nwl_dsi_write(dsi, NWL_DSI_ENABLE_MULT_PKTS, 0x0);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_BLLP_MODE, 0x1);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_USE_NULL_PKT_BLLP, 0x0);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_VC, 0x0);
> >>> +
> >>> +	nwl_dsi_write(dsi, NWL_DSI_PIXEL_PAYLOAD_SIZE, dsi->mode.hdisplay);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_VACTIVE, dsi->mode.vdisplay - 1);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_VBP, vback_porch);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_VFP, vfront_porch);
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static void nwl_dsi_init_interrupts(struct nwl_dsi *dsi)
> >>> +{
> >>> +	u32 irq_enable;
> >>> +
> >>> +	nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, 0xffffffff);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK2, 0x7);
> >>> +
> >>> +	irq_enable = ~(u32)(NWL_DSI_TX_PKT_DONE_MASK |
> >>> +			    NWL_DSI_RX_PKT_HDR_RCVD_MASK |
> >>> +			    NWL_DSI_TX_FIFO_OVFLW_MASK |
> >>> +			    NWL_DSI_HS_TX_TIMEOUT_MASK);
> >>> +
> >>> +	nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, irq_enable);
> >>> +}
> >>> +
> >>> +static int nwl_dsi_host_attach(struct mipi_dsi_host *dsi_host,
> >>> +			       struct mipi_dsi_device *device)
> >>> +{
> >>> +	struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host);
> >>> +	struct device *dev = dsi->dev;
> >>> +	struct drm_bridge *bridge;
> >>> +	struct drm_panel *panel;
> >>> +	int ret;
> >>> +
> >>> +	DRM_DEV_INFO(dev, "lanes=%u, format=0x%x flags=0x%lx\n", device->lanes,
> >>> +		     device->format, device->mode_flags);
> >>> +
> >>> +	if (device->lanes < 1 || device->lanes > 4)
> >>> +		return -EINVAL;
> >>> +
> >>> +	dsi->lanes = device->lanes;
> >>> +	dsi->format = device->format;
> >>> +	dsi->dsi_mode_flags = device->mode_flags;
> >>> +
> >>> +	ret = drm_of_find_panel_or_bridge(dsi->dev->of_node, 1, 0, &panel,
> >>> +					  &bridge);
> >>> +	if (ret)
> >>> +		return ret;
> >>> +
> >>> +	if (panel) {
> >>> +		bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DSI);
> >>> +		if (IS_ERR(bridge))
> >>> +			return PTR_ERR(bridge);
> >>> +	}
> >>> +
> >>> +	dsi->panel_bridge = bridge;
> >>> +	drm_bridge_add(&dsi->bridge);
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int nwl_dsi_host_detach(struct mipi_dsi_host *dsi_host,
> >>> +			       struct mipi_dsi_device *device)
> >>> +{
> >>> +	struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host);
> >>> +
> >>> +	drm_of_panel_bridge_remove(dsi->dev->of_node, 1, 0);
> >>> +	drm_bridge_remove(&dsi->bridge);
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static bool nwl_dsi_read_packet(struct nwl_dsi *dsi, u32 status)
> >>> +{
> >>> +	struct device *dev = dsi->dev;
> >>> +	struct nwl_dsi_transfer *xfer = dsi->xfer;
> >>> +	u8 *payload = xfer->msg->rx_buf;
> >>> +	u32 val;
> >>> +	u16 word_count;
> >>> +	u8 channel;
> >>> +	u8 data_type;
> >>> +
> >>> +	xfer->status = 0;
> >>> +
> >>> +	if (xfer->rx_word_count == 0) {
> >>> +		if (!(status & NWL_DSI_RX_PKT_HDR_RCVD))
> >>> +			return false;
> >>> +		/* Get the RX header and parse it */
> >>> +		val = nwl_dsi_read(dsi, NWL_DSI_RX_PKT_HEADER);
> >>> +		word_count = NWL_DSI_WC(val);
> >>> +		channel = NWL_DSI_RX_VC(val);
> >>> +		data_type = NWL_DSI_RX_DT(val);
> >>> +
> >>> +		if (channel != xfer->msg->channel) {
> >>> +			DRM_DEV_ERROR(dev,
> >>> +				      "[%02X] Channel mismatch (%u != %u)\n",
> >>> +				      xfer->cmd, channel, xfer->msg->channel);
> >>> +			return true;
> >>> +		}
> >>> +
> >>> +		switch (data_type) {
> >>> +		case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
> >>> +			/* Fall through */
> >>> +		case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
> >>> +			if (xfer->msg->rx_len > 1) {
> >>> +				/* read second byte */
> >>> +				payload[1] = word_count >> 8;
> >>> +				++xfer->rx_len;
> >>> +			}
> >>> +			/* Fall through */
> >>> +		case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
> >>> +			/* Fall through */
> >>> +		case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
> >>> +			if (xfer->msg->rx_len > 0) {
> >>> +				/* read first byte */
> >>> +				payload[0] = word_count & 0xff;
> >>> +				++xfer->rx_len;
> >>> +			}
> >>> +			xfer->status = xfer->rx_len;
> >>> +			return true;
> >>> +		case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
> >>> +			word_count &= 0xff;
> >>> +			DRM_DEV_ERROR(dev, "[%02X] DSI error report: 0x%02x\n",
> >>> +				      xfer->cmd, word_count);
> >>> +			xfer->status = -EPROTO;
> >>> +			return true;
> >>> +		}
> >>> +
> >>> +		if (word_count > xfer->msg->rx_len) {
> >>> +			DRM_DEV_ERROR(
> >>> +				dev,
> >>> +				"[%02X] Receive buffer too small: %zu (< %u)\n",
> >>> +				xfer->cmd, xfer->msg->rx_len, word_count);
> >>> +			return true;
> >>> +		}
> >>> +
> >>> +		xfer->rx_word_count = word_count;
> >>> +	} else {
> >>> +		/* Set word_count from previous header read */
> >>> +		word_count = xfer->rx_word_count;
> >>> +	}
> >>> +
> >>> +	/* If RX payload is not yet received, wait for it */
> >>> +	if (!(status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD))
> >>> +		return false;
> >>> +
> >>> +	/* Read the RX payload */
> >>> +	while (word_count >= 4) {
> >>> +		val = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD);
> >>> +		payload[0] = (val >> 0) & 0xff;
> >>> +		payload[1] = (val >> 8) & 0xff;
> >>> +		payload[2] = (val >> 16) & 0xff;
> >>> +		payload[3] = (val >> 24) & 0xff;
> >>> +		payload += 4;
> >>> +		xfer->rx_len += 4;
> >>> +		word_count -= 4;
> >>> +	}
> >>> +
> >>> +	if (word_count > 0) {
> >>> +		val = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD);
> >>> +		switch (word_count) {
> >>> +		case 3:
> >>> +			payload[2] = (val >> 16) & 0xff;
> >>> +			++xfer->rx_len;
> >>> +			/* Fall through */
> >>> +		case 2:
> >>> +			payload[1] = (val >> 8) & 0xff;
> >>> +			++xfer->rx_len;
> >>> +			/* Fall through */
> >>> +		case 1:
> >>> +			payload[0] = (val >> 0) & 0xff;
> >>> +			++xfer->rx_len;
> >>> +			break;
> >>> +		}
> >>> +	}
> >>> +
> >>> +	xfer->status = xfer->rx_len;
> >>> +
> >>> +	return true;
> >>> +}
> >>> +
> >>> +static void nwl_dsi_finish_transmission(struct nwl_dsi *dsi, u32 status)
> >>> +{
> >>> +	struct nwl_dsi_transfer *xfer = dsi->xfer;
> >>> +	bool end_packet = false;
> >>> +
> >>> +	if (!xfer)
> >>> +		return;
> >>> +
> >>> +	if (xfer->direction == DSI_PACKET_SEND &&
> >>> +	    status & NWL_DSI_TX_PKT_DONE) {
> >>> +		xfer->status = xfer->tx_len;
> >>> +		end_packet = true;
> >>> +	} else if (status & NWL_DSI_DPHY_DIRECTION &&
> >>> +		   ((status & (NWL_DSI_RX_PKT_HDR_RCVD |
> >>> +			       NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)))) {
> >>> +		end_packet = nwl_dsi_read_packet(dsi, status);
> >>> +	}
> >>> +
> >>> +	if (end_packet)
> >>> +		complete(&xfer->completed);
> >>> +}
> >>> +
> >>> +static void nwl_dsi_begin_transmission(struct nwl_dsi *dsi)
> >>> +{
> >>> +	struct nwl_dsi_transfer *xfer = dsi->xfer;
> >>> +	struct mipi_dsi_packet *pkt = &xfer->packet;
> >>> +	const u8 *payload;
> >>> +	size_t length;
> >>> +	u16 word_count;
> >>> +	u8 hs_mode;
> >>> +	u32 val;
> >>> +	u32 hs_workaround = 0;
> >>> +
> >>> +	/* Send the payload, if any */
> >>> +	length = pkt->payload_length;
> >>> +	payload = pkt->payload;
> >>> +
> >>> +	while (length >= 4) {
> >>> +		val = *(u32 *)payload;
> >>> +		hs_workaround |= !(val & 0xFFFF00);
> >>> +		nwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val);
> >>> +		payload += 4;
> >>> +		length -= 4;
> >>> +	}
> >>> +	/* Send the rest of the payload */
> >>> +	val = 0;
> >>> +	switch (length) {
> >>> +	case 3:
> >>> +		val |= payload[2] << 16;
> >>> +		/* Fall through */
> >>> +	case 2:
> >>> +		val |= payload[1] << 8;
> >>> +		hs_workaround |= !(val & 0xFFFF00);
> >>> +		/* Fall through */
> >>> +	case 1:
> >>> +		val |= payload[0];
> >>> +		nwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val);
> >>> +		break;
> >>> +	}
> >>> +	xfer->tx_len = pkt->payload_length;
> >>> +
> >>> +	/*
> >>> +	 * Send the header
> >>> +	 * header[0] = Virtual Channel + Data Type
> >>> +	 * header[1] = Word Count LSB (LP) or first param (SP)
> >>> +	 * header[2] = Word Count MSB (LP) or second param (SP)
> >>> +	 */
> >>> +	word_count = pkt->header[1] | (pkt->header[2] << 8);
> >>> +	if (hs_workaround && (dsi->quirks & E11418_HS_MODE_QUIRK)) {
> >>> +		DRM_DEV_DEBUG_DRIVER(dsi->dev,
> >>> +				     "Using hs mode workaround for cmd 0x%x\n",
> >>> +				     xfer->cmd);
> >>> +		hs_mode = 1;
> >>> +	} else {
> >>> +		hs_mode = (xfer->msg->flags & MIPI_DSI_MSG_USE_LPM) ? 0 : 1;
> >>> +	}
> >>> +	val = NWL_DSI_WC(word_count) | NWL_DSI_TX_VC(xfer->msg->channel) |
> >>> +	      NWL_DSI_TX_DT(xfer->msg->type) | NWL_DSI_HS_SEL(hs_mode) |
> >>> +	      NWL_DSI_BTA_TX(xfer->need_bta);
> >>> +	nwl_dsi_write(dsi, NWL_DSI_PKT_CONTROL, val);
> >>> +
> >>> +	/* Send packet command */
> >>> +	nwl_dsi_write(dsi, NWL_DSI_SEND_PACKET, 0x1);
> >>> +}
> >>> +
> >>> +static ssize_t nwl_dsi_host_transfer(struct mipi_dsi_host *dsi_host,
> >>> +				     const struct mipi_dsi_msg *msg)
> >>> +{
> >>> +	struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host);
> >>> +	struct nwl_dsi_transfer xfer;
> >>> +	ssize_t ret = 0;
> >>> +
> >>> +	/* Create packet to be sent */
> >>> +	dsi->xfer = &xfer;
> >>> +	ret = mipi_dsi_create_packet(&xfer.packet, msg);
> >>> +	if (ret < 0) {
> >>> +		dsi->xfer = NULL;
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	if ((msg->type & MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM ||
> >>> +	     msg->type & MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM ||
> >>> +	     msg->type & MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM ||
> >>> +	     msg->type & MIPI_DSI_DCS_READ) &&
> >>> +	    msg->rx_len > 0 && msg->rx_buf != NULL)
> >>> +		xfer.direction = DSI_PACKET_RECEIVE;
> >>> +	else
> >>> +		xfer.direction = DSI_PACKET_SEND;
> >>> +
> >>> +	xfer.need_bta = (xfer.direction == DSI_PACKET_RECEIVE);
> >>> +	xfer.need_bta |= (msg->flags & MIPI_DSI_MSG_REQ_ACK) ? 1 : 0;
> >>> +	xfer.msg = msg;
> >>> +	xfer.status = -ETIMEDOUT;
> >>> +	xfer.rx_word_count = 0;
> >>> +	xfer.rx_len = 0;
> >>> +	xfer.cmd = 0x00;
> >>> +	if (msg->tx_len > 0)
> >>> +		xfer.cmd = ((u8 *)(msg->tx_buf))[0];
> >>> +	init_completion(&xfer.completed);
> >>> +
> >>> +	ret = clk_prepare_enable(dsi->rx_esc_clk);
> >>> +	if (ret < 0) {
> >>> +		DRM_DEV_ERROR(dsi->dev, "Failed to enable rx_esc clk: %zd\n",
> >>> +			      ret);
> >>> +		return ret;
> >>> +	}
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled rx_esc clk @%lu Hz\n",
> >>> +			     clk_get_rate(dsi->rx_esc_clk));
> >>> +
> >>> +	/* Initiate the DSI packet transmision */
> >>> +	nwl_dsi_begin_transmission(dsi);
> >>> +
> >>> +	if (!wait_for_completion_timeout(&xfer.completed,
> >>> +					 NWL_DSI_MIPI_FIFO_TIMEOUT)) {
> >>> +		DRM_DEV_ERROR(dsi_host->dev, "[%02X] DSI transfer timed out\n",
> >>> +			      xfer.cmd);
> >>> +		ret = -ETIMEDOUT;
> >>> +	} else {
> >>> +		ret = xfer.status;
> >>> +	}
> >>> +
> >>> +	clk_disable_unprepare(dsi->rx_esc_clk);
> >>> +
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +const struct mipi_dsi_host_ops nwl_dsi_host_ops = {
> >>> +	.attach = nwl_dsi_host_attach,
> >>> +	.detach = nwl_dsi_host_detach,
> >>> +	.transfer = nwl_dsi_host_transfer,
> >>> +};
> >>> +
> >>> +irqreturn_t nwl_dsi_irq_handler(int irq, void *data)
> >>> +{
> >>> +	u32 irq_status;
> >>> +	struct nwl_dsi *dsi = data;
> >>> +
> >>> +	irq_status = nwl_dsi_read(dsi, NWL_DSI_IRQ_STATUS);
> >>> +
> >>> +	if (irq_status & NWL_DSI_TX_FIFO_OVFLW)
> >>> +		DRM_DEV_ERROR_RATELIMITED(dsi->dev, "tx fifo overflow\n");
> >>> +
> >>> +	if (irq_status & NWL_DSI_HS_TX_TIMEOUT)
> >>> +		DRM_DEV_ERROR_RATELIMITED(dsi->dev, "HS tx timeout\n");
> >>> +
> >>> +	if (irq_status & NWL_DSI_TX_PKT_DONE ||
> >>> +	    irq_status & NWL_DSI_RX_PKT_HDR_RCVD ||
> >>> +	    irq_status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)
> >>> +		nwl_dsi_finish_transmission(dsi, irq_status);
> >>> +
> >>> +	return IRQ_HANDLED;
> >>> +}
> >>> +
> >>> +int nwl_dsi_enable(struct nwl_dsi *dsi)
> >>> +{
> >>> +	struct device *dev = dsi->dev;
> >>> +	union phy_configure_opts *phy_cfg = &dsi->phy_cfg;
> >>> +	int ret;
> >>> +
> >>> +	if (!dsi->lanes) {
> >>> +		DRM_DEV_ERROR(dev, "Need DSI lanes: %d\n", dsi->lanes);
> >>> +		return -EINVAL;
> >>> +	}
> >>> +
> >>> +	ret = phy_init(dsi->phy);
> >>> +	if (ret < 0) {
> >>> +		DRM_DEV_ERROR(dev, "Failed to init DSI phy: %d\n", ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	ret = phy_configure(dsi->phy, phy_cfg);
> >>> +	if (ret < 0) {
> >>> +		DRM_DEV_ERROR(dev, "Failed to configure DSI phy: %d\n", ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	ret = clk_prepare_enable(dsi->tx_esc_clk);
> >>> +	if (ret < 0) {
> >>> +		DRM_DEV_ERROR(dsi->dev, "Failed to enable tx_esc clk: %d\n",
> >>> +			      ret);
> >>> +		return ret;
> >>> +	}
> >>> +	DRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled tx_esc clk @%lu Hz\n",
> >>> +			     clk_get_rate(dsi->tx_esc_clk));
> >>> +
> >>> +	ret = nwl_dsi_config_host(dsi);
> >>> +	if (ret < 0) {
> >>> +		DRM_DEV_ERROR(dev, "Failed to set up DSI: %d", ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	ret = nwl_dsi_config_dpi(dsi);
> >>> +	if (ret < 0) {
> >>> +		DRM_DEV_ERROR(dev, "Failed to set up DPI: %d", ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	ret = phy_power_on(dsi->phy);
> >>> +	if (ret < 0) {
> >>> +		DRM_DEV_ERROR(dev, "Failed to power on DPHY (%d)\n", ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	nwl_dsi_init_interrupts(dsi);
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +int nwl_dsi_disable(struct nwl_dsi *dsi)
> >>> +{
> >>> +	struct device *dev = dsi->dev;
> >>> +
> >>> +	DRM_DEV_DEBUG_DRIVER(dev, "Disabling clocks and phy\n");
> >>> +
> >>> +	phy_power_off(dsi->phy);
> >>> +	phy_exit(dsi->phy);
> >>> +
> >>> +	/* Disabling the clock before the phy breaks enabling dsi again */
> >>> +	clk_disable_unprepare(dsi->tx_esc_clk);
> >>> +
> >>> +	return 0;
> >>> +}
> >>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
> >>> new file mode 100644
> >>> index 000000000000..579b366de652
> >>> --- /dev/null
> >>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
> >>> @@ -0,0 +1,112 @@
> >>> +/* SPDX-License-Identifier: GPL-2.0+ */
> >>> +/*
> >>> + * NWL MIPI DSI host driver
> >>> + *
> >>> + * Copyright (C) 2017 NXP
> >>> + * Copyright (C) 2019 Purism SPC
> >>> + */
> >>> +#ifndef __NWL_DSI_H__
> >>> +#define __NWL_DSI_H__
> >>> +
> >>> +#include <linux/irqreturn.h>
> >>> +
> >>> +#include <drm/drm_mipi_dsi.h>
> >>> +
> >>> +#include "nwl-drv.h"
> >>> +
> >>> +/* DSI HOST registers */
> >>> +#define NWL_DSI_CFG_NUM_LANES			0x0
> >>> +#define NWL_DSI_CFG_NONCONTINUOUS_CLK		0x4
> >>> +#define NWL_DSI_CFG_T_PRE			0x8
> >>> +#define NWL_DSI_CFG_T_POST			0xc
> >>> +#define NWL_DSI_CFG_TX_GAP			0x10
> >>> +#define NWL_DSI_CFG_AUTOINSERT_EOTP		0x14
> >>> +#define NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTP	0x18
> >>> +#define NWL_DSI_CFG_HTX_TO_COUNT		0x1c
> >>> +#define NWL_DSI_CFG_LRX_H_TO_COUNT		0x20
> >>> +#define NWL_DSI_CFG_BTA_H_TO_COUNT		0x24
> >>> +#define NWL_DSI_CFG_TWAKEUP			0x28
> >>> +#define NWL_DSI_CFG_STATUS_OUT			0x2c
> >>> +#define NWL_DSI_RX_ERROR_STATUS			0x30
> >>> +
> >>> +/* DSI DPI registers */
> >>> +#define NWL_DSI_PIXEL_PAYLOAD_SIZE		0x200
> >>> +#define NWL_DSI_PIXEL_FIFO_SEND_LEVEL		0x204
> >>> +#define NWL_DSI_INTERFACE_COLOR_CODING		0x208
> >>> +#define NWL_DSI_PIXEL_FORMAT			0x20c
> >>> +#define NWL_DSI_VSYNC_POLARITY			0x210
> >>> +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_LOW	0
> >>> +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_HIGH	BIT(1)
> >>> +
> >>> +#define NWL_DSI_HSYNC_POLARITY			0x214
> >>> +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_LOW	0
> >>> +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_HIGH	BIT(1)
> >>> +
> >>> +#define NWL_DSI_VIDEO_MODE			0x218
> >>> +#define NWL_DSI_HFP				0x21c
> >>> +#define NWL_DSI_HBP				0x220
> >>> +#define NWL_DSI_HSA				0x224
> >>> +#define NWL_DSI_ENABLE_MULT_PKTS		0x228
> >>> +#define NWL_DSI_VBP				0x22c
> >>> +#define NWL_DSI_VFP				0x230
> >>> +#define NWL_DSI_BLLP_MODE			0x234
> >>> +#define NWL_DSI_USE_NULL_PKT_BLLP		0x238
> >>> +#define NWL_DSI_VACTIVE				0x23c
> >>> +#define NWL_DSI_VC				0x240
> >>> +
> >>> +/* DSI APB PKT control */
> >>> +#define NWL_DSI_TX_PAYLOAD			0x280
> >>> +#define NWL_DSI_PKT_CONTROL			0x284
> >>> +#define NWL_DSI_SEND_PACKET			0x288
> >>> +#define NWL_DSI_PKT_STATUS			0x28c
> >>> +#define NWL_DSI_PKT_FIFO_WR_LEVEL		0x290
> >>> +#define NWL_DSI_PKT_FIFO_RD_LEVEL		0x294
> >>> +#define NWL_DSI_RX_PAYLOAD			0x298
> >>> +#define NWL_DSI_RX_PKT_HEADER			0x29c
> >>> +
> >>> +/* DSI IRQ handling */
> >>> +#define NWL_DSI_IRQ_STATUS			0x2a0
> >>> +#define NWL_DSI_SM_NOT_IDLE			BIT(0)
> >>> +#define NWL_DSI_TX_PKT_DONE			BIT(1)
> >>> +#define NWL_DSI_DPHY_DIRECTION			BIT(2)
> >>> +#define NWL_DSI_TX_FIFO_OVFLW			BIT(3)
> >>> +#define NWL_DSI_TX_FIFO_UDFLW			BIT(4)
> >>> +#define NWL_DSI_RX_FIFO_OVFLW			BIT(5)
> >>> +#define NWL_DSI_RX_FIFO_UDFLW			BIT(6)
> >>> +#define NWL_DSI_RX_PKT_HDR_RCVD			BIT(7)
> >>> +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD	BIT(8)
> >>> +#define NWL_DSI_BTA_TIMEOUT			BIT(29)
> >>> +#define NWL_DSI_LP_RX_TIMEOUT			BIT(30)
> >>> +#define NWL_DSI_HS_TX_TIMEOUT			BIT(31)
> >>> +
> >>> +#define NWL_DSI_IRQ_STATUS2			0x2a4
> >>> +#define NWL_DSI_SINGLE_BIT_ECC_ERR		BIT(0)
> >>> +#define NWL_DSI_MULTI_BIT_ECC_ERR		BIT(1)
> >>> +#define NWL_DSI_CRC_ERR				BIT(2)
> >>> +
> >>> +#define NWL_DSI_IRQ_MASK			0x2a8
> >>> +#define NWL_DSI_SM_NOT_IDLE_MASK		BIT(0)
> >>> +#define NWL_DSI_TX_PKT_DONE_MASK		BIT(1)
> >>> +#define NWL_DSI_DPHY_DIRECTION_MASK		BIT(2)
> >>> +#define NWL_DSI_TX_FIFO_OVFLW_MASK		BIT(3)
> >>> +#define NWL_DSI_TX_FIFO_UDFLW_MASK		BIT(4)
> >>> +#define NWL_DSI_RX_FIFO_OVFLW_MASK		BIT(5)
> >>> +#define NWL_DSI_RX_FIFO_UDFLW_MASK		BIT(6)
> >>> +#define NWL_DSI_RX_PKT_HDR_RCVD_MASK		BIT(7)
> >>> +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD_MASK	BIT(8)
> >>> +#define NWL_DSI_BTA_TIMEOUT_MASK		BIT(29)
> >>> +#define NWL_DSI_LP_RX_TIMEOUT_MASK		BIT(30)
> >>> +#define NWL_DSI_HS_TX_TIMEOUT_MASK		BIT(31)
> >>> +
> >>> +#define NWL_DSI_IRQ_MASK2			0x2ac
> >>> +#define NWL_DSI_SINGLE_BIT_ECC_ERR_MASK		BIT(0)
> >>> +#define NWL_DSI_MULTI_BIT_ECC_ERR_MASK		BIT(1)
> >>> +#define NWL_DSI_CRC_ERR_MASK			BIT(2)
> >>> +
> >>> +extern const struct mipi_dsi_host_ops nwl_dsi_host_ops;
> >>> +
> >>> +irqreturn_t nwl_dsi_irq_handler(int irq, void *data);
> >>> +int nwl_dsi_enable(struct nwl_dsi *dsi);
> >>> +int nwl_dsi_disable(struct nwl_dsi *dsi);
> >>> +
> >>> +#endif /* __NWL_DSI_H__ */
> >>
> >
> 

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 5/8] PM / devfreq: Introduce devfreq_get_freq_range
From: Matthias Kaehlcke @ 2019-09-19 18:07 UTC (permalink / raw)
  To: Leonard Crestez
  Cc: Artur Świgoń, Abel Vesa, Saravana Kannan, linux-pm,
	Viresh Kumar, Krzysztof Kozlowski, Chanwoo Choi, Kyungmin Park,
	MyungJoo Ham, Alexandre Bailon, Georgi Djakov, linux-arm-kernel,
	Jacky Bai
In-Reply-To: <730613d6f7182c6a6784fd509d6324f28be2cac3.1568764439.git.leonard.crestez@nxp.com>

Hi Leonard,

On Wed, Sep 18, 2019 at 03:18:24AM +0300, Leonard Crestez wrote:
> Moving handling of min/max freq to a single function and call it from
> update_devfreq and for printing min/max freq values in sysfs.
> 
> This changes the behavior of out-of-range min_freq/max_freq: clamping
> is now done at evaluation time. This means that if an out-of-range
> constraint is imposed by sysfs and it later becomes valid then it will
> be enforced.
> 
> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> ---
>  drivers/devfreq/devfreq.c | 111 +++++++++++++++++++++-----------------
>  1 file changed, 63 insertions(+), 48 deletions(-)
> 
> diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
> index 860cbbab476c..51a4179e2c69 100644
> --- a/drivers/devfreq/devfreq.c
> +++ b/drivers/devfreq/devfreq.c
> @@ -24,10 +24,12 @@
>  #include <linux/printk.h>
>  #include <linux/hrtimer.h>
>  #include <linux/of.h>
>  #include "governor.h"
>  
> +#define HZ_PER_KHZ 1000
> +
>  #define CREATE_TRACE_POINTS
>  #include <trace/events/devfreq.h>
>  
>  static struct class *devfreq_class;
>  
> @@ -96,10 +98,50 @@ static unsigned long find_available_max_freq(struct devfreq *devfreq)
>  		dev_pm_opp_put(opp);
>  
>  	return max_freq;
>  }
>  
> +/**
> + * devfreq_get_freq_range() - Get the current freq range
> + * @devfreq:	the devfreq instance
> + * @min_freq:	the min frequency
> + * @max_freq:	the max frequency
> + *
> + * This takes into consideration all constraints.
> + */
> +static void devfreq_get_freq_range(struct devfreq *devfreq,
> +				   unsigned long *min_freq,
> +				   unsigned long *max_freq)
> +{
> +	unsigned long *freq_table = devfreq->profile->freq_table;
> +
> +	lockdep_assert_held(&devfreq->lock);
> +
> +	/* Init min/max frequency from freq table */
> +	if (freq_table[0] < freq_table[devfreq->profile->max_state - 1]) {
> +		*min_freq = freq_table[0];
> +		*max_freq = freq_table[devfreq->profile->max_state - 1];
> +	} else {
> +		*min_freq = freq_table[devfreq->profile->max_state - 1];
> +		*max_freq = freq_table[0];
> +	}
> +
> +	/* constraints from sysfs: */
> +	*min_freq = max(*min_freq, devfreq->min_freq);
> +	*max_freq = min(*max_freq, devfreq->max_freq);
> +
> +	/* constraints from opp interface: */

nit: OPP

> +	*min_freq = max(*min_freq, devfreq->scaling_min_freq);
> +	/* scaling_max_freq can be zero on error */
> +	if (devfreq->scaling_max_freq)
> +		*max_freq = min(*max_freq, devfreq->scaling_max_freq);
> +
> +	/* max_freq takes precedence over min_freq */
> +	if (*min_freq > *max_freq)
> +		*min_freq = *max_freq;
> +}
> +
>  /**
>   * devfreq_get_freq_level() - Lookup freq_table for the frequency
>   * @devfreq:	the devfreq instance
>   * @freq:	the target frequency
>   */
> @@ -349,21 +391,13 @@ int update_devfreq(struct devfreq *devfreq)
>  
>  	/* Reevaluate the proper frequency */
>  	err = devfreq->governor->get_target_freq(devfreq, &freq);
>  	if (err)
>  		return err;
> +	devfreq_get_freq_range(devfreq, &min_freq, &max_freq);
>  
> -	/*
> -	 * Adjust the frequency with user freq, QoS and available freq.
> -	 *
> -	 * List from the highest priority
> -	 * max_freq
> -	 * min_freq
> -	 */
> -	max_freq = min(devfreq->scaling_max_freq, devfreq->max_freq);
> -	min_freq = max(devfreq->scaling_min_freq, devfreq->min_freq);
> -
> +	/* max freq takes priority over min freq */
>  	if (freq < min_freq) {
>  		freq = min_freq;
>  		flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use GLB */
>  	}
>  	if (freq > max_freq) {
> @@ -1293,40 +1327,28 @@ static ssize_t min_freq_store(struct device *dev, struct device_attribute *attr,
>  	ret = sscanf(buf, "%lu", &value);
>  	if (ret != 1)
>  		return -EINVAL;
>  
>  	mutex_lock(&df->lock);
> -
> -	if (value) {
> -		if (value > df->max_freq) {
> -			ret = -EINVAL;
> -			goto unlock;
> -		}
> -	} else {
> -		unsigned long *freq_table = df->profile->freq_table;
> -
> -		/* Get minimum frequency according to sorting order */
> -		if (freq_table[0] < freq_table[df->profile->max_state - 1])
> -			value = freq_table[0];
> -		else
> -			value = freq_table[df->profile->max_state - 1];
> -	}
> -
>  	df->min_freq = value;
>  	update_devfreq(df);
> -	ret = count;
> -unlock:
>  	mutex_unlock(&df->lock);
> -	return ret;
> +
> +	return count;
>  }
>  
>  static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
>  			     char *buf)
>  {
>  	struct devfreq *df = to_devfreq(dev);
> +	unsigned long min_freq, max_freq;
>  
> -	return sprintf(buf, "%lu\n", max(df->scaling_min_freq, df->min_freq));
> +	mutex_lock(&df->lock);
> +	devfreq_get_freq_range(df, &min_freq, &max_freq);
> +	mutex_unlock(&df->lock);
> +
> +	return sprintf(buf, "%lu\n", min_freq);
>  }
>  
>  static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
>  			      const char *buf, size_t count)
>  {
> @@ -1338,40 +1360,33 @@ static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
>  	if (ret != 1)
>  		return -EINVAL;
>  
>  	mutex_lock(&df->lock);
>  
> -	if (value) {
> -		if (value < df->min_freq) {
> -			ret = -EINVAL;
> -			goto unlock;
> -		}
> -	} else {
> -		unsigned long *freq_table = df->profile->freq_table;
> -
> -		/* Get maximum frequency according to sorting order */
> -		if (freq_table[0] < freq_table[df->profile->max_state - 1])
> -			value = freq_table[df->profile->max_state - 1];
> -		else
> -			value = freq_table[0];
> -	}
> +	/* Interpret zero as "don't care" */
> +	if (!value)
> +		value = ULONG_MAX;
>  
>  	df->max_freq = value;
>  	update_devfreq(df);
> -	ret = count;
> -unlock:
>  	mutex_unlock(&df->lock);
> -	return ret;
> +
> +	return count;
>  }
>  static DEVICE_ATTR_RW(min_freq);
>  
>  static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr,
>  			     char *buf)
>  {
>  	struct devfreq *df = to_devfreq(dev);
> +	unsigned long min_freq, max_freq;
> +
> +	mutex_lock(&df->lock);
> +	devfreq_get_freq_range(df, &min_freq, &max_freq);
> +	mutex_unlock(&df->lock);
>  
> -	return sprintf(buf, "%lu\n", min(df->scaling_max_freq, df->max_freq));
> +	return sprintf(buf, "%lu\n", max_freq);
>  }
>  static DEVICE_ATTR_RW(max_freq);
>  
>  static ssize_t available_frequencies_show(struct device *d,
>  					  struct device_attribute *attr,

Nice, having the constraint evaluation in a single function makes it
easier to follow the code. Clamping userspace constraints at runtime
makes sense.

Reviewed-by: Matthias Kaehlcke <mka@chromium.org>

I plan to take a look at the rest of the series, but probably won't
find time for all of it before next week.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [linux-sunxi] [PATCH] clk: sunxi-ng: h6: Use sigma-delta modulation for audio PLL
From: Maxime Ripard @ 2019-09-19 18:18 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Jernej Skrabec, Stephen Boyd, Mike Turquette, linux-kernel,
	linux-sunxi, linux-clk, linux-arm-kernel
In-Reply-To: <CAGb2v65KQf_OX1sX9+4DAKKMKHP464cCZKjCRsn3LzTKRGLTcQ@mail.gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 3903 bytes --]

On Wed, Sep 18, 2019 at 01:46:34PM +0800, Chen-Yu Tsai wrote:
> On Wed, Sep 18, 2019 at 1:21 PM Jernej Škrabec <jernej.skrabec@siol.net> wrote:
> >
> > Dne torek, 17. september 2019 ob 08:54:08 CEST je Chen-Yu Tsai napisal(a):
> > > On Sat, Sep 14, 2019 at 9:51 PM Jernej Skrabec <jernej.skrabec@siol.net>
> > wrote:
> > > > Audio devices needs exact clock rates in order to correctly reproduce
> > > > the sound. Until now, only integer factors were used to configure H6
> > > > audio PLL which resulted in inexact rates. Fix that by adding support
> > > > for fractional factors using sigma-delta modulation look-up table. It
> > > > contains values for two most commonly used audio base frequencies.
> > > >
> > > > Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> > > > ---
> > > >
> > > >  drivers/clk/sunxi-ng/ccu-sun50i-h6.c | 21 +++++++++++++++------
> > > >  1 file changed, 15 insertions(+), 6 deletions(-)
> > > >
> > > > diff --git a/drivers/clk/sunxi-ng/ccu-sun50i-h6.c
> > > > b/drivers/clk/sunxi-ng/ccu-sun50i-h6.c index d89353a3cdec..ed6338d74474
> > > > 100644
> > > > --- a/drivers/clk/sunxi-ng/ccu-sun50i-h6.c
> > > > +++ b/drivers/clk/sunxi-ng/ccu-sun50i-h6.c
> > > > @@ -203,12 +203,21 @@ static struct ccu_nkmp pll_hsic_clk = {
> > > >
> > > >   * hardcode it to match with the clock names.
> > > >   */
> > > >
> > > >  #define SUN50I_H6_PLL_AUDIO_REG                0x078
> > > >
> > > > +
> > > > +static struct ccu_sdm_setting pll_audio_sdm_table[] = {
> > > > +       { .rate = 541900800, .pattern = 0xc001288d, .m = 1, .n = 22 },
> > > > +       { .rate = 589824000, .pattern = 0xc00126e9, .m = 1, .n = 24 },
> > > > +};
> > > > +
> > > >
> > > >  static struct ccu_nm pll_audio_base_clk = {
> > > >
> > > >         .enable         = BIT(31),
> > > >         .lock           = BIT(28),
> > > >         .n              = _SUNXI_CCU_MULT_MIN(8, 8, 12),
> > > >         .m              = _SUNXI_CCU_DIV(1, 1), /* input divider */
> > > >
> > > > +       .sdm            = _SUNXI_CCU_SDM(pll_audio_sdm_table,
> > > > +                                        BIT(24), 0x178, BIT(31)),
> > > >
> > > >         .common         = {
> > > >
> > > > +               .features       = CCU_FEATURE_SIGMA_DELTA_MOD,
> > > >
> > > >                 .reg            = 0x078,
> > > >                 .hw.init        = CLK_HW_INIT("pll-audio-base", "osc24M",
> > > >
> > > >                                               &ccu_nm_ops,
> > > >
> > > > @@ -753,12 +762,12 @@ static const struct clk_hw *clk_parent_pll_audio[] =
> > > > {>
> > > >  };
> > > >
> > > >  /*
> > > >
> > > > - * The divider of pll-audio is fixed to 8 now, as pll-audio-4x has a
> > > > - * fixed post-divider 2.
> > > > + * The divider of pll-audio is fixed to 24 for now, so 24576000 and
> > > > 22579200 + * rates can be set exactly in conjunction with sigma-delta
> > > > modulation.>
> > > >   */
> > > >
> > > >  static CLK_FIXED_FACTOR_HWS(pll_audio_clk, "pll-audio",
> > > >
> > > >                             clk_parent_pll_audio,
> > > >
> > > > -                           8, 1, CLK_SET_RATE_PARENT);
> > > > +                           24, 1, CLK_SET_RATE_PARENT);
> > > >
> > > >  static CLK_FIXED_FACTOR_HWS(pll_audio_2x_clk, "pll-audio-2x",
> > > >
> > > >                             clk_parent_pll_audio,
> > > >                             4, 1, CLK_SET_RATE_PARENT);
> > >
> > > You need to fix the factors for the other two outputs as well, since all
> > > three are derived from pll-audio-base.
> >
> > Fix how? pll-audio-2x and pll-audio-4x clocks have fixed divider in regards to
> > pll-audio-base, while pll-audio has not. Unless you mean changing their name?
>
> Argh... I got it wrong. It looks good actually.
>
> Acked-by: Chen-Yu Tsai <wens@csie.org>

Queued for 5.5, thanks

Maxime

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH v3 1/2] memory: samsung: exynos5422-dmc: Fix kfree() of devm-allocated memory and missing static
From: Krzysztof Kozlowski @ 2019-09-19 18:19 UTC (permalink / raw)
  To: Lukasz Luba
  Cc: mark.rutland, devicetree, willy.mh.wolff.ml, linux-samsung-soc,
	b.zolnierkie, linux-pm, linux-kernel, robh+dt, cw00.choi,
	kyungmin.park, kgene, myungjoo.ham, s.nawrocki, dan.carpenter,
	linux-arm-kernel, m.szyprowski
In-Reply-To: <20190919092641.4407-2-l.luba@partner.samsung.com>

On Thu, Sep 19, 2019 at 11:26:40AM +0200, Lukasz Luba wrote:
> Fix issues captured by static checkers: used kfree() and missing 'static'
> in the private function.
> 
> Fixes Smatch warning:
>     drivers/memory/samsung/exynos5422-dmc.c:272
>         exynos5_init_freq_table() warn: passing devm_ allocated variable to kfree. 'dmc->opp'
> 
> Fixes Sparse warning:
>     drivers/memory/samsung/exynos5422-dmc.c:736:1:
>         warning: symbol 'exynos5_dmc_align_init_freq' was not declared.
> 
> Reported-by: kbuild test robot <lkp@intel.com>
> Reported-by: Dan Carpenter <dan.carpenter@oracle.com>
> Reported-by: Krzysztof Kozlowski <krzk@kernel.org>
> Signed-off-by: Lukasz Luba <l.luba@partner.samsung.com>
> ---
>  drivers/memory/samsung/exynos5422-dmc.c | 6 ++----

Thanks, applied.

Best regards,
Krzysztof


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH v3 2/2] dt-bindings: ddr: Add bindings for Samsung LPDDR3 memories
From: Krzysztof Kozlowski @ 2019-09-19 18:20 UTC (permalink / raw)
  To: Lukasz Luba
  Cc: mark.rutland, devicetree, willy.mh.wolff.ml, linux-samsung-soc,
	b.zolnierkie, linux-pm, linux-kernel, robh+dt, cw00.choi,
	kyungmin.park, kgene, myungjoo.ham, s.nawrocki, dan.carpenter,
	linux-arm-kernel, m.szyprowski
In-Reply-To: <20190919092641.4407-3-l.luba@partner.samsung.com>

On Thu, Sep 19, 2019 at 11:26:41AM +0200, Lukasz Luba wrote:
> Add compatible for Samsung k3qf2f20db LPDDR3 memory bindings.
> Suggested to based on at25.txt compatible section.
> Introduce minor fixes in the old documentation.
> 
> Suggested-by: Krzysztof Kozlowski <krzk@kernel.org>
> Signed-off-by: Lukasz Luba <l.luba@partner.samsung.com>
> ---
>  Documentation/devicetree/bindings/ddr/lpddr3.txt | 10 +++++++---
>  1 file changed, 7 insertions(+), 3 deletions(-)

Thanks, applied.

Best regards,
Krzysztof


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 1/8] PM / devfreq: Lock devfreq in trans_stat_show
From: Leonard Crestez @ 2019-09-19 18:42 UTC (permalink / raw)
  To: Matthias Kaehlcke, MyungJoo Ham, Kyungmin Park, Chanwoo Choi
  Cc: Artur Świgoń, Abel Vesa, Saravana Kannan,
	linux-pm@vger.kernel.org, Viresh Kumar, Krzysztof Kozlowski,
	Alexandre Bailon, Georgi Djakov,
	linux-arm-kernel@lists.infradead.org, Jacky Bai
In-Reply-To: <20190918212836.GN133864@google.com>

On 19.09.2019 00:28, Matthias Kaehlcke wrote:
> Hi Leonard,
> 
> this series doesn't indicate the version, from the change history in
> the cover letter I suppose it is v5.

Sorry about that, I forgot --subject-prefix. It is indeed v5

> On Wed, Sep 18, 2019 at 03:18:20AM +0300, Leonard Crestez wrote:
>> There is no locking in this sysfs show function so stats printing can
>> race with a devfreq_update_status called as part of freq switching or
>> with initialization.
>>
>> Also add an assert in devfreq_update_status to make it clear that lock
>> must be held by caller.
> 
> This and some other patches look like generic improvements and not
> directly related to the series "PM / devfreq: Add dev_pm_qos
> support". If there are no dependencies I think it is usually better to
> send the improvements separately, it keeps the series more focussed
> and might reduce version churn. Just my POV though ;-)

The locking cleanups are required in order to initialize pm_qos request 
and notifiers without introducing lockdep warnings.

pm_qos calls notifiers under dev_pm_qos_mtx and those notifiers needs to 
take &devfreq->lock. This means initializing pm_qos notifiers and 
requests must be done outside &devfreq->lock which needs some cleanups 
in devfreq_add_device.

This particular patch is a more loosely related bugfix. Devfreq 
maintainers: would it help to post it separately?

>> @@ -1415,15 +1416,20 @@ static ssize_t trans_stat_show(struct device *dev,
>>   	struct devfreq *devfreq = to_devfreq(dev);
>>   	ssize_t len;
>>   	int i, j;
>>   	unsigned int max_state = devfreq->profile->max_state;
>>   
>> +	mutex_lock(&devfreq->lock);
>>   	if (!devfreq->stop_polling &&
>> -			devfreq_update_status(devfreq, devfreq->previous_freq))
>> -		return 0;
>> -	if (max_state == 0)
>> -		return sprintf(buf, "Not Supported.\n");
>> +			devfreq_update_status(devfreq, devfreq->previous_freq)) {
>> +		len = 0;
> 
> you could assign 'len' in the declaration instead, but it's just
> another option, it'ss fine as is
>> +		goto out;
>> +	}
>> +	if (max_state == 0) {
>> +		len = sprintf(buf, "Not Supported.\n");
>> +		goto out;
>> +	}
> 
> This leaves the general structure of the code as is, which is great,
> but since you are already touching this part you can consider to
> improve it: 'max_state' is constant after device creation, hence the
> check could be done at the beginning, which IMO would be clearer, it
> could also save an unnecessary devfreq_update_status() call and it
> wouldn't be necessary to hold the lock (one goto less).

Now that I look at this more closely &devfreq->lock only really needs to 
be held during the stats update, it can be released during sprintf.

--
Regards,
Leonard

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 3/8] PM / devfreq: Move more initialization before registration
From: Leonard Crestez @ 2019-09-19 18:52 UTC (permalink / raw)
  To: Matthias Kaehlcke, MyungJoo Ham, Kyungmin Park, Chanwoo Choi
  Cc: Artur Świgoń, Abel Vesa, Saravana Kannan,
	linux-pm@vger.kernel.org, Viresh Kumar, Krzysztof Kozlowski,
	Alexandre Bailon, Georgi Djakov,
	linux-arm-kernel@lists.infradead.org, Jacky Bai
In-Reply-To: <20190918232904.GP133864@google.com>

On 19.09.2019 02:29, Matthias Kaehlcke wrote:
> Hi Leonard,
> 
> On Wed, Sep 18, 2019 at 03:18:22AM +0300, Leonard Crestez wrote:
>> In general it is a better to initialize an object before making it
>> accessible externally (through device_register).
>>
>> This make it possible to avoid relying on locking a partially
>> initialized object.
>>
>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>> ---
>>   drivers/devfreq/devfreq.c | 38 ++++++++++++++++++++------------------
>>   1 file changed, 20 insertions(+), 18 deletions(-)
>>
>> diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
>> index a715f27f35fd..57a217fc92de 100644
>> --- a/drivers/devfreq/devfreq.c
>> +++ b/drivers/devfreq/devfreq.c
>> @@ -589,10 +589,12 @@ static void devfreq_dev_release(struct device *dev)
>>   
>>   	if (devfreq->profile->exit)
>>   		devfreq->profile->exit(devfreq->dev.parent);
>>   
>>   	mutex_destroy(&devfreq->lock);
>> +	kfree(devfreq->time_in_state);
>> +	kfree(devfreq->trans_table);
>>   	kfree(devfreq);
>>   }
>>   
>>   /**
>>    * devfreq_add_device() - Add devfreq feature to the device
>> @@ -671,44 +673,43 @@ struct devfreq *devfreq_add_device(struct device *dev,
>>   	devfreq->max_freq = devfreq->scaling_max_freq;
>>   
>>   	devfreq->suspend_freq = dev_pm_opp_get_suspend_opp_freq(dev);
>>   	atomic_set(&devfreq->suspend_count, 0);
>>   
>> -	dev_set_name(&devfreq->dev, "devfreq%d",
>> -				atomic_inc_return(&devfreq_no));
>> -	err = device_register(&devfreq->dev);
>> -	if (err) {
>> -		mutex_unlock(&devfreq->lock);
>> -		put_device(&devfreq->dev);
>> -		goto err_out;
>> -	}
>> -
>> -	devfreq->trans_table = devm_kzalloc(&devfreq->dev,
>> +	devfreq->trans_table = kzalloc(
>>   			array3_size(sizeof(unsigned int),
>>   				    devfreq->profile->max_state,
>>   				    devfreq->profile->max_state),
>>   			GFP_KERNEL);
>>   	if (!devfreq->trans_table) {
>>   		mutex_unlock(&devfreq->lock);
>>   		err = -ENOMEM;
>> -		goto err_devfreq;
>> +		goto err_dev;
>>   	}
>>   
>> -	devfreq->time_in_state = devm_kcalloc(&devfreq->dev,
>> -			devfreq->profile->max_state,
>> -			sizeof(unsigned long),
>> -			GFP_KERNEL);
>> +	devfreq->time_in_state = kcalloc(devfreq->profile->max_state,
>> +					 sizeof(unsigned long),
>> +					 GFP_KERNEL);
>>   	if (!devfreq->time_in_state) {
>>   		mutex_unlock(&devfreq->lock);
>>   		err = -ENOMEM;
>> -		goto err_devfreq;
>> +		goto err_dev;
>>   	}
>>   
>>   	devfreq->last_stat_updated = jiffies;
>>   
>>   	srcu_init_notifier_head(&devfreq->transition_notifier_list);
>>   
>> +	dev_set_name(&devfreq->dev, "devfreq%d",
>> +				atomic_inc_return(&devfreq_no));
>> +	err = device_register(&devfreq->dev);
>> +	if (err) {
>> +		mutex_unlock(&devfreq->lock);
>> +		put_device(&devfreq->dev);
>> +		goto err_out;
> 
>    		goto err_dev;
> 
>> +	}
>> +
>>   	mutex_unlock(&devfreq->lock);
>>   
>>   	mutex_lock(&devfreq_list_lock);
>>   
>>   	governor = try_then_request_governor(devfreq->governor_name);
>> @@ -734,14 +735,15 @@ struct devfreq *devfreq_add_device(struct device *dev,
>>   
>>   	return devfreq;
>>   
>>   err_init:
>>   	mutex_unlock(&devfreq_list_lock);
>> -err_devfreq:
>>   	devfreq_remove_device(devfreq);
>> -	devfreq = NULL;
>> +	return ERR_PTR(err);
> 
> The two return paths in the unwind part are unorthodox, but I
> see why they are needed. Maybe add an empty line between the two paths
> to make it a bit more evident that they are separate.

Old code did "devfreq = NULL" just so that the later kfree did nothing. 
There were already two unwind paths, it's just that the second one was 
less obvious. I will add a comment.

>>   err_dev:
> 
> This code path should include
> 
> 	mutex_destroy(&devfreq->lock);
> 
> That was already missing in the original code though.

Yes, that would be a separate patch.

> Actually with the later device registration the mutex could be
> initialized later and doesn't need to be held. This would
> obsolete the mutex_unlock() calls in the error paths
Next patch already removes mutex_lock before device_register (that's the 
purpose of the cleanup). If you're suggesting to move mutex_init around 
it's not clear what would be gained?

--
Regards,
Leonard

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: Aw: Re: [BUG] [PATCH v5 02/10] mfd: mt6397: extract irq related code from core driver
From: Frank Wunderlich @ 2019-09-19 18:59 UTC (permalink / raw)
  To: linux-mediatek, Hsin-hsiung Wang, Matthias Brugger
  Cc: Mark Rutland, Alessandro Zummo, Alexandre Belloni, srv_heupstream,
	devicetree, Greg Kroah-Hartman, Sean Wang, Liam Girdwood,
	Rob Herring, linux-kernel, Richard Fontana, Mark Brown,
	linux-arm-kernel, "René van Dorst", Thomas Gleixner,
	Eddie Huang, Lee Jones, Kate Stewart, linux-rtc
In-Reply-To: <1567059876.15320.3.camel@mtksdaap41>

Hi

When is new version ready? First 2 patches are still in next for 5.4 and i see no fix so i guess it is still broken.

Regards Frank

Am 29. August 2019 08:24:36 MESZ schrieb Hsin-hsiung Wang <hsin-hsiung.wang@mediatek.com>:
>Hi Frank/Matthias,
>
>Thanks for your comments.
>The root cause seems I didn't split the code well.
>I will fix it in the next version.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 6/8] PM / devfreq: Add dev_pm_qos support
From: Matthias Kaehlcke @ 2019-09-19 19:12 UTC (permalink / raw)
  To: Leonard Crestez
  Cc: Artur Świgoń, Abel Vesa, Saravana Kannan, linux-pm,
	Viresh Kumar, Krzysztof Kozlowski, Chanwoo Choi, Kyungmin Park,
	MyungJoo Ham, Alexandre Bailon, Georgi Djakov, linux-arm-kernel,
	Jacky Bai
In-Reply-To: <feab364d702ba62102f212b7d415d9f768159163.1568764439.git.leonard.crestez@nxp.com>

On Wed, Sep 18, 2019 at 03:18:25AM +0300, Leonard Crestez wrote:
> Register notifiers with the pm_qos framework in order to respond to
> requests for MIN_FREQUENCY and MAX_FREQUENCY.

To make it clear that this change on it's own is a NOP maybe add
something like "No constraints are added for now though.", as in
67d874c3b2c6 ("cpufreq: Register notifiers with the PM QoS framework")

> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> ---
>  drivers/devfreq/devfreq.c | 71 +++++++++++++++++++++++++++++++++++++++
>  include/linux/devfreq.h   |  5 +++
>  2 files changed, 76 insertions(+)
> 
> diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
> index 51a4179e2c69..d8d57318b12c 100644
> --- a/drivers/devfreq/devfreq.c
> +++ b/drivers/devfreq/devfreq.c
> @@ -22,17 +22,20 @@
>  #include <linux/platform_device.h>
>  #include <linux/list.h>
>  #include <linux/printk.h>
>  #include <linux/hrtimer.h>
>  #include <linux/of.h>
> +#include <linux/pm_qos.h>
>  #include "governor.h"
>  
>  #define HZ_PER_KHZ 1000
>  
>  #define CREATE_TRACE_POINTS
>  #include <trace/events/devfreq.h>
>  
> +#define HZ_PER_KHZ	1000
> +
>  static struct class *devfreq_class;
>  
>  /*
>   * devfreq core provides delayed work based load monitoring helper
>   * functions. Governors can use these or can implement their own
> @@ -123,10 +126,16 @@ static void devfreq_get_freq_range(struct devfreq *devfreq,
>  	} else {
>  		*min_freq = freq_table[devfreq->profile->max_state - 1];
>  		*max_freq = freq_table[0];
>  	}
>  
> +	/* constraints from dev_pm_qos: */

nit: QoS constraints?

> +	*min_freq = max(*min_freq, HZ_PER_KHZ * (unsigned long)dev_pm_qos_read_value(
> +				devfreq->dev.parent, DEV_PM_QOS_MIN_FREQUENCY));
> +	*max_freq = min(*max_freq, HZ_PER_KHZ * (unsigned long)dev_pm_qos_read_value(
> +				devfreq->dev.parent, DEV_PM_QOS_MAX_FREQUENCY));
> +
>  	/* constraints from sysfs: */
>  	*min_freq = max(*min_freq, devfreq->min_freq);
>  	*max_freq = min(*max_freq, devfreq->max_freq);
>  
>  	/* constraints from opp interface: */
> @@ -605,10 +614,49 @@ static int devfreq_notifier_call(struct notifier_block *nb, unsigned long type,
>  	mutex_unlock(&devfreq->lock);
>  
>  	return ret;
>  }
>  
> +/**
> + * devfreq_qos_notifier_call() - Common handler for qos freq changes.

nit: QoS

s/freq/constraint/ ?

> + * @devfreq:    the devfreq instance.
> + */
> +static int devfreq_qos_notifier_call(struct devfreq *devfreq)
> +{
> +	int ret;
> +
> +	mutex_lock(&devfreq->lock);
> +	ret = update_devfreq(devfreq);
> +	mutex_unlock(&devfreq->lock);
> +
> +	return ret;
> +}
> +
> +/**
> + * devfreq_qos_min_notifier_call() - Callback for qos min_freq changes.

nit: QoS

> + * @nb:		Should to be devfreq->nb_min

s/to//

> + */
> +static int devfreq_qos_min_notifier_call(struct notifier_block *nb,
> +					 unsigned long val, void *ptr)
> +{
> +	struct devfreq *devfreq = container_of(nb, struct devfreq, nb_min);
> +
> +	return devfreq_qos_notifier_call(devfreq);
> +}
> +
> +/**
> + * devfreq_qos_max_notifier_call() - Callback for qos min_freq changes.

nit: QoS

s/min/max/

> + * @nb:		Should to be devfreq->nb_max

s/to//

> + */
> +static int devfreq_qos_max_notifier_call(struct notifier_block *nb,
> +					 unsigned long val, void *ptr)
> +{
> +	struct devfreq *devfreq = container_of(nb, struct devfreq, nb_max);
> +
> +	return devfreq_qos_notifier_call(devfreq);
> +}
> +
>  /**
>   * devfreq_dev_release() - Callback for struct device to release the device.
>   * @dev:	the devfreq device
>   *
>   * Remove devfreq from the list and release its resources.
> @@ -619,10 +667,15 @@ static void devfreq_dev_release(struct device *dev)
>  
>  	mutex_lock(&devfreq_list_lock);
>  	list_del(&devfreq->node);
>  	mutex_unlock(&devfreq_list_lock);
>  
> +	dev_pm_qos_remove_notifier(devfreq->dev.parent, &devfreq->nb_max,
> +			DEV_PM_QOS_MAX_FREQUENCY);
> +	dev_pm_qos_remove_notifier(devfreq->dev.parent, &devfreq->nb_min,
> +			DEV_PM_QOS_MIN_FREQUENCY);
> +

mega-nit: removing 'max' then 'min' does things in reverse order as
during initialization, which is common practice. But since the order
here doesn't really matter I'd stick to the common min/max order,
might readers save a few milli-seconds wondering why 'max' comes first.

>  	if (devfreq->profile->exit)
>  		devfreq->profile->exit(devfreq->dev.parent);
>  
>  	mutex_destroy(&devfreq->lock);
>  	kfree(devfreq->time_in_state);
> @@ -732,10 +785,27 @@ struct devfreq *devfreq_add_device(struct device *dev,
>  	if (err) {
>  		put_device(&devfreq->dev);
>  		goto err_out;
>  	}
>  
> +	/*
> +	 * Register notifiers for updates to min_freq/max_freq after device is

nit: min/max_freq?

> +	 * initialized (and we can handle notifications) but before the governor
> +	 * is started (which should do an initial enforcement of constraints)
> +	 */
> +	devfreq->nb_min.notifier_call = devfreq_qos_min_notifier_call;
> +	err = dev_pm_qos_add_notifier(devfreq->dev.parent, &devfreq->nb_min,
> +				      DEV_PM_QOS_MIN_FREQUENCY);
> +	if (err)
> +		goto err_devfreq;

IIUC you rely on the notifiers being removed by
devfreq_dev_release(). Does dev_pm_qos_remove_notifier() behave
gracefully if the notifier is not initialized/added or do we need
to use BLOCKING_NOTIFIER_INIT() or similar?

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH] spi: atmel: Remove AVR32 leftover
From: Thomas Petazzoni @ 2019-09-19 19:13 UTC (permalink / raw)
  To: Alexandre Belloni
  Cc: Gregory CLEMENT, linux-kernel, Ludovic Desroches, Mark Brown,
	linux-spi, linux-arm-kernel
In-Reply-To: <20190919172453.GA21254@piout.net>

On Thu, 19 Sep 2019 19:24:53 +0200
Alexandre Belloni <alexandre.belloni@bootlin.com> wrote:

> On 19/09/2019 17:40:34+0200, Gregory CLEMENT wrote:
> > AV32 support has been from the kernel a few release ago, but there was  
> AVR32 and  missing word^
> 
> > still some specific macro for this architecture in this driver. Lets
> > remove it.

If you want to actually be pedantic, there are a few other typos in the
commit. Hopefully the below text has all of them fixed (and does not
introduce any new one):

==

AVR32 support has been removed from the kernel a few releases ago, but
there were still some specific macros for this architecture in this
driver. Let's remove them.

==

Best regards,

Thomas
-- 
Thomas Petazzoni, CTO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* [PATCH] clocksource: mediatek: fix error handling
From: Fabien Parent @ 2019-09-19 19:13 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, linux-mediatek
  Cc: matthias.bgg, Fabien Parent, tglx, daniel.lezcano

When timer_of_init fails, it cleans up after itself by undoing
everything it did during the initialization function.

mtk_syst_init and mtk_gpt_init both call timer_of_cleanup if
timer_of_init fails. timer_of_cleanup try to release the resource taken.
Since these resources have already been cleaned up by timer_of_init,
we end up getting a few warnings printed:

[    0.001935] WARNING: CPU: 0 PID: 0 at __clk_put+0xe8/0x128
[    0.002650] Modules linked in:
[    0.003058] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 4.19.67+ #1
[    0.003852] Hardware name: MediaTek MT8183 (DT)
[    0.004446] pstate: 20400085 (nzCv daIf +PAN -UAO)
[    0.005073] pc : __clk_put+0xe8/0x128
[    0.005555] lr : clk_put+0xc/0x14
[    0.005988] sp : ffffff80090b3ea0
[    0.006422] x29: ffffff80090b3ea0 x28: 0000000040e20018
[    0.007121] x27: ffffffc07bfff780 x26: 0000000000000001
[    0.007819] x25: ffffff80090bda80 x24: ffffff8008ec5828
[    0.008517] x23: ffffff80090bd000 x22: ffffff8008d8b2e8
[    0.009216] x21: 0000000000000001 x20: fffffffffffffdfb
[    0.009914] x19: ffffff8009166180 x18: 00000000002bffa8
[    0.010612] x17: ffffffc012996980 x16: 0000000000000000
[    0.011311] x15: ffffffbf004a6800 x14: 3536343038393334
[    0.012009] x13: 2079726576652073 x12: 7eb9c62c5c38f100
[    0.012707] x11: ffffff80090b3ba0 x10: ffffff80090b3ba0
[    0.013405] x9 : 0000000000000004 x8 : 0000000000000040
[    0.014103] x7 : ffffffc079400270 x6 : 0000000000000000
[    0.014801] x5 : ffffffc079400248 x4 : 0000000000000000
[    0.015499] x3 : 0000000000000000 x2 : 0000000000000000
[    0.016197] x1 : ffffff80091661c0 x0 : fffffffffffffdfb
[    0.016896] Call trace:
[    0.017218]  __clk_put+0xe8/0x128
[    0.017654]  clk_put+0xc/0x14
[    0.018048]  timer_of_cleanup+0x60/0x7c
[    0.018551]  mtk_syst_init+0x8c/0x9c
[    0.019020]  timer_probe+0x6c/0xe0
[    0.019469]  time_init+0x14/0x44
[    0.019893]  start_kernel+0x2d0/0x46c
[    0.020378] ---[ end trace 8c1efabea1267649 ]---
[    0.020982] ------------[ cut here ]------------
[    0.021586] Trying to vfree() nonexistent vm area ((____ptrval____))
[    0.022427] WARNING: CPU: 0 PID: 0 at __vunmap+0xd0/0xd8
[    0.023119] Modules linked in:
[    0.023524] CPU: 0 PID: 0 Comm: swapper/0 Tainted: G        W         4.19.67+ #1
[    0.024498] Hardware name: MediaTek MT8183 (DT)
[    0.025091] pstate: 60400085 (nZCv daIf +PAN -UAO)
[    0.025718] pc : __vunmap+0xd0/0xd8
[    0.026176] lr : __vunmap+0xd0/0xd8
[    0.026632] sp : ffffff80090b3e90
[    0.027066] x29: ffffff80090b3e90 x28: 0000000040e20018
[    0.027764] x27: ffffffc07bfff780 x26: 0000000000000001
[    0.028462] x25: ffffff80090bda80 x24: ffffff8008ec5828
[    0.029160] x23: ffffff80090bd000 x22: ffffff8008d8b2e8
[    0.029858] x21: 0000000000000000 x20: 0000000000000000
[    0.030556] x19: ffffff800800d000 x18: 00000000002bffa8
[    0.031254] x17: 0000000000000000 x16: 0000000000000000
[    0.031952] x15: ffffffbf004a6800 x14: 3536343038393334
[    0.032651] x13: 2079726576652073 x12: 7eb9c62c5c38f100
[    0.033349] x11: ffffff80090b3b40 x10: ffffff80090b3b40
[    0.034047] x9 : 0000000000000005 x8 : 5f5f6c6176727470
[    0.034745] x7 : 5f5f5f5f28282061 x6 : ffffff80091c86ef
[    0.035443] x5 : ffffff800852b690 x4 : 0000000000000000
[    0.036141] x3 : 0000000000000002 x2 : 0000000000000002
[    0.036839] x1 : 7eb9c62c5c38f100 x0 : 7eb9c62c5c38f100
[    0.037536] Call trace:
[    0.037859]  __vunmap+0xd0/0xd8
[    0.038271]  vunmap+0x24/0x30
[    0.038664]  __iounmap+0x2c/0x34
[    0.039088]  timer_of_cleanup+0x70/0x7c
[    0.039591]  mtk_syst_init+0x8c/0x9c
[    0.040060]  timer_probe+0x6c/0xe0
[    0.040507]  time_init+0x14/0x44
[    0.040932]  start_kernel+0x2d0/0x46c

This commit remove the calls to timer_of_cleanup when timer_of_init
fails since it is unnecessary and actually cause warnings to be printed.

Signed-off-by: Fabien Parent <fparent@baylibre.com>
---
 drivers/clocksource/timer-mediatek.c | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/drivers/clocksource/timer-mediatek.c b/drivers/clocksource/timer-mediatek.c
index a562f491b0f8..9318edcd8963 100644
--- a/drivers/clocksource/timer-mediatek.c
+++ b/drivers/clocksource/timer-mediatek.c
@@ -268,15 +268,12 @@ static int __init mtk_syst_init(struct device_node *node)
 
 	ret = timer_of_init(node, &to);
 	if (ret)
-		goto err;
+		return ret;
 
 	clockevents_config_and_register(&to.clkevt, timer_of_rate(&to),
 					TIMER_SYNC_TICKS, 0xffffffff);
 
 	return 0;
-err:
-	timer_of_cleanup(&to);
-	return ret;
 }
 
 static int __init mtk_gpt_init(struct device_node *node)
@@ -293,7 +290,7 @@ static int __init mtk_gpt_init(struct device_node *node)
 
 	ret = timer_of_init(node, &to);
 	if (ret)
-		goto err;
+		return ret;
 
 	/* Configure clock source */
 	mtk_gpt_setup(&to, TIMER_CLK_SRC, GPT_CTRL_OP_FREERUN);
@@ -311,9 +308,6 @@ static int __init mtk_gpt_init(struct device_node *node)
 	mtk_gpt_enable_irq(&to, TIMER_CLK_EVT);
 
 	return 0;
-err:
-	timer_of_cleanup(&to);
-	return ret;
 }
 TIMER_OF_DECLARE(mtk_mt6577, "mediatek,mt6577-timer", mtk_gpt_init);
 TIMER_OF_DECLARE(mtk_mt6765, "mediatek,mt6765-timer", mtk_syst_init);
-- 
2.23.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related

* Re: [PATCH 3/8] PM / devfreq: Move more initialization before registration
From: Matthias Kaehlcke @ 2019-09-19 19:16 UTC (permalink / raw)
  To: Leonard Crestez
  Cc: Artur Świgoń, Abel Vesa, Saravana Kannan,
	linux-pm@vger.kernel.org, Viresh Kumar, Krzysztof Kozlowski,
	Chanwoo Choi, Kyungmin Park, MyungJoo Ham, Alexandre Bailon,
	Georgi Djakov, linux-arm-kernel@lists.infradead.org, Jacky Bai
In-Reply-To: <VI1PR04MB702350FA21534747D540C04FEE890@VI1PR04MB7023.eurprd04.prod.outlook.com>

On Thu, Sep 19, 2019 at 06:52:07PM +0000, Leonard Crestez wrote:
> On 19.09.2019 02:29, Matthias Kaehlcke wrote:
> > Hi Leonard,
> > 
> > On Wed, Sep 18, 2019 at 03:18:22AM +0300, Leonard Crestez wrote:
> >> In general it is a better to initialize an object before making it
> >> accessible externally (through device_register).
> >>
> >> This make it possible to avoid relying on locking a partially
> >> initialized object.
> >>
> >> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> >> ---
> >>   drivers/devfreq/devfreq.c | 38 ++++++++++++++++++++------------------
> >>   1 file changed, 20 insertions(+), 18 deletions(-)
> >>
> >> diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
> >> index a715f27f35fd..57a217fc92de 100644
> >> --- a/drivers/devfreq/devfreq.c
> >> +++ b/drivers/devfreq/devfreq.c
> >> @@ -589,10 +589,12 @@ static void devfreq_dev_release(struct device *dev)
> >>   
> >>   	if (devfreq->profile->exit)
> >>   		devfreq->profile->exit(devfreq->dev.parent);
> >>   
> >>   	mutex_destroy(&devfreq->lock);
> >> +	kfree(devfreq->time_in_state);
> >> +	kfree(devfreq->trans_table);
> >>   	kfree(devfreq);
> >>   }
> >>   
> >>   /**
> >>    * devfreq_add_device() - Add devfreq feature to the device
> >> @@ -671,44 +673,43 @@ struct devfreq *devfreq_add_device(struct device *dev,
> >>   	devfreq->max_freq = devfreq->scaling_max_freq;
> >>   
> >>   	devfreq->suspend_freq = dev_pm_opp_get_suspend_opp_freq(dev);
> >>   	atomic_set(&devfreq->suspend_count, 0);
> >>   
> >> -	dev_set_name(&devfreq->dev, "devfreq%d",
> >> -				atomic_inc_return(&devfreq_no));
> >> -	err = device_register(&devfreq->dev);
> >> -	if (err) {
> >> -		mutex_unlock(&devfreq->lock);
> >> -		put_device(&devfreq->dev);
> >> -		goto err_out;
> >> -	}
> >> -
> >> -	devfreq->trans_table = devm_kzalloc(&devfreq->dev,
> >> +	devfreq->trans_table = kzalloc(
> >>   			array3_size(sizeof(unsigned int),
> >>   				    devfreq->profile->max_state,
> >>   				    devfreq->profile->max_state),
> >>   			GFP_KERNEL);
> >>   	if (!devfreq->trans_table) {
> >>   		mutex_unlock(&devfreq->lock);
> >>   		err = -ENOMEM;
> >> -		goto err_devfreq;
> >> +		goto err_dev;
> >>   	}
> >>   
> >> -	devfreq->time_in_state = devm_kcalloc(&devfreq->dev,
> >> -			devfreq->profile->max_state,
> >> -			sizeof(unsigned long),
> >> -			GFP_KERNEL);
> >> +	devfreq->time_in_state = kcalloc(devfreq->profile->max_state,
> >> +					 sizeof(unsigned long),
> >> +					 GFP_KERNEL);
> >>   	if (!devfreq->time_in_state) {
> >>   		mutex_unlock(&devfreq->lock);
> >>   		err = -ENOMEM;
> >> -		goto err_devfreq;
> >> +		goto err_dev;
> >>   	}
> >>   
> >>   	devfreq->last_stat_updated = jiffies;
> >>   
> >>   	srcu_init_notifier_head(&devfreq->transition_notifier_list);
> >>   
> >> +	dev_set_name(&devfreq->dev, "devfreq%d",
> >> +				atomic_inc_return(&devfreq_no));
> >> +	err = device_register(&devfreq->dev);
> >> +	if (err) {
> >> +		mutex_unlock(&devfreq->lock);
> >> +		put_device(&devfreq->dev);
> >> +		goto err_out;
> > 
> >    		goto err_dev;
> > 
> >> +	}
> >> +
> >>   	mutex_unlock(&devfreq->lock);
> >>   
> >>   	mutex_lock(&devfreq_list_lock);
> >>   
> >>   	governor = try_then_request_governor(devfreq->governor_name);
> >> @@ -734,14 +735,15 @@ struct devfreq *devfreq_add_device(struct device *dev,
> >>   
> >>   	return devfreq;
> >>   
> >>   err_init:
> >>   	mutex_unlock(&devfreq_list_lock);
> >> -err_devfreq:
> >>   	devfreq_remove_device(devfreq);
> >> -	devfreq = NULL;
> >> +	return ERR_PTR(err);
> > 
> > The two return paths in the unwind part are unorthodox, but I
> > see why they are needed. Maybe add an empty line between the two paths
> > to make it a bit more evident that they are separate.
> 
> Old code did "devfreq = NULL" just so that the later kfree did nothing. 
> There were already two unwind paths, it's just that the second one was 
> less obvious. I will add a comment.
> 
> >>   err_dev:
> > 
> > This code path should include
> > 
> > 	mutex_destroy(&devfreq->lock);
> > 
> > That was already missing in the original code though.
> 
> Yes, that would be a separate patch.
> 
> > Actually with the later device registration the mutex could be
> > initialized later and doesn't need to be held. This would
> > obsolete the mutex_unlock() calls in the error paths
> Next patch already removes mutex_lock before device_register (that's the 
> purpose of the cleanup). If you're suggesting to move mutex_init around 
> it's not clear what would be gained?

As per my earlier reply to self: I didn't look at the next patch
before writing this, it's all good, nothing to do here :)

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 1/8] PM / devfreq: Lock devfreq in trans_stat_show
From: Matthias Kaehlcke @ 2019-09-19 19:25 UTC (permalink / raw)
  To: Leonard Crestez
  Cc: Artur Świgoń, Abel Vesa, Saravana Kannan,
	linux-pm@vger.kernel.org, Viresh Kumar, Krzysztof Kozlowski,
	Chanwoo Choi, Kyungmin Park, MyungJoo Ham, Alexandre Bailon,
	Georgi Djakov, linux-arm-kernel@lists.infradead.org, Jacky Bai
In-Reply-To: <VI1PR04MB7023CAC70C08BF3963301A67EE890@VI1PR04MB7023.eurprd04.prod.outlook.com>

On Thu, Sep 19, 2019 at 06:42:22PM +0000, Leonard Crestez wrote:
> On 19.09.2019 00:28, Matthias Kaehlcke wrote:
> > Hi Leonard,
> > 
> > this series doesn't indicate the version, from the change history in
> > the cover letter I suppose it is v5.
> 
> Sorry about that, I forgot --subject-prefix. It is indeed v5
> 
> > On Wed, Sep 18, 2019 at 03:18:20AM +0300, Leonard Crestez wrote:
> >> There is no locking in this sysfs show function so stats printing can
> >> race with a devfreq_update_status called as part of freq switching or
> >> with initialization.
> >>
> >> Also add an assert in devfreq_update_status to make it clear that lock
> >> must be held by caller.
> > 
> > This and some other patches look like generic improvements and not
> > directly related to the series "PM / devfreq: Add dev_pm_qos
> > support". If there are no dependencies I think it is usually better to
> > send the improvements separately, it keeps the series more focussed
> > and might reduce version churn. Just my POV though ;-)
> 
> The locking cleanups are required in order to initialize pm_qos request 
> and notifiers without introducing lockdep warnings.
> 
> pm_qos calls notifiers under dev_pm_qos_mtx and those notifiers needs to 
> take &devfreq->lock. This means initializing pm_qos notifiers and 
> requests must be done outside &devfreq->lock which needs some cleanups 
> in devfreq_add_device.

Thanks for the clarification!

> This particular patch is a more loosely related bugfix. Devfreq 
> maintainers: would it help to post it separately?

If it's just this single patch it probably isn't a problem, I'd be
more concerned about multiple unrelated patches or if the changes are
complex.

> >> @@ -1415,15 +1416,20 @@ static ssize_t trans_stat_show(struct device *dev,
> >>   	struct devfreq *devfreq = to_devfreq(dev);
> >>   	ssize_t len;
> >>   	int i, j;
> >>   	unsigned int max_state = devfreq->profile->max_state;
> >>   
> >> +	mutex_lock(&devfreq->lock);
> >>   	if (!devfreq->stop_polling &&
> >> -			devfreq_update_status(devfreq, devfreq->previous_freq))
> >> -		return 0;
> >> -	if (max_state == 0)
> >> -		return sprintf(buf, "Not Supported.\n");
> >> +			devfreq_update_status(devfreq, devfreq->previous_freq)) {
> >> +		len = 0;
> > 
> > you could assign 'len' in the declaration instead, but it's just
> > another option, it'ss fine as is
> >> +		goto out;
> >> +	}
> >> +	if (max_state == 0) {
> >> +		len = sprintf(buf, "Not Supported.\n");
> >> +		goto out;
> >> +	}
> > 
> > This leaves the general structure of the code as is, which is great,
> > but since you are already touching this part you can consider to
> > improve it: 'max_state' is constant after device creation, hence the
> > check could be done at the beginning, which IMO would be clearer, it
> > could also save an unnecessary devfreq_update_status() call and it
> > wouldn't be necessary to hold the lock (one goto less).
> 
> Now that I look at this more closely &devfreq->lock only really needs to 
> be held during the stats update, it can be released during sprintf.

right, another simplification :)

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ 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