* [PATCH net v2 0/4] net: ethernet: fec: move GPR reigster offset and bit into DT
From: fugang.duan @ 2020-05-25 7:09 UTC (permalink / raw)
To: andrew, martin.fuzzey, davem
Cc: netdev, robh+dt, shawnguo, devicetree, kuba, fugang.duan
From: Fugang Duan <fugang.duan@nxp.com>
The commit da722186f654 (net: fec: set GPR bit on suspend by DT configuration) set the GPR reigster offset and bit in driver for wol feature.
It bring trouble to enable wol feature on imx6sx/imx6ul/imx7d platforms that have multiple ethernet instances with different GPR bit for stop mode control. So the patch set is to move GPR reigster offset and bit define into DT, and enable imx6q/imx6dl/imx6sx/imx6ul/imx7d stop mode support.
Currently, below NXP i.MX boards support wol:
- imx6q/imx6dl sabresd
- imx6sx sabreauto
- imx7d sdb
imx6q/imx6dl sarebsd board dts file miss the property "fsl,magic-packet;", so patch#4 is to add the property for stop mode support.
v1 -> v2:
- driver: switch back to store the quirks bitmask in driver_data
- dt-bindings: rename 'gpr' property string to 'fsl,stop-mode'
- imx6/7 dtsi: add imx6sx/imx6ul/imx7d ethernet stop mode property
Thanks Martin and Andrew for the review.
Fugang Duan (4):
net: ethernet: fec: move GPR register offset and bit into DT
dt-bindings: fec: update the gpr property
ARM: dts: imx: add ethernet stop mode property
ARM: dts: imx6qdl-sabresd: enable fec wake-on-lan
Documentation/devicetree/bindings/net/fsl-fec.txt | 7 +-
arch/arm/boot/dts/imx6qdl-sabresd.dtsi | 1 +
arch/arm/boot/dts/imx6qdl.dtsi | 2 +-
arch/arm/boot/dts/imx6sx.dtsi | 2 +
arch/arm/boot/dts/imx6ul.dtsi | 2 +
arch/arm/boot/dts/imx7d.dtsi | 1 +
arch/arm/boot/dts/imx7s.dtsi | 1 +
drivers/net/ethernet/freescale/fec_main.c | 103 +++++++---------------
8 files changed, 47 insertions(+), 72 deletions(-)
--
2.7.4
^ permalink raw reply
* Re: [PATCH v3 7/7] iommu/mediatek: Add mt6779 basic support
From: Yong Wu @ 2020-05-25 6:54 UTC (permalink / raw)
To: Chao Hao
Cc: Joerg Roedel, Rob Herring, Matthias Brugger, iommu, devicetree,
linux-kernel, linux-arm-kernel, linux-mediatek, wsd_upstream,
FY Yang, Jun Yan
In-Reply-To: <20200509083654.5178-8-chao.hao@mediatek.com>
On Sat, 2020-05-09 at 16:36 +0800, Chao Hao wrote:
> 1. Start from mt6779, INVLDT_SEL move to offset=0x2c, so we add
> REG_MMU_INV_SEL_GEN2 definition and mt6779 uses it.
> 2. Change PROTECT_PA_ALIGN from 128 byte to 256 byte.
> 3. For REG_MMU_CTRL_REG register, we only need to change bit[2:0],
> others bits keep default value, ex: enable victim tlb.
> 4. Add mt6779_data to support mm_iommu HW init.
>
> Signed-off-by: Chao Hao <chao.hao@mediatek.com>
> ---
> drivers/iommu/mtk_iommu.c | 18 +++++++++++++++---
> drivers/iommu/mtk_iommu.h | 1 +
> 2 files changed, 16 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c
> index dc9ae944e712..34c4ffb77c73 100644
> --- a/drivers/iommu/mtk_iommu.c
> +++ b/drivers/iommu/mtk_iommu.c
> @@ -37,6 +37,7 @@
> #define REG_MMU_INVLD_START_A 0x024
> #define REG_MMU_INVLD_END_A 0x028
>
> +#define REG_MMU_INV_SEL_GEN2 0x02c
> #define REG_MMU_INV_SEL_GEN1 0x038
Normally the register name comes from the CODA. In the lasted CODA,
this is called "MMU_INVLDT_SEL". But it's same with the previous 0x38
totally. Using _GEN1, _GEN2 is ok for me. Please add its coda name in
the comment. like:
#define REG_MMU_INV_SEL_GEN2 0x02c /* MMU_INVLDT_SEL */
> #define F_INVLD_EN0 BIT(0)
> #define F_INVLD_EN1 BIT(1)
> @@ -97,7 +98,7 @@
> #define F_MMU_INT_ID_LARB_ID(a) (((a) >> 7) & 0x7)
> #define F_MMU_INT_ID_PORT_ID(a) (((a) >> 2) & 0x1f)
>
> -#define MTK_PROTECT_PA_ALIGN 128
> +#define MTK_PROTECT_PA_ALIGN 256
>
> /*
> * Get the local arbiter ID and the portid within the larb arbiter
> @@ -554,11 +555,12 @@ static int mtk_iommu_hw_init(const struct mtk_iommu_data *data)
> return ret;
> }
>
> + regval = readl_relaxed(data->base + REG_MMU_CTRL_REG);
> if (data->plat_data->m4u_plat == M4U_MT8173)
> - regval = F_MMU_PREFETCH_RT_REPLACE_MOD |
> + regval |= F_MMU_PREFETCH_RT_REPLACE_MOD |
The default value is not ok for mt8173(Its bit9 is in_order_write_en, we
could not use its default 1'b1). thus, Don't touch this line.
> F_MMU_TF_PROT_TO_PROGRAM_ADDR_MT8173;
> else
> - regval = F_MMU_TF_PROT_TO_PROGRAM_ADDR;
> + regval |= F_MMU_TF_PROT_TO_PROGRAM_ADDR;
> writel_relaxed(regval, data->base + REG_MMU_CTRL_REG);
>
> regval = F_L2_MULIT_HIT_EN |
> @@ -804,6 +806,15 @@ static const struct mtk_iommu_plat_data mt2712_data = {
> .larbid_remap = {{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}},
> };
>
> +static const struct mtk_iommu_plat_data mt6779_data = {
> + .m4u_plat = M4U_MT6779,
> + .larbid_remap = {{0}, {1}, {2}, {3}, {5}, {7, 8}, {10}, {9}},
> + .has_sub_comm = true,
> + .has_wr_len = true,
> + .has_misc_ctrl = true,
> + .inv_sel_reg = REG_MMU_INV_SEL_GEN2,
align '=' a bit.
> +};
> +
> static const struct mtk_iommu_plat_data mt8173_data = {
> .m4u_plat = M4U_MT8173,
> .has_4gb_mode = true,
> @@ -822,6 +833,7 @@ static const struct mtk_iommu_plat_data mt8183_data = {
>
> static const struct of_device_id mtk_iommu_of_ids[] = {
> { .compatible = "mediatek,mt2712-m4u", .data = &mt2712_data},
> + { .compatible = "mediatek,mt6779-m4u", .data = &mt6779_data},
> { .compatible = "mediatek,mt8173-m4u", .data = &mt8173_data},
> { .compatible = "mediatek,mt8183-m4u", .data = &mt8183_data},
> {}
> diff --git a/drivers/iommu/mtk_iommu.h b/drivers/iommu/mtk_iommu.h
> index 9971cedd72ea..fb79e710c8d9 100644
> --- a/drivers/iommu/mtk_iommu.h
> +++ b/drivers/iommu/mtk_iommu.h
> @@ -31,6 +31,7 @@ struct mtk_iommu_suspend_reg {
> enum mtk_iommu_plat {
> M4U_MT2701,
> M4U_MT2712,
> + M4U_MT6779,
> M4U_MT8173,
> M4U_MT8183,
> };
^ permalink raw reply
* Re: [PATCH 3/3] usb: dwc3: keystone: Turn on USB3 PHY before controller
From: Roger Quadros @ 2020-05-25 6:41 UTC (permalink / raw)
To: Felipe Balbi, Chunfeng Yun
Cc: robh+dt, vigneshr, linux-usb, devicetree, linux-kernel
In-Reply-To: <debe1c1d-8e84-8047-d387-42d4f6e03f91@ti.com>
On 25/05/2020 09:39, Roger Quadros wrote:
> Felipe,
>
> On 14/05/2020 13:21, Felipe Balbi wrote:
>> Roger Quadros <rogerq@ti.com> writes:
>>
>>> On 14/05/2020 04:37, Chunfeng Yun wrote:
>>>> On Wed, 2020-05-13 at 16:07 +0300, Roger Quadros wrote:
>>>>> The Local Power Sleep Controller (LPSC) dependency on AM65
>>>>> requires SERDES0 to be powered on before USB.
>>>>>
>>>>> We need to power up SERDES0 power domain and hold it on
>>>>> throughout the reset, init, power on sequence.
>>>>>
>>>>> Signed-off-by: Roger Quadros <rogerq@ti.com>
>>>>> ---
>>>>> drivers/usb/dwc3/dwc3-keystone.c | 47 +++++++++++++++++++++++++++++++-
>>>>> 1 file changed, 46 insertions(+), 1 deletion(-)
>>>>>
>>>>> diff --git a/drivers/usb/dwc3/dwc3-keystone.c b/drivers/usb/dwc3/dwc3-keystone.c
>>>>> index 1e14a6f4884b..46d46f3507fc 100644
>>>>> --- a/drivers/usb/dwc3/dwc3-keystone.c
>>>>> +++ b/drivers/usb/dwc3/dwc3-keystone.c
>>>>> @@ -14,6 +14,7 @@
>>>>> #include <linux/dma-mapping.h>
>>>>> #include <linux/io.h>
>>>>> #include <linux/of_platform.h>
>>>>> +#include <linux/phy/phy.h>
>>>>> #include <linux/pm_runtime.h>
>>>>> /* USBSS register offsets */
>>>>> @@ -34,6 +35,7 @@
>>>>> struct dwc3_keystone {
>>>>> struct device *dev;
>>>>> void __iomem *usbss;
>>>>> + struct phy *usb3_phy;
>>>>> };
>>>>> static inline u32 kdwc3_readl(void __iomem *base, u32 offset)
>>>>> @@ -95,8 +97,44 @@ static int kdwc3_probe(struct platform_device *pdev)
>>>>> if (IS_ERR(kdwc->usbss))
>>>>> return PTR_ERR(kdwc->usbss);
>>>>> - pm_runtime_enable(kdwc->dev);
>>>>> + /* PSC dependency on AM65 needs SERDES0 to be powered before USB0 */
>>>>> + kdwc->usb3_phy = devm_phy_get(dev, "usb3-phy");
>>>> Use devm_phy_optional_get() instead?
>>>
>>> Indeed, it seems better suited.
>>
>> patches 1 and 2 are in testing/next
>>
>
> Could you please drop them as I need to make changes to make the PHY optional.
> I will send v2 of entire series.
Actually only patch 2 and 3 need to be revised. Patch 1 is fine.
cheers,
-roger
--
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
^ permalink raw reply
* Re: [PATCH 3/3] usb: dwc3: keystone: Turn on USB3 PHY before controller
From: Roger Quadros @ 2020-05-25 6:39 UTC (permalink / raw)
To: Felipe Balbi, Chunfeng Yun
Cc: robh+dt, vigneshr, linux-usb, devicetree, linux-kernel
In-Reply-To: <87tv0i4y0v.fsf@kernel.org>
Felipe,
On 14/05/2020 13:21, Felipe Balbi wrote:
> Roger Quadros <rogerq@ti.com> writes:
>
>> On 14/05/2020 04:37, Chunfeng Yun wrote:
>>> On Wed, 2020-05-13 at 16:07 +0300, Roger Quadros wrote:
>>>> The Local Power Sleep Controller (LPSC) dependency on AM65
>>>> requires SERDES0 to be powered on before USB.
>>>>
>>>> We need to power up SERDES0 power domain and hold it on
>>>> throughout the reset, init, power on sequence.
>>>>
>>>> Signed-off-by: Roger Quadros <rogerq@ti.com>
>>>> ---
>>>> drivers/usb/dwc3/dwc3-keystone.c | 47 +++++++++++++++++++++++++++++++-
>>>> 1 file changed, 46 insertions(+), 1 deletion(-)
>>>>
>>>> diff --git a/drivers/usb/dwc3/dwc3-keystone.c b/drivers/usb/dwc3/dwc3-keystone.c
>>>> index 1e14a6f4884b..46d46f3507fc 100644
>>>> --- a/drivers/usb/dwc3/dwc3-keystone.c
>>>> +++ b/drivers/usb/dwc3/dwc3-keystone.c
>>>> @@ -14,6 +14,7 @@
>>>> #include <linux/dma-mapping.h>
>>>> #include <linux/io.h>
>>>> #include <linux/of_platform.h>
>>>> +#include <linux/phy/phy.h>
>>>> #include <linux/pm_runtime.h>
>>>>
>>>> /* USBSS register offsets */
>>>> @@ -34,6 +35,7 @@
>>>> struct dwc3_keystone {
>>>> struct device *dev;
>>>> void __iomem *usbss;
>>>> + struct phy *usb3_phy;
>>>> };
>>>>
>>>> static inline u32 kdwc3_readl(void __iomem *base, u32 offset)
>>>> @@ -95,8 +97,44 @@ static int kdwc3_probe(struct platform_device *pdev)
>>>> if (IS_ERR(kdwc->usbss))
>>>> return PTR_ERR(kdwc->usbss);
>>>>
>>>> - pm_runtime_enable(kdwc->dev);
>>>> + /* PSC dependency on AM65 needs SERDES0 to be powered before USB0 */
>>>> + kdwc->usb3_phy = devm_phy_get(dev, "usb3-phy");
>>> Use devm_phy_optional_get() instead?
>>
>> Indeed, it seems better suited.
>
> patches 1 and 2 are in testing/next
>
Could you please drop them as I need to make changes to make the PHY optional.
I will send v2 of entire series.
cheers,
-roger
--
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
^ permalink raw reply
* Re: [PATCH v3 5/7] iommu/mediatek: Add sub_comm id in translation fault
From: Yong Wu @ 2020-05-25 6:31 UTC (permalink / raw)
To: Chao Hao
Cc: Joerg Roedel, Rob Herring, Matthias Brugger, iommu, devicetree,
linux-kernel, linux-arm-kernel, linux-mediatek, wsd_upstream,
FY Yang, Jun Yan
In-Reply-To: <20200509083654.5178-6-chao.hao@mediatek.com>
On Sat, 2020-05-09 at 16:36 +0800, Chao Hao wrote:
> The max larb number that a iommu HW support is 8(larb0~larb7 in the below
> diagram).
> If the larb's number is over 8, we use a sub_common for merging
> several larbs into one larb. At this case, we will extend larb_id:
> bit[11:9] means common-id;
> bit[8:7] means subcommon-id;
> From these two variable, we could get the real larb number when
> translation fault happen.
> The diagram is as below:
> EMI
> |
> IOMMU
> |
> -----------------
> | |
> common1 common0
> | |
> -----------------
> |
> smi common
> |
> ------------------------------------
> | | | | | |
> 3'd0 3'd1 3'd2 3'd3 ... 3'd7 <-common_id(max is 8)
> | | | | | |
> Larb0 Larb1 | Larb3 ... Larb7
> |
> smi sub common
> |
> --------------------------
> | | | |
> 2'd0 2'd1 2'd2 2'd3 <-sub_common_id(max is 4)
> | | | |
> Larb8 Larb9 Larb10 Larb11
>
> In this patch we extern larb_remap[] to larb_remap[8][4] for this.
> larb_remap[x][y]: x mean common-id above, y means subcommon_id above.
>
> We can also distinguish if the M4U HW has sub_common by has_sub_comm
> property.
>
> Signed-off-by: Chao Hao <chao.hao@mediatek.com>
> ---
> drivers/iommu/mtk_iommu.c | 20 +++++++++++++-------
> drivers/iommu/mtk_iommu.h | 3 ++-
> 2 files changed, 15 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c
> index d73de987f8be..3914c418d1b0 100644
> --- a/drivers/iommu/mtk_iommu.c
> +++ b/drivers/iommu/mtk_iommu.c
> @@ -90,6 +90,8 @@
> #define REG_MMU1_INVLD_PA 0x148
> #define REG_MMU0_INT_ID 0x150
> #define REG_MMU1_INT_ID 0x154
> +#define F_MMU_INT_ID_COMM_ID(a) (((a) >> 9) & 0x7)
> +#define F_MMU_INT_ID_SUB_COMM_ID(a) (((a) >> 7) & 0x3)
> #define F_MMU_INT_ID_LARB_ID(a) (((a) >> 7) & 0x7)
> #define F_MMU_INT_ID_PORT_ID(a) (((a) >> 2) & 0x1f)
>
> @@ -228,7 +230,7 @@ static irqreturn_t mtk_iommu_isr(int irq, void *dev_id)
> struct mtk_iommu_data *data = dev_id;
> struct mtk_iommu_domain *dom = data->m4u_dom;
> u32 int_state, regval, fault_iova, fault_pa;
> - unsigned int fault_larb, fault_port;
> + unsigned int fault_larb, fault_port, sub_comm = 0;
> bool layer, write;
>
> /* Read error info from registers */
> @@ -244,10 +246,14 @@ static irqreturn_t mtk_iommu_isr(int irq, void *dev_id)
> }
> layer = fault_iova & F_MMU_FAULT_VA_LAYER_BIT;
> write = fault_iova & F_MMU_FAULT_VA_WRITE_BIT;
> - fault_larb = F_MMU_INT_ID_LARB_ID(regval);
> fault_port = F_MMU_INT_ID_PORT_ID(regval);
> -
> - fault_larb = data->plat_data->larbid_remap[fault_larb];
> + if (data->plat_data->has_sub_comm) {
> + fault_larb = F_MMU_INT_ID_COMM_ID(regval);
> + sub_comm = F_MMU_INT_ID_SUB_COMM_ID(regval);
> + } else {
> + fault_larb = F_MMU_INT_ID_LARB_ID(regval);
> + }
> + fault_larb = data->plat_data->larbid_remap[fault_larb][sub_comm];
>
> if (report_iommu_fault(&dom->domain, data->dev, fault_iova,
> write ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ)) {
> @@ -785,7 +791,7 @@ static const struct mtk_iommu_plat_data mt2712_data = {
> .has_bclk = true,
> .has_vld_pa_rng = true,
> .inv_sel_reg = REG_MMU_INV_SEL_GEN1,
> - .larbid_remap = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
> + .larbid_remap = {{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}},
The original mt2712 has 10 larbs, but it actually has 2 IOMMU HWs.
For each a smi-common, The max larb number could only be 8. So it is
right here.
Thus,
Reviewed-by: Yong Wu <yong.wu@mediatek.com>
> };
>
> static const struct mtk_iommu_plat_data mt8173_data = {
> @@ -794,14 +800,14 @@ static const struct mtk_iommu_plat_data mt8173_data = {
> .has_bclk = true,
> .reset_axi = true,
> .inv_sel_reg = REG_MMU_INV_SEL_GEN1,
> - .larbid_remap = {0, 1, 2, 3, 4, 5}, /* Linear mapping. */
> + .larbid_remap = {{0}, {1}, {2}, {3}, {4}, {5}}, /* Linear mapping. */
> };
>
> static const struct mtk_iommu_plat_data mt8183_data = {
> .m4u_plat = M4U_MT8183,
> .reset_axi = true,
> .inv_sel_reg = REG_MMU_INV_SEL_GEN1,
> - .larbid_remap = {0, 4, 5, 6, 7, 2, 3, 1},
> + .larbid_remap = {{0}, {4}, {5}, {6}, {7}, {2}, {3}, {1}},
> };
>
> static const struct of_device_id mtk_iommu_of_ids[] = {
> diff --git a/drivers/iommu/mtk_iommu.h b/drivers/iommu/mtk_iommu.h
> index afd7a2de5c1e..d51ff99c2c71 100644
> --- a/drivers/iommu/mtk_iommu.h
> +++ b/drivers/iommu/mtk_iommu.h
> @@ -41,10 +41,11 @@ struct mtk_iommu_plat_data {
> /* HW will use the EMI clock if there isn't the "bclk". */
> bool has_bclk;
> bool has_misc_ctrl;
> + bool has_sub_comm;
> bool has_vld_pa_rng;
> bool reset_axi;
> u32 inv_sel_reg;
> - unsigned char larbid_remap[MTK_LARB_NR_MAX];
> + unsigned char larbid_remap[8][4];
> };
>
> struct mtk_iommu_domain;
^ permalink raw reply
* Re: [PATCH v3 4/7] iommu/mediatek: Move inv_sel_reg into the plat_data
From: Yong Wu @ 2020-05-25 6:22 UTC (permalink / raw)
To: Chao Hao
Cc: Joerg Roedel, Rob Herring, Matthias Brugger, iommu, devicetree,
linux-kernel, linux-arm-kernel, linux-mediatek, wsd_upstream,
FY Yang, Jun Yan
In-Reply-To: <20200509083654.5178-5-chao.hao@mediatek.com>
On Sat, 2020-05-09 at 16:36 +0800, Chao Hao wrote:
> For mt6779, MMU_INVLDT_SEL register's offset is changed from
At this patch, the register is still called by "MMU_INV_SEL".
> 0x38 to 0x2c, so we can put inv_sel_reg in the plat_data to
> use it.
> In addition, we renamed it to REG_MMU_INV_SEL_GEN1 and use it
> before mt6779.
>
> Signed-off-by: Chao Hao <chao.hao@mediatek.com>
> ---
> drivers/iommu/mtk_iommu.c | 9 ++++++---
> drivers/iommu/mtk_iommu.h | 1 +
> 2 files changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c
> index 9ede327a418d..d73de987f8be 100644
> --- a/drivers/iommu/mtk_iommu.c
> +++ b/drivers/iommu/mtk_iommu.c
> @@ -37,7 +37,7 @@
> #define REG_MMU_INVLD_START_A 0x024
> #define REG_MMU_INVLD_END_A 0x028
>
> -#define REG_MMU_INV_SEL 0x038
> +#define REG_MMU_INV_SEL_GEN1 0x038
> #define F_INVLD_EN0 BIT(0)
> #define F_INVLD_EN1 BIT(1)
>
> @@ -167,7 +167,7 @@ static void mtk_iommu_tlb_flush_all(void *cookie)
>
> for_each_m4u(data) {
> writel_relaxed(F_INVLD_EN1 | F_INVLD_EN0,
> - data->base + REG_MMU_INV_SEL);
> + data->base + data->plat_data->inv_sel_reg);
> writel_relaxed(F_ALL_INVLD, data->base + REG_MMU_INVALIDATE);
> wmb(); /* Make sure the tlb flush all done */
> }
> @@ -184,7 +184,7 @@ static void mtk_iommu_tlb_flush_range_sync(unsigned long iova, size_t size,
> for_each_m4u(data) {
> spin_lock_irqsave(&data->tlb_lock, flags);
> writel_relaxed(F_INVLD_EN1 | F_INVLD_EN0,
> - data->base + REG_MMU_INV_SEL);
> + data->base + data->plat_data->inv_sel_reg);
>
> writel_relaxed(iova, data->base + REG_MMU_INVLD_START_A);
> writel_relaxed(iova + size - 1,
> @@ -784,6 +784,7 @@ static const struct mtk_iommu_plat_data mt2712_data = {
> .has_4gb_mode = true,
> .has_bclk = true,
> .has_vld_pa_rng = true,
> + .inv_sel_reg = REG_MMU_INV_SEL_GEN1,
nitpick: align '=' with the next line.
> .larbid_remap = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
> };
>
> @@ -792,12 +793,14 @@ static const struct mtk_iommu_plat_data mt8173_data = {
> .has_4gb_mode = true,
> .has_bclk = true,
> .reset_axi = true,
> + .inv_sel_reg = REG_MMU_INV_SEL_GEN1,
align '='
> .larbid_remap = {0, 1, 2, 3, 4, 5}, /* Linear mapping. */
> };
>
> static const struct mtk_iommu_plat_data mt8183_data = {
> .m4u_plat = M4U_MT8183,
> .reset_axi = true,
> + .inv_sel_reg = REG_MMU_INV_SEL_GEN1,
> .larbid_remap = {0, 4, 5, 6, 7, 2, 3, 1},
> };
>
> diff --git a/drivers/iommu/mtk_iommu.h b/drivers/iommu/mtk_iommu.h
> index d711ac630037..afd7a2de5c1e 100644
> --- a/drivers/iommu/mtk_iommu.h
> +++ b/drivers/iommu/mtk_iommu.h
> @@ -43,6 +43,7 @@ struct mtk_iommu_plat_data {
> bool has_misc_ctrl;
> bool has_vld_pa_rng;
> bool reset_axi;
> + u32 inv_sel_reg;
> unsigned char larbid_remap[MTK_LARB_NR_MAX];
> };
>
^ permalink raw reply
* Re: [PATCH v3 3/7] iommu/mediatek: Disable STANDARD_AXI_MODE in MISC_CTRL
From: Yong Wu @ 2020-05-25 6:14 UTC (permalink / raw)
To: Chao Hao
Cc: Joerg Roedel, Rob Herring, Matthias Brugger, iommu, devicetree,
linux-kernel, linux-arm-kernel, linux-mediatek, wsd_upstream,
FY Yang, Jun Yan
In-Reply-To: <20200509083654.5178-4-chao.hao@mediatek.com>
On Sat, 2020-05-09 at 16:36 +0800, Chao Hao wrote:
> In order to improve performance, we always disable STANDARD_AXI_MODE in
> MISC_CTRL.
>
> Signed-off-by: Chao Hao <chao.hao@mediatek.com>
> ---
> drivers/iommu/mtk_iommu.c | 8 +++++++-
> drivers/iommu/mtk_iommu.h | 1 +
> 2 files changed, 8 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c
> index e7e7c7695ed1..9ede327a418d 100644
> --- a/drivers/iommu/mtk_iommu.c
> +++ b/drivers/iommu/mtk_iommu.c
> @@ -42,6 +42,8 @@
> #define F_INVLD_EN1 BIT(1)
>
> #define REG_MMU_MISC_CTRL 0x048
> +#define F_MMU_STANDARD_AXI_MODE_BIT (BIT(3) | BIT(19))
> +
> #define REG_MMU_DCM_DIS 0x050
>
> #define REG_MMU_CTRL_REG 0x110
> @@ -585,7 +587,11 @@ static int mtk_iommu_hw_init(const struct mtk_iommu_data *data)
> }
> writel_relaxed(0, data->base + REG_MMU_DCM_DIS);
>
> - if (data->plat_data->reset_axi) {
> + if (data->plat_data->has_misc_ctrl) {
> + regval = readl_relaxed(data->base + REG_MMU_MISC_CTRL);
> + regval &= ~F_MMU_STANDARD_AXI_MODE_BIT;
> + writel_relaxed(regval, data->base + REG_MMU_MISC_CTRL);
> + } else if (data->plat_data->reset_axi) {
> /* The register is called STANDARD_AXI_MODE in this case */
> writel_relaxed(0, data->base + REG_MMU_MISC_CTRL);
> }
0x48 is either STANDARD_AXI_MODE or MISC_CTRL.
Thus,
if (data->plat_data->reset_axi) {
xxx
} else { /* MISC_CTRL */
xxx
}
No need add "has_misc_ctrl".
> diff --git a/drivers/iommu/mtk_iommu.h b/drivers/iommu/mtk_iommu.h
> index 1b6ea839b92c..d711ac630037 100644
> --- a/drivers/iommu/mtk_iommu.h
> +++ b/drivers/iommu/mtk_iommu.h
> @@ -40,6 +40,7 @@ struct mtk_iommu_plat_data {
>
> /* HW will use the EMI clock if there isn't the "bclk". */
> bool has_bclk;
> + bool has_misc_ctrl;
> bool has_vld_pa_rng;
> bool reset_axi;
> unsigned char larbid_remap[MTK_LARB_NR_MAX];
^ permalink raw reply
* Re: [PATCH v3 2/7] iommu/mediatek: Rename the register STANDARD_AXI_MODE(0x48) to MISC_CTRL
From: Yong Wu @ 2020-05-25 6:11 UTC (permalink / raw)
To: Chao Hao
Cc: Joerg Roedel, Rob Herring, Matthias Brugger, iommu, devicetree,
linux-kernel, linux-arm-kernel, linux-mediatek, wsd_upstream,
FY Yang, Jun Yan
In-Reply-To: <20200509083654.5178-3-chao.hao@mediatek.com>
On Sat, 2020-05-09 at 16:36 +0800, Chao Hao wrote:
> For iommu offset=0x48 register, only the previous mt8173/mt8183 use the
> name STANDARD_AXI_MODE, all the latest SoC extend the register more
> feature by different bits, for example: axi_mode, in_order_en, coherent_en
> and so on. So rename REG_MMU_MISC_CTRL may be more proper.
>
> This patch only rename the register name, no functional change.
>
> Signed-off-by: Chao Hao <chao.hao@mediatek.com>
Reviewed-by: Yong Wu <yong.wu@mediatek.com>
> ---
> drivers/iommu/mtk_iommu.c | 14 +++++++-------
> drivers/iommu/mtk_iommu.h | 2 +-
> 2 files changed, 8 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c
> index 5f4d6df59cf6..e7e7c7695ed1 100644
> --- a/drivers/iommu/mtk_iommu.c
> +++ b/drivers/iommu/mtk_iommu.c
> @@ -41,7 +41,7 @@
> #define F_INVLD_EN0 BIT(0)
> #define F_INVLD_EN1 BIT(1)
>
> -#define REG_MMU_STANDARD_AXI_MODE 0x048
> +#define REG_MMU_MISC_CTRL 0x048
> #define REG_MMU_DCM_DIS 0x050
>
> #define REG_MMU_CTRL_REG 0x110
> @@ -585,8 +585,10 @@ static int mtk_iommu_hw_init(const struct mtk_iommu_data *data)
> }
> writel_relaxed(0, data->base + REG_MMU_DCM_DIS);
>
> - if (data->plat_data->reset_axi)
> - writel_relaxed(0, data->base + REG_MMU_STANDARD_AXI_MODE);
> + if (data->plat_data->reset_axi) {
> + /* The register is called STANDARD_AXI_MODE in this case */
> + writel_relaxed(0, data->base + REG_MMU_MISC_CTRL);
> + }
>
> if (devm_request_irq(data->dev, data->irq, mtk_iommu_isr, 0,
> dev_name(data->dev), (void *)data)) {
> @@ -730,8 +732,7 @@ static int __maybe_unused mtk_iommu_suspend(struct device *dev)
> struct mtk_iommu_suspend_reg *reg = &data->reg;
> void __iomem *base = data->base;
>
> - reg->standard_axi_mode = readl_relaxed(base +
> - REG_MMU_STANDARD_AXI_MODE);
> + reg->misc_ctrl = readl_relaxed(base + REG_MMU_MISC_CTRL);
> reg->dcm_dis = readl_relaxed(base + REG_MMU_DCM_DIS);
> reg->ctrl_reg = readl_relaxed(base + REG_MMU_CTRL_REG);
> reg->int_control0 = readl_relaxed(base + REG_MMU_INT_CONTROL0);
> @@ -755,8 +756,7 @@ static int __maybe_unused mtk_iommu_resume(struct device *dev)
> dev_err(data->dev, "Failed to enable clk(%d) in resume\n", ret);
> return ret;
> }
> - writel_relaxed(reg->standard_axi_mode,
> - base + REG_MMU_STANDARD_AXI_MODE);
> + writel_relaxed(reg->misc_ctrl, base + REG_MMU_MISC_CTRL);
> writel_relaxed(reg->dcm_dis, base + REG_MMU_DCM_DIS);
> writel_relaxed(reg->ctrl_reg, base + REG_MMU_CTRL_REG);
> writel_relaxed(reg->int_control0, base + REG_MMU_INT_CONTROL0);
> diff --git a/drivers/iommu/mtk_iommu.h b/drivers/iommu/mtk_iommu.h
> index ea949a324e33..1b6ea839b92c 100644
> --- a/drivers/iommu/mtk_iommu.h
> +++ b/drivers/iommu/mtk_iommu.h
> @@ -18,7 +18,7 @@
> #include <soc/mediatek/smi.h>
>
> struct mtk_iommu_suspend_reg {
> - u32 standard_axi_mode;
> + u32 misc_ctrl;
> u32 dcm_dis;
> u32 ctrl_reg;
> u32 int_control0;
^ permalink raw reply
* Re: [PATCH V2 3/3] mmc: sdhci: Allow platform controlled voltage switching
From: Adrian Hunter @ 2020-05-25 5:42 UTC (permalink / raw)
To: Veerabhadrarao Badiganti, ulf.hansson, bjorn.andersson, robh+dt
Cc: linux-mmc, linux-kernel, linux-arm-msm, devicetree,
Vijay Viswanath
In-Reply-To: <1590074615-10787-4-git-send-email-vbadigan@codeaurora.org>
On 21/05/20 6:23 pm, Veerabhadrarao Badiganti wrote:
> From: Vijay Viswanath <vviswana@codeaurora.org>
>
> If vendor platform drivers are controlling whole logic of voltage
> switching, then sdhci driver no need control vqmmc regulator.
> So skip enabling/disable vqmmc from SDHC driver.
>
> Signed-off-by: Vijay Viswanath <vviswana@codeaurora.org>
> Signed-off-by: Veerabhadrarao Badiganti <vbadigan@codeaurora.org>
Acked-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
> drivers/mmc/host/sdhci.c | 32 +++++++++++++++++++-------------
> drivers/mmc/host/sdhci.h | 1 +
> 2 files changed, 20 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index 1bb6b67..88e5312 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -4098,6 +4098,7 @@ int sdhci_setup_host(struct sdhci_host *host)
> unsigned int override_timeout_clk;
> u32 max_clk;
> int ret;
> + bool enable_vqmmc = false;
>
> WARN_ON(host == NULL);
> if (host == NULL)
> @@ -4111,9 +4112,12 @@ int sdhci_setup_host(struct sdhci_host *host)
> * the host can take the appropriate action if regulators are not
> * available.
> */
> - ret = mmc_regulator_get_supply(mmc);
> - if (ret)
> - return ret;
> + if (!mmc->supply.vqmmc) {
> + ret = mmc_regulator_get_supply(mmc);
> + if (ret)
> + return ret;
> + enable_vqmmc = true;
> + }
>
> DBG("Version: 0x%08x | Present: 0x%08x\n",
> sdhci_readw(host, SDHCI_HOST_VERSION),
> @@ -4373,7 +4377,15 @@ int sdhci_setup_host(struct sdhci_host *host)
> mmc->caps |= MMC_CAP_NEEDS_POLL;
>
> if (!IS_ERR(mmc->supply.vqmmc)) {
> - ret = regulator_enable(mmc->supply.vqmmc);
> + if (enable_vqmmc) {
> + ret = regulator_enable(mmc->supply.vqmmc);
> + if (ret) {
> + pr_warn("%s: Failed to enable vqmmc regulator: %d\n",
> + mmc_hostname(mmc), ret);
> + mmc->supply.vqmmc = ERR_PTR(-EINVAL);
> + }
> + host->sdhci_core_to_disable_vqmmc = !ret;
> + }
>
> /* If vqmmc provides no 1.8V signalling, then there's no UHS */
> if (!regulator_is_supported_voltage(mmc->supply.vqmmc, 1700000,
> @@ -4386,12 +4398,6 @@ int sdhci_setup_host(struct sdhci_host *host)
> if (!regulator_is_supported_voltage(mmc->supply.vqmmc, 2700000,
> 3600000))
> host->flags &= ~SDHCI_SIGNALING_330;
> -
> - if (ret) {
> - pr_warn("%s: Failed to enable vqmmc regulator: %d\n",
> - mmc_hostname(mmc), ret);
> - mmc->supply.vqmmc = ERR_PTR(-EINVAL);
> - }
> }
>
> if (host->quirks2 & SDHCI_QUIRK2_NO_1_8_V) {
> @@ -4625,7 +4631,7 @@ int sdhci_setup_host(struct sdhci_host *host)
> return 0;
>
> unreg:
> - if (!IS_ERR(mmc->supply.vqmmc))
> + if (host->sdhci_core_to_disable_vqmmc)
> regulator_disable(mmc->supply.vqmmc);
> undma:
> if (host->align_buffer)
> @@ -4643,7 +4649,7 @@ void sdhci_cleanup_host(struct sdhci_host *host)
> {
> struct mmc_host *mmc = host->mmc;
>
> - if (!IS_ERR(mmc->supply.vqmmc))
> + if (host->sdhci_core_to_disable_vqmmc)
> regulator_disable(mmc->supply.vqmmc);
>
> if (host->align_buffer)
> @@ -4780,7 +4786,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead)
>
> destroy_workqueue(host->complete_wq);
>
> - if (!IS_ERR(mmc->supply.vqmmc))
> + if (host->sdhci_core_to_disable_vqmmc)
> regulator_disable(mmc->supply.vqmmc);
>
> if (host->align_buffer)
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index 8d2a096..c7dbc68 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -570,6 +570,7 @@ struct sdhci_host {
> u32 caps1; /* CAPABILITY_1 */
> bool read_caps; /* Capability flags have been read */
>
> + bool sdhci_core_to_disable_vqmmc; /* sdhci core can disable vqmmc */
> unsigned int ocr_avail_sdio; /* OCR bit masks */
> unsigned int ocr_avail_sd;
> unsigned int ocr_avail_mmc;
>
^ permalink raw reply
* [PATCH v2 2/9] irqchip/sun6i-r: Add wakeup support
From: Samuel Holland @ 2020-05-25 4:12 UTC (permalink / raw)
To: Thomas Gleixner, Jason Cooper, Marc Zyngier, Rob Herring,
Maxime Ripard, Chen-Yu Tsai, Russell King, Catalin Marinas,
Will Deacon
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-sunxi,
Samuel Holland
In-Reply-To: <20200525041302.51213-1-samuel@sholland.org>
Maintain a mask of wake-enabled IRQs, and enable them in hardware
during the syscore phase of suspend. The restore the original mask
of enabled IRQs (just the NMI) during resume.
This serves two purposes. First, it lets power management firmware
running on the ARISC coprocessor know which wakeup sources Linux wants
to have enabled. That way, it can avoid turning them off when it shuts
down the remainder of the clock tree. Second, it preconfigures the
coprocessor's interrupt controller, so the firmware's wakeup logic
is as simple as waiting for an interrupt to arrive.
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
drivers/irqchip/irq-sun6i-r.c | 51 +++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/drivers/irqchip/irq-sun6i-r.c b/drivers/irqchip/irq-sun6i-r.c
index f8bfa5515f20..a5deea92057f 100644
--- a/drivers/irqchip/irq-sun6i-r.c
+++ b/drivers/irqchip/irq-sun6i-r.c
@@ -3,12 +3,14 @@
// Allwinner A31 and newer SoCs R_INTC driver
//
+#include <linux/atomic.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
+#include <linux/syscore_ops.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
@@ -24,6 +26,9 @@
static void __iomem *base;
static irq_hw_number_t parent_offset;
static u32 parent_type;
+#ifdef CONFIG_PM_SLEEP
+static atomic_t wake_mask;
+#endif
static struct irq_chip sun6i_r_intc_edge;
static struct irq_chip sun6i_r_intc_level;
@@ -106,6 +111,20 @@ static int sun6i_r_intc_irq_set_type(struct irq_data *data, unsigned int type)
return irq_chip_set_type_parent(data, type);
}
+#ifdef CONFIG_PM_SLEEP
+static int sun6i_r_intc_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+ if (on)
+ atomic_or(BIT(data->hwirq), &wake_mask);
+ else
+ atomic_andnot(BIT(data->hwirq), &wake_mask);
+
+ return 0;
+}
+#else
+#define sun6i_r_intc_irq_set_wake NULL
+#endif
+
static struct irq_chip sun6i_r_intc_edge = {
.name = "sun6i-r-intc",
.irq_ack = sun6i_r_intc_irq_ack,
@@ -116,6 +135,7 @@ static struct irq_chip sun6i_r_intc_edge = {
.irq_set_type = sun6i_r_intc_irq_set_type,
.irq_get_irqchip_state = irq_chip_get_parent_state,
.irq_set_irqchip_state = irq_chip_set_parent_state,
+ .irq_set_wake = sun6i_r_intc_irq_set_wake,
.irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent,
.flags = IRQCHIP_SET_TYPE_MASKED,
};
@@ -129,6 +149,7 @@ static struct irq_chip sun6i_r_intc_level = {
.irq_set_type = sun6i_r_intc_irq_set_type,
.irq_get_irqchip_state = irq_chip_get_parent_state,
.irq_set_irqchip_state = irq_chip_set_parent_state,
+ .irq_set_wake = sun6i_r_intc_irq_set_wake,
.irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent,
.flags = IRQCHIP_SET_TYPE_MASKED |
IRQCHIP_EOI_THREADED,
@@ -170,6 +191,34 @@ static const struct irq_domain_ops sun6i_r_intc_domain_ops = {
.free = irq_domain_free_irqs_common,
};
+#ifdef CONFIG_PM_SLEEP
+static int sun6i_r_intc_suspend(void)
+{
+ /* All wake IRQs are enabled during system sleep. */
+ writel_relaxed(atomic_read(&wake_mask), base + SUN6I_R_INTC_ENABLE);
+
+ return 0;
+}
+
+static void sun6i_r_intc_resume(void)
+{
+ /* Only the NMI is relevant during normal operation. */
+ writel_relaxed(NMI_HWIRQ_BIT, base + SUN6I_R_INTC_ENABLE);
+}
+
+static struct syscore_ops sun6i_r_intc_syscore_ops = {
+ .suspend = sun6i_r_intc_suspend,
+ .resume = sun6i_r_intc_resume,
+};
+
+static void sun6i_r_intc_syscore_init(void)
+{
+ register_syscore_ops(&sun6i_r_intc_syscore_ops);
+}
+#else
+static inline void sun6i_r_intc_syscore_init(void) {}
+#endif
+
static int __init sun6i_r_intc_init(struct device_node *node,
struct device_node *parent)
{
@@ -211,6 +260,8 @@ static int __init sun6i_r_intc_init(struct device_node *node,
writel_relaxed(NMI_HWIRQ_BIT, base + SUN6I_R_INTC_PENDING);
writel_relaxed(NMI_HWIRQ_BIT, base + SUN6I_R_INTC_ENABLE);
+ sun6i_r_intc_syscore_init();
+
return 0;
}
IRQCHIP_DECLARE(sun6i_r_intc, "allwinner,sun6i-a31-r-intc", sun6i_r_intc_init);
--
2.24.1
^ permalink raw reply related
* [PATCH v2 4/9] ARM: dts: sunxi: h3/h5: Add r_intc node
From: Samuel Holland @ 2020-05-25 4:12 UTC (permalink / raw)
To: Thomas Gleixner, Jason Cooper, Marc Zyngier, Rob Herring,
Maxime Ripard, Chen-Yu Tsai, Russell King, Catalin Marinas,
Will Deacon
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-sunxi,
Samuel Holland
In-Reply-To: <20200525041302.51213-1-samuel@sholland.org>
The H3 and H5 SoCs have an additional interrupt controller in the RTC
power domain that can be used to enable wakeup for certain IRQs.
Add a node for it.
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
arch/arm/boot/dts/sunxi-h3-h5.dtsi | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/arm/boot/dts/sunxi-h3-h5.dtsi b/arch/arm/boot/dts/sunxi-h3-h5.dtsi
index 01a5df9aa71b..94f648ad1c9e 100644
--- a/arch/arm/boot/dts/sunxi-h3-h5.dtsi
+++ b/arch/arm/boot/dts/sunxi-h3-h5.dtsi
@@ -836,6 +836,15 @@ rtc: rtc@1f00000 {
#clock-cells = <1>;
};
+ r_intc: interrupt-controller@1f00c00 {
+ compatible = "allwinner,sun8i-h3-r-intc",
+ "allwinner,sun6i-a31-r-intc";
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ reg = <0x01f00c00 0x400>;
+ interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
r_ccu: clock@1f01400 {
compatible = "allwinner,sun8i-h3-r-ccu";
reg = <0x01f01400 0x100>;
--
2.24.1
^ permalink raw reply related
* [PATCH v2 1/9] irqchip/sun6i-r: Use a stacked irqchip driver
From: Samuel Holland @ 2020-05-25 4:12 UTC (permalink / raw)
To: Thomas Gleixner, Jason Cooper, Marc Zyngier, Rob Herring,
Maxime Ripard, Chen-Yu Tsai, Russell King, Catalin Marinas,
Will Deacon
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-sunxi,
Samuel Holland
In-Reply-To: <20200525041302.51213-1-samuel@sholland.org>
The R_INTC in the A31 and newer sun8i/sun50i SoCs is more similar to the
original sun4i interrupt controller than the sun7i/sun9i NMI controller.
It is used for two distinct purposes:
1) To control the trigger, latch, and mask for the NMI input pin
2) To provide the interrupt input for the ARISC coprocessor
As this interrupt controller is not documented, information about it
comes from vendor-provided ARISC firmware and from experimentation.
Like the original sun4i interrupt controller, it has:
- A VECTOR_REG at 0x00 (configurable via the BASE_ADDR_REG at 0x04)
- A NMI_CTRL_REG, PENDING_REG, and ENABLE_REG as used by both the
sun4i and sunxi-nmi drivers
- A MASK_REG at 0x50
- A RESP_REG at 0x60
Differences from the sun4i interrupt controller appear to be:
- It is only known to have one register of each kind (max 32 inputs)
- There is no FIQ-related logic
- There is no interrupt priority logic
In order to fulfill its two purposes, this hardware block combines two
types of IRQs. First, the NMI pin is routed to the "IRQ 0" input on this
chip, with a trigger type controlled by the NMI_CTRL_REG. The "IRQ 0
pending" output from this chip, if enabled, is then routed to a SPI IRQ
input on the GIC, as IRQ_TYPE_LEVEL_HIGH. In other words, bit 0 of
ENABLE_REG *does* affect the NMI IRQ seen at the GIC.
The NMI is then followed by a contiguous block of (at least) 15 IRQ
inputs that are connected in parallel to both R_INTC and the GIC. Or
in other words, the other bits of ENABLE_REG *do not* affect the IRQs
seen at the GIC.
Finally, the global "IRQ pending" output from R_INTC, after being masked
by MASK_REG and RESP_REG, is connected to the "external interrupt" input
of the ARISC CPU (an OR1200). This path is not relevant to Linux.
Because of the 1:1 correspondence between R_INTC and GIC inputs, this is
a perfect scenario for using a stacked irqchip driver. We want to hook
into enabling/disabling IRQs to add more features to the GIC
(specifically to allow masking the NMI and setting its trigger type),
but we don't need to actually handle the IRQ in this driver.
And since R_INTC is in the always-on power domain, and its output is
connected directly in to the power management coprocessor, a stacked
irqchip driver provides a simple way to add wakeup support to this set
of IRQs. That is a future patch; for now, just the NMI is moved over.
This driver keeps the same DT binding as the existing driver. The
"interrupt" property of the R_INTC node is used to determine 1) the
offset between GIC and R_INTC hwirq numbers and 2) the type of trigger
between the R_INTC "IRQ 0 pending" output and the GIC NMI input.
This commit mostly reverts commit 173bda53b340 ("irqchip/sunxi-nmi:
Support sun6i-a31-r-intc compatible").
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
arch/arm/mach-sunxi/Kconfig | 4 +
arch/arm64/Kconfig.platforms | 2 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-sun6i-r.c | 216 ++++++++++++++++++++++++++++++++
drivers/irqchip/irq-sunxi-nmi.c | 26 +---
5 files changed, 226 insertions(+), 23 deletions(-)
create mode 100644 drivers/irqchip/irq-sun6i-r.c
diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
index eeadb1a4dcfe..216b5954d6a9 100644
--- a/arch/arm/mach-sunxi/Kconfig
+++ b/arch/arm/mach-sunxi/Kconfig
@@ -30,6 +30,8 @@ config MACH_SUN6I
bool "Allwinner A31 (sun6i) SoCs support"
default ARCH_SUNXI
select ARM_GIC
+ select IRQ_DOMAIN_HIERARCHY
+ select IRQ_FASTEOI_HIERARCHY_HANDLERS
select MFD_SUN6I_PRCM
select SUN5I_HSTIMER
@@ -46,6 +48,8 @@ config MACH_SUN8I
bool "Allwinner sun8i Family SoCs support"
default ARCH_SUNXI
select ARM_GIC
+ select IRQ_DOMAIN_HIERARCHY
+ select IRQ_FASTEOI_HIERARCHY_HANDLERS
select MFD_SUN6I_PRCM
config MACH_SUN9I
diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index 55d70cfe0f9e..b9c3a7118a2c 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -17,6 +17,8 @@ config ARCH_SUNXI
bool "Allwinner sunxi 64-bit SoC Family"
select ARCH_HAS_RESET_CONTROLLER
select GENERIC_IRQ_CHIP
+ select IRQ_DOMAIN_HIERARCHY
+ select IRQ_FASTEOI_HIERARCHY_HANDLERS
select PINCTRL
select RESET_CONTROLLER
help
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 37bbe39bf909..c9692bdaabfa 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_OR1K_PIC) += irq-or1k-pic.o
obj-$(CONFIG_ORION_IRQCHIP) += irq-orion.o
obj-$(CONFIG_OMAP_IRQCHIP) += irq-omap-intc.o
obj-$(CONFIG_ARCH_SUNXI) += irq-sun4i.o
+obj-$(CONFIG_ARCH_SUNXI) += irq-sun6i-r.o
obj-$(CONFIG_ARCH_SUNXI) += irq-sunxi-nmi.o
obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o
obj-$(CONFIG_ARM_GIC) += irq-gic.o irq-gic-common.o
diff --git a/drivers/irqchip/irq-sun6i-r.c b/drivers/irqchip/irq-sun6i-r.c
new file mode 100644
index 000000000000..f8bfa5515f20
--- /dev/null
+++ b/drivers/irqchip/irq-sun6i-r.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Allwinner A31 and newer SoCs R_INTC driver
+//
+
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqdomain.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+#define NMI_HWIRQ 0
+#define NMI_HWIRQ_BIT BIT(NMI_HWIRQ)
+
+#define SUN6I_R_INTC_NR_IRQS 16
+
+#define SUN6I_R_INTC_NMI_CTRL 0x0c
+#define SUN6I_R_INTC_PENDING 0x10
+#define SUN6I_R_INTC_ENABLE 0x40
+
+static void __iomem *base;
+static irq_hw_number_t parent_offset;
+static u32 parent_type;
+
+static struct irq_chip sun6i_r_intc_edge;
+static struct irq_chip sun6i_r_intc_level;
+
+static void sun6i_r_intc_nmi_ack(void)
+{
+ /*
+ * The NMI IRQ channel has a latch, separate from its trigger.
+ * This latch must be cleared to clear the output to the GIC.
+ */
+ writel_relaxed(NMI_HWIRQ_BIT, base + SUN6I_R_INTC_PENDING);
+}
+
+static void sun6i_r_intc_irq_ack(struct irq_data *data)
+{
+ if (data->hwirq == NMI_HWIRQ)
+ sun6i_r_intc_nmi_ack();
+}
+
+static void sun6i_r_intc_irq_eoi(struct irq_data *data)
+{
+ if (data->hwirq == NMI_HWIRQ)
+ sun6i_r_intc_nmi_ack();
+
+ irq_chip_eoi_parent(data);
+}
+
+static int sun6i_r_intc_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ /*
+ * Only the NMI IRQ is routed through this interrupt controller on its
+ * way to the GIC. Other IRQs are routed to the GIC in parallel and
+ * must have a trigger type appropriate for the GIC.
+ *
+ * The "External NMI" input to the GIC actually comes from bit 0 of
+ * this device's PENDING register. So the IRQ type of the NMI, as seen
+ * by the GIC, does not depend on the IRQ type of the NMI pin itself.
+ */
+ if (data->hwirq == NMI_HWIRQ) {
+ u32 nmi_src_type;
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_LOW:
+ nmi_src_type = 0;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ nmi_src_type = 1;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ nmi_src_type = 2;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ nmi_src_type = 3;
+ break;
+ default:
+ pr_err("%pOF: invalid trigger type %d for IRQ %d\n",
+ irq_domain_get_of_node(data->domain), type,
+ data->irq);
+ return -EBADR;
+ }
+
+ if (type & IRQ_TYPE_EDGE_BOTH) {
+ irq_set_chip_handler_name_locked(data,
+ &sun6i_r_intc_edge,
+ handle_fasteoi_ack_irq,
+ NULL);
+ } else {
+ irq_set_chip_handler_name_locked(data,
+ &sun6i_r_intc_level,
+ handle_fasteoi_irq,
+ NULL);
+ }
+
+ writel_relaxed(nmi_src_type, base + SUN6I_R_INTC_NMI_CTRL);
+
+ /* Send the R_INTC -> GIC trigger type to the GIC driver. */
+ type = parent_type;
+ }
+
+ return irq_chip_set_type_parent(data, type);
+}
+
+static struct irq_chip sun6i_r_intc_edge = {
+ .name = "sun6i-r-intc",
+ .irq_ack = sun6i_r_intc_irq_ack,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_set_type = sun6i_r_intc_irq_set_type,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent,
+ .flags = IRQCHIP_SET_TYPE_MASKED,
+};
+
+static struct irq_chip sun6i_r_intc_level = {
+ .name = "sun6i-r-intc",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = sun6i_r_intc_irq_eoi,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_set_type = sun6i_r_intc_irq_set_type,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent,
+ .flags = IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_EOI_THREADED,
+};
+
+static int sun6i_r_intc_domain_alloc(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct irq_fwspec *fwspec = arg;
+ struct irq_fwspec gic_fwspec;
+ irq_hw_number_t hwirq;
+ unsigned int type;
+ int i, ret;
+
+ ret = irq_domain_translate_twocell(domain, fwspec, &hwirq, &type);
+ if (ret)
+ return ret;
+ if (hwirq + nr_irqs > SUN6I_R_INTC_NR_IRQS)
+ return -EINVAL;
+
+ /* Construct a GIC-compatible fwspec from this fwspec. */
+ gic_fwspec = (struct irq_fwspec) {
+ .fwnode = domain->parent->fwnode,
+ .param_count = 3,
+ .param = { GIC_SPI, parent_offset + hwirq, type },
+ };
+
+ for (i = 0; i < nr_irqs; ++i)
+ irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+ &sun6i_r_intc_level, NULL);
+
+ return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_fwspec);
+}
+
+static const struct irq_domain_ops sun6i_r_intc_domain_ops = {
+ .translate = irq_domain_translate_twocell,
+ .alloc = sun6i_r_intc_domain_alloc,
+ .free = irq_domain_free_irqs_common,
+};
+
+static int __init sun6i_r_intc_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct irq_domain *domain, *parent_domain;
+ struct of_phandle_args parent_irq;
+ int ret;
+
+ /* Extract the R_INTC -> GIC mapping from the OF node. */
+ ret = of_irq_parse_one(node, 0, &parent_irq);
+ if (ret)
+ return ret;
+ if (parent_irq.args_count != 3 || parent_irq.args[0] != GIC_SPI)
+ return -EINVAL;
+ parent_offset = parent_irq.args[1];
+ parent_type = parent_irq.args[2];
+
+ parent_domain = irq_find_host(parent);
+ if (!parent_domain) {
+ pr_err("%pOF: Failed to obtain parent domain\n", node);
+ return -ENXIO;
+ }
+
+ base = of_io_request_and_map(node, 0, NULL);
+ if (IS_ERR(base)) {
+ pr_err("%pOF: Failed to map MMIO region\n", node);
+ return PTR_ERR(base);
+ }
+
+ domain = irq_domain_add_hierarchy(parent_domain, 0,
+ SUN6I_R_INTC_NR_IRQS, node,
+ &sun6i_r_intc_domain_ops, NULL);
+ if (!domain) {
+ pr_err("%pOF: Failed to allocate domain\n", node);
+ iounmap(base);
+ return -ENOMEM;
+ }
+
+ /* Clear and enable the NMI. */
+ writel_relaxed(NMI_HWIRQ_BIT, base + SUN6I_R_INTC_PENDING);
+ writel_relaxed(NMI_HWIRQ_BIT, base + SUN6I_R_INTC_ENABLE);
+
+ return 0;
+}
+IRQCHIP_DECLARE(sun6i_r_intc, "allwinner,sun6i-a31-r-intc", sun6i_r_intc_init);
diff --git a/drivers/irqchip/irq-sunxi-nmi.c b/drivers/irqchip/irq-sunxi-nmi.c
index a412b5d5d0fa..9f2bd0c5d289 100644
--- a/drivers/irqchip/irq-sunxi-nmi.c
+++ b/drivers/irqchip/irq-sunxi-nmi.c
@@ -27,18 +27,12 @@
#define SUNXI_NMI_IRQ_BIT BIT(0)
-#define SUN6I_R_INTC_CTRL 0x0c
-#define SUN6I_R_INTC_PENDING 0x10
-#define SUN6I_R_INTC_ENABLE 0x40
-
/*
* For deprecated sun6i-a31-sc-nmi compatible.
- * Registers are offset by 0x0c.
*/
-#define SUN6I_R_INTC_NMI_OFFSET 0x0c
-#define SUN6I_NMI_CTRL (SUN6I_R_INTC_CTRL - SUN6I_R_INTC_NMI_OFFSET)
-#define SUN6I_NMI_PENDING (SUN6I_R_INTC_PENDING - SUN6I_R_INTC_NMI_OFFSET)
-#define SUN6I_NMI_ENABLE (SUN6I_R_INTC_ENABLE - SUN6I_R_INTC_NMI_OFFSET)
+#define SUN6I_NMI_CTRL 0x00
+#define SUN6I_NMI_PENDING 0x04
+#define SUN6I_NMI_ENABLE 0x34
#define SUN7I_NMI_CTRL 0x00
#define SUN7I_NMI_PENDING 0x04
@@ -61,12 +55,6 @@ struct sunxi_sc_nmi_reg_offs {
u32 enable;
};
-static const struct sunxi_sc_nmi_reg_offs sun6i_r_intc_reg_offs __initconst = {
- .ctrl = SUN6I_R_INTC_CTRL,
- .pend = SUN6I_R_INTC_PENDING,
- .enable = SUN6I_R_INTC_ENABLE,
-};
-
static const struct sunxi_sc_nmi_reg_offs sun6i_reg_offs __initconst = {
.ctrl = SUN6I_NMI_CTRL,
.pend = SUN6I_NMI_PENDING,
@@ -232,14 +220,6 @@ static int __init sunxi_sc_nmi_irq_init(struct device_node *node,
return ret;
}
-static int __init sun6i_r_intc_irq_init(struct device_node *node,
- struct device_node *parent)
-{
- return sunxi_sc_nmi_irq_init(node, &sun6i_r_intc_reg_offs);
-}
-IRQCHIP_DECLARE(sun6i_r_intc, "allwinner,sun6i-a31-r-intc",
- sun6i_r_intc_irq_init);
-
static int __init sun6i_sc_nmi_irq_init(struct device_node *node,
struct device_node *parent)
{
--
2.24.1
^ permalink raw reply related
* [PATCH v2 5/9] ARM: dts: sunxi: h3/h5: Move wakeup-capable IRQs to r_intc
From: Samuel Holland @ 2020-05-25 4:12 UTC (permalink / raw)
To: Thomas Gleixner, Jason Cooper, Marc Zyngier, Rob Herring,
Maxime Ripard, Chen-Yu Tsai, Russell King, Catalin Marinas,
Will Deacon
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-sunxi,
Samuel Holland
In-Reply-To: <20200525041302.51213-1-samuel@sholland.org>
All IRQs that can be used to wake up the system must be routed through
r_intc, so they are visible to firmware while the system is suspended.
For the H3/H5, r_intc IRQ numbers are offset by 32 from the GIC IRQ
numbers.
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
arch/arm/boot/dts/sunxi-h3-h5.dtsi | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/arch/arm/boot/dts/sunxi-h3-h5.dtsi b/arch/arm/boot/dts/sunxi-h3-h5.dtsi
index 94f648ad1c9e..93e7ce60a64b 100644
--- a/arch/arm/boot/dts/sunxi-h3-h5.dtsi
+++ b/arch/arm/boot/dts/sunxi-h3-h5.dtsi
@@ -829,8 +829,9 @@ hdmi_phy: hdmi-phy@1ef0000 {
rtc: rtc@1f00000 {
/* compatible is in per SoC .dtsi file */
reg = <0x01f00000 0x400>;
- interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <8 IRQ_TYPE_LEVEL_HIGH>,
+ <9 IRQ_TYPE_LEVEL_HIGH>;
clock-output-names = "osc32k", "osc32k-out", "iosc";
clocks = <&osc32k>;
#clock-cells = <1>;
@@ -865,7 +866,8 @@ ir: ir@1f02000 {
clocks = <&r_ccu CLK_APB0_IR>, <&r_ccu CLK_IR>;
clock-names = "apb", "ir";
resets = <&r_ccu RST_APB0_IR>;
- interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <5 IRQ_TYPE_LEVEL_HIGH>;
reg = <0x01f02000 0x400>;
status = "disabled";
};
@@ -886,7 +888,8 @@ r_i2c: i2c@1f02400 {
r_pio: pinctrl@1f02c00 {
compatible = "allwinner,sun8i-h3-r-pinctrl";
reg = <0x01f02c00 0x400>;
- interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <13 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&r_ccu CLK_APB0_PIO>, <&osc24M>, <&rtc 0>;
clock-names = "apb", "hosc", "losc";
gpio-controller;
--
2.24.1
^ permalink raw reply related
* [PATCH v2 8/9] arm64: dts: allwinner: h6: Fix indentation of IR node
From: Samuel Holland @ 2020-05-25 4:13 UTC (permalink / raw)
To: Thomas Gleixner, Jason Cooper, Marc Zyngier, Rob Herring,
Maxime Ripard, Chen-Yu Tsai, Russell King, Catalin Marinas,
Will Deacon
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-sunxi,
Samuel Holland
In-Reply-To: <20200525041302.51213-1-samuel@sholland.org>
This node was indented by two tabs when added instead of one.
Remove the extra tab.
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 22 ++++++++++----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
index b9ab7d8fa8af..d912188dc6ea 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
@@ -901,17 +901,17 @@ r_ir_rx_pin: r-ir-rx-pin {
};
r_ir: ir@7040000 {
- compatible = "allwinner,sun50i-h6-ir",
- "allwinner,sun6i-a31-ir";
- reg = <0x07040000 0x400>;
- interrupts = <GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&r_ccu CLK_R_APB1_IR>,
- <&r_ccu CLK_IR>;
- clock-names = "apb", "ir";
- resets = <&r_ccu RST_R_APB1_IR>;
- pinctrl-names = "default";
- pinctrl-0 = <&r_ir_rx_pin>;
- status = "disabled";
+ compatible = "allwinner,sun50i-h6-ir",
+ "allwinner,sun6i-a31-ir";
+ reg = <0x07040000 0x400>;
+ interrupts = <GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&r_ccu CLK_R_APB1_IR>,
+ <&r_ccu CLK_IR>;
+ clock-names = "apb", "ir";
+ resets = <&r_ccu RST_R_APB1_IR>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&r_ir_rx_pin>;
+ status = "disabled";
};
r_i2c: i2c@7081400 {
--
2.24.1
^ permalink raw reply related
* [PATCH v2 7/9] arm64: dts: allwinner: a64: Move wakeup-capable IRQs to r_intc
From: Samuel Holland @ 2020-05-25 4:13 UTC (permalink / raw)
To: Thomas Gleixner, Jason Cooper, Marc Zyngier, Rob Herring,
Maxime Ripard, Chen-Yu Tsai, Russell King, Catalin Marinas,
Will Deacon
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-sunxi,
Samuel Holland
In-Reply-To: <20200525041302.51213-1-samuel@sholland.org>
All IRQs that can be used to wake up the system must be routed through
r_intc, so they are visible to firmware while the system is suspended.
For the A64, r_intc IRQ numbers are offset by 32 from the GIC IRQ
numbers.
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
index c26cc1fcaffd..33f2ce0d93aa 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
@@ -1176,8 +1176,9 @@ rtc: rtc@1f00000 {
compatible = "allwinner,sun50i-a64-rtc",
"allwinner,sun8i-h3-rtc";
reg = <0x01f00000 0x400>;
- interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <8 IRQ_TYPE_LEVEL_HIGH>,
+ <9 IRQ_TYPE_LEVEL_HIGH>;
clock-output-names = "osc32k", "osc32k-out", "iosc";
clocks = <&osc32k>;
#clock-cells = <1>;
@@ -1227,7 +1228,8 @@ r_ir: ir@1f02000 {
clocks = <&r_ccu CLK_APB0_IR>, <&r_ccu CLK_IR>;
clock-names = "apb", "ir";
resets = <&r_ccu RST_APB0_IR>;
- interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <5 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&r_ir_rx_pin>;
status = "disabled";
@@ -1247,7 +1249,8 @@ r_pwm: pwm@1f03800 {
r_pio: pinctrl@1f02c00 {
compatible = "allwinner,sun50i-a64-r-pinctrl";
reg = <0x01f02c00 0x400>;
- interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <13 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&r_ccu CLK_APB0_PIO>, <&osc24M>, <&osc32k>;
clock-names = "apb", "hosc", "losc";
gpio-controller;
--
2.24.1
^ permalink raw reply related
* [PATCH v2 9/9] arm64: dts: allwinner: h6: Move wakeup-capable IRQs to r_intc
From: Samuel Holland @ 2020-05-25 4:13 UTC (permalink / raw)
To: Thomas Gleixner, Jason Cooper, Marc Zyngier, Rob Herring,
Maxime Ripard, Chen-Yu Tsai, Russell King, Catalin Marinas,
Will Deacon
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-sunxi,
Samuel Holland
In-Reply-To: <20200525041302.51213-1-samuel@sholland.org>
All IRQs that can be used to wake up the system must be routed through
r_intc, so they are visible to firmware while the system is suspended.
For the H6, r_intc IRQ numbers are offset by 96 from the GIC IRQ
numbers.
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
index d912188dc6ea..188699f47dea 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
@@ -844,8 +844,9 @@ tcon_tv_out_tcon_top: endpoint@1 {
rtc: rtc@7000000 {
compatible = "allwinner,sun50i-h6-rtc";
reg = <0x07000000 0x400>;
- interrupts = <GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <5 IRQ_TYPE_LEVEL_HIGH>,
+ <6 IRQ_TYPE_LEVEL_HIGH>;
clock-output-names = "osc32k", "osc32k-out", "iosc";
#clock-cells = <1>;
};
@@ -880,8 +881,9 @@ r_intc: interrupt-controller@7021000 {
r_pio: pinctrl@7022000 {
compatible = "allwinner,sun50i-h6-r-pinctrl";
reg = <0x07022000 0x400>;
- interrupts = <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 111 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = < 9 IRQ_TYPE_LEVEL_HIGH>,
+ <15 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&r_ccu CLK_R_APB1>, <&osc24M>, <&rtc 0>;
clock-names = "apb", "hosc", "losc";
gpio-controller;
@@ -904,7 +906,8 @@ r_ir: ir@7040000 {
compatible = "allwinner,sun50i-h6-ir",
"allwinner,sun6i-a31-ir";
reg = <0x07040000 0x400>;
- interrupts = <GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <13 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&r_ccu CLK_R_APB1_IR>,
<&r_ccu CLK_IR>;
clock-names = "apb", "ir";
--
2.24.1
^ permalink raw reply related
* [PATCH v2 6/9] ARM: dts: sunxi: a83t: Move wakeup-capable IRQs to r_intc
From: Samuel Holland @ 2020-05-25 4:12 UTC (permalink / raw)
To: Thomas Gleixner, Jason Cooper, Marc Zyngier, Rob Herring,
Maxime Ripard, Chen-Yu Tsai, Russell King, Catalin Marinas,
Will Deacon
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-sunxi,
Samuel Holland
In-Reply-To: <20200525041302.51213-1-samuel@sholland.org>
All IRQs that can be used to wake up the system must be routed through
r_intc, so they are visible to firmware while the system is suspended.
For the A83T, r_intc IRQ numbers are offset by 32 from the GIC IRQ
numbers.
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
arch/arm/boot/dts/sun8i-a83t.dtsi | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/arch/arm/boot/dts/sun8i-a83t.dtsi b/arch/arm/boot/dts/sun8i-a83t.dtsi
index 655404d6d3a3..fcfc573eb96d 100644
--- a/arch/arm/boot/dts/sun8i-a83t.dtsi
+++ b/arch/arm/boot/dts/sun8i-a83t.dtsi
@@ -1130,7 +1130,8 @@ r_cir: ir@1f02000 {
clocks = <&r_ccu CLK_APB0_IR>, <&r_ccu CLK_IR>;
clock-names = "apb", "ir";
resets = <&r_ccu RST_APB0_IR>;
- interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <5 IRQ_TYPE_LEVEL_HIGH>;
reg = <0x01f02000 0x400>;
pinctrl-names = "default";
pinctrl-0 = <&r_cir_pin>;
@@ -1140,14 +1141,16 @@ r_cir: ir@1f02000 {
r_lradc: lradc@1f03c00 {
compatible = "allwinner,sun8i-a83t-r-lradc";
reg = <0x01f03c00 0x100>;
- interrupts = <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <10 IRQ_TYPE_LEVEL_HIGH>;
status = "disabled";
};
r_pio: pinctrl@1f02c00 {
compatible = "allwinner,sun8i-a83t-r-pinctrl";
reg = <0x01f02c00 0x400>;
- interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <13 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&r_ccu CLK_APB0_PIO>, <&osc24M>,
<&osc16Md512>;
clock-names = "apb", "hosc", "losc";
--
2.24.1
^ permalink raw reply related
* [PATCH v2 3/9] dt-bindings: irq: Add a compatible for the H3 R_INTC
From: Samuel Holland @ 2020-05-25 4:12 UTC (permalink / raw)
To: Thomas Gleixner, Jason Cooper, Marc Zyngier, Rob Herring,
Maxime Ripard, Chen-Yu Tsai, Russell King, Catalin Marinas,
Will Deacon
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-sunxi,
Samuel Holland
In-Reply-To: <20200525041302.51213-1-samuel@sholland.org>
The Allwinner H3 SoC contains an R_INTC that is, as far as we know,
compatible with the R_INTC present in other sun8i/sun50i SoCs starting
with the A31. Since the R_INTC hardware is undocumented, introduce a new
compatible for the R_INTC variant in this SoC, in case there turns out
to be some difference.
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
.../allwinner,sun7i-a20-sc-nmi.yaml | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/Documentation/devicetree/bindings/interrupt-controller/allwinner,sun7i-a20-sc-nmi.yaml b/Documentation/devicetree/bindings/interrupt-controller/allwinner,sun7i-a20-sc-nmi.yaml
index cf09055da78b..973fe5d17af0 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/allwinner,sun7i-a20-sc-nmi.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/allwinner,sun7i-a20-sc-nmi.yaml
@@ -26,15 +26,13 @@ properties:
- const: allwinner,sun6i-a31-sc-nmi
deprecated: true
- const: allwinner,sun7i-a20-sc-nmi
- - items:
- - const: allwinner,sun8i-a83t-r-intc
- - const: allwinner,sun6i-a31-r-intc
- const: allwinner,sun9i-a80-sc-nmi
- items:
- - const: allwinner,sun50i-a64-r-intc
- - const: allwinner,sun6i-a31-r-intc
- - items:
- - const: allwinner,sun50i-h6-r-intc
+ - enum:
+ - allwinner,sun8i-a83t-r-intc
+ - allwinner,sun8i-h3-r-intc
+ - allwinner,sun50i-a64-r-intc
+ - allwinner,sun50i-h6-r-intc
- const: allwinner,sun6i-a31-r-intc
reg:
--
2.24.1
^ permalink raw reply related
* [PATCH v2 0/9] sunxi: Support IRQ wakeup from deep sleep
From: Samuel Holland @ 2020-05-25 4:12 UTC (permalink / raw)
To: Thomas Gleixner, Jason Cooper, Marc Zyngier, Rob Herring,
Maxime Ripard, Chen-Yu Tsai, Russell King, Catalin Marinas,
Will Deacon
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-sunxi,
Samuel Holland
Allwinner sun8i/sun50i SoCs (A31 and newer) have two interrupt
controllers: GIC and R_INTC. GIC does not support wakeup. R_INTC handles
the external NMI pin, and provides 16-32 IRQs to the ARISC. At least the
first 16 of these correspond 1:1 to a block of GIC IRQs starting with
the NMI.
This series replaces the existing chained irqchip driver used only to
control the NMI, with a stacked irqchip driver that also provides wakeup
capability for those 16 IRQs. The idea is that we preconfigure the
ARISC's IRQ controller, and then the ARISC firmware knows to wake up as
soon as it receives an IRQ.
I went back and forth about updating the existing driver versus writing
a new one. Since the hardware really is different from the NMI-only
controller on the A20, ultimately I went with a new driver. It may be
useful to separately do the chained->stacked conversion on the sunxi-nmi
driver as well.
Patch 1 adds the new driver.
Patch 2 adds wakeup capability.
The other patches update the DT+bindings to use R_INTC where beneficial.
With appropriate firmware, this series allows waking from RTC, NMI/PMIC,
(power button, plugging in USB, etc.), and Port L GPIO (lid switch, BT,
WiFi, modem, etc.).
Changes from v1:
- Use writel_relaxed() instead if writel().
- Remove use of the MASK register, as it doesn't affect the NMI as seen
by the GIC. It only affects the IRQs seen by the coprocessor.
- Leave NMI_HWIRQ enabled at all times, since it can be masked at the
GIC level (removed .irq_enable and .irq_disable).
- Use .irq_ack vs .irq_eoi depending on the trigger type, to avoid
missing interrupts or double interrupts.
- Because of this change, the driver needs two "irq_chip"s, one
with .irq_eoi set to our function and one without.
- Also because of this, we need IRQ_FASTEOI_HIERARCHY_HANDLERS for
handle_fasteoi_ack_irq(), so our .irq_ack function gets called
while the GIC driver works as if handle_fasteoi_irq() was used.
- Inline the SUNXI_SRC_TYPE_* enum into sun6i_r_intc_irq_set_type().
- Add a comment explaining how the trigger type is used.
- Don't call irqd_set_trigger_type().
- Set IRQCHIP_SET_TYPE_MASKED to match the GIC (since flags from this
driver mask flags from that one).
- Set IRQCHIP_EOI_THREADED to avoid doubled level interrupts, since the
latch will be set again as long as the trigger is met.
- Replace sun6i_r_intc_domain_translate() with
irq_domain_translate_twocell().
- Use an enum for the device tree binding.
- Update commit messages for accuracy and typos.
Samuel Holland (9):
irqchip/sun6i-r: Use a stacked irqchip driver
irqchip/sun6i-r: Add wakeup support
dt-bindings: irq: Add a compatible for the H3 R_INTC
ARM: dts: sunxi: h3/h5: Add r_intc node
ARM: dts: sunxi: h3/h5: Move wakeup-capable IRQs to r_intc
ARM: dts: sunxi: a83t: Move wakeup-capable IRQs to r_intc
arm64: dts: allwinner: a64: Move wakeup-capable IRQs to r_intc
arm64: dts: allwinner: h6: Fix indentation of IR node
arm64: dts: allwinner: h6: Move wakeup-capable IRQs to r_intc
.../allwinner,sun7i-a20-sc-nmi.yaml | 12 +-
arch/arm/boot/dts/sun8i-a83t.dtsi | 9 +-
arch/arm/boot/dts/sunxi-h3-h5.dtsi | 20 +-
arch/arm/mach-sunxi/Kconfig | 4 +
arch/arm64/Kconfig.platforms | 2 +
arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi | 11 +-
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 33 ++-
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-sun6i-r.c | 267 ++++++++++++++++++
drivers/irqchip/irq-sunxi-nmi.c | 26 +-
10 files changed, 329 insertions(+), 56 deletions(-)
create mode 100644 drivers/irqchip/irq-sun6i-r.c
--
2.24.1
^ permalink raw reply
* Re: [PATCH 1/9] irqchip/sun6i-r: Switch to a stacked irqchip driver
From: Samuel Holland @ 2020-05-25 4:12 UTC (permalink / raw)
To: Marc Zyngier
Cc: Thomas Gleixner, Jason Cooper, Rob Herring, Mark Rutland,
Maxime Ripard, Chen-Yu Tsai, Russell King, Catalin Marinas,
Will Deacon, devicetree, linux-arm-kernel, linux-kernel,
linux-sunxi
In-Reply-To: <22bbdefdc0060e1eacb2c3a41dffebe4@kernel.org>
Hello, and thanks for the feedback!
I know this is quite the delay in responding; I wanted to make sure my
understanding of the hardware was as clear as possible before sending a v2.
After experimentation, I came up with a diagram describing the hardware
architecture, available here:
https://linux-sunxi.org/images/5/5c/R_INTC.png (PNG)
https://sholland.org/files/R_INTC_v2.svg (SVG)
Based on that, your feedback, and similar examples like the great explanation of
the robustness requirements in 6dd859508336 ("gpio: zynq: Fix IRQ handlers"), I
think v2 will work properly for both edge and level interrupts. I tested both
triggers, albeit with the same source of (level) interrupts connected to the NMI
pin.
On 1/20/20 4:52 AM, Marc Zyngier wrote:
> Hi Samuel,
>
> On 2020-01-13 05:49, Samuel Holland wrote:
>> The R_INTC in the A31 and newer sun8i/sun50i SoCs is more similar to the
>> original sun4i interrupt controller than the sun7i/sun9i NMI controller.
>> It is used for two distinct purposes:
>> 1) To control the trigger and mask for the NMI input pin
>> 2) To provide the interrupt input for the ARISC coprocessor
>>
>> As this interrupt controller is not documented, information about it
>> comes from reverse-engineering the BSP-provided ARISC firmware.
>>
>> Like the original sun4i interrupt controller, it has:
>> - A VECTOR_REG at 0x00 (configurable via the BASE_ADDR_REG at 0x04)
>> - A NMI_CTRL_REG, PENDING_REG, and ENABLE_REG as used by both the
>> sun4i and sunxi-nmi drivers
>> - A MASK_REG at 0x50
>>
>> Differences from the sun4i interrupt controller appear to be:
>> - It is only known to have one register of each kind (max 32 inputs)
>> - There is no FIQ-related logic
>> - There is no interrupt priority logic
>>
>> In order to fulfill its two purposes, this hardware block combines two
>> types of IRQs. First, the NMI pin is routed to the "IRQ 0" input on this
>> chip, with a trigger type controlled by the NMI_CTRL_REG. The (masked)
>> "IRQ 0 pending" output from this chip is then routed to a non-maskable
>> SPI IRQ input on the GIC, as IRQ_TYPE_LEVEL_HIGH. In other words, bit 0
>
> I object to the "non-maskable" wording here. It may be non-maskable
> at this irqchip level (and yet you seem to have code to that effect),
> but the GIC definitely should be able to mask things.
You're 100% correct here. I had thought IRQ 0 was non-maskable because the MASK
register didn't affect the IRQ being sent to the GIC. Disabling the IRQ via
GICD_ICENABLER does indeed work.
>> of ENABLE_REG and MASK_REG *do* affect the IRQs seen at the GIC.
>>
>> The NMI is then followed by a contiguous block of (at least) 15 IRQ
>> inputs that are connected *in parallel* to both R_INTC and the GIC. Or
>> in other words, the other bits of ENABLE_REG and MASK_REG *do not*
>> affect the IRQs seen at the GIC.
>>
>> Finally, the global "IRQ pending" output from R_INTC is connected to the
>> "external interrupt" input of the ARISC CPU (an OR1200).
>>
>> Because of the 1:1 correspondence between R_INTC and GIC inputs, this is
>> a perfect scenario for using a stacked irqchip driver. We want to hook
>> into enabling/disabling/masking IRQs to add more features to the GIC
>> (specifically to allow masking the NMI and setting its trigger type),
>> but we don't need to actually *handle* the IRQ.
>>
>> And since R_INTC is in the always-on power domain, and its output is
>> connected directly in to the power management coprocessor, a stacked
>> irqchip driver provides a simple way to add wakeup support to this set
>> of IRQs. That is a future patch; for now, just the NMI is moved over.
>>
>> This driver keeps the same DT binding as the existing driver. The
>> "interrupt" property of the R_INTC node is used to determine 1) the
>> offset between GIC and R_INTC hwirq numbers and 2) the type of trigger
>> between the R_INTC "IRQ 0 pending" output and the GIC NMI input.
>>
>> This commit mostly reverts commit 173bda53b340 ("irqchip/sunxi-nmi:
>> Support sun6i-a31-r-intc compatible").
>>
>> Signed-off-by: Samuel Holland <samuel@sholland.org>
>> ---
>> arch/arm/mach-sunxi/Kconfig | 1 +
>> arch/arm64/Kconfig.platforms | 1 +
>> drivers/irqchip/Makefile | 1 +
>> drivers/irqchip/irq-sun6i-r.c | 220 ++++++++++++++++++++++++++++++++
>> drivers/irqchip/irq-sunxi-nmi.c | 26 +---
>> 5 files changed, 226 insertions(+), 23 deletions(-)
>> create mode 100644 drivers/irqchip/irq-sun6i-r.c
>>
>> diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
>> index eeadb1a4dcfe..ef1cc25902b5 100644
>> --- a/arch/arm/mach-sunxi/Kconfig
>> +++ b/arch/arm/mach-sunxi/Kconfig
>> @@ -6,6 +6,7 @@ menuconfig ARCH_SUNXI
>> select CLKSRC_MMIO
>> select GENERIC_IRQ_CHIP
>> select GPIOLIB
>> + select IRQ_DOMAIN_HIERARCHY
>> select PINCTRL
>> select PM_OPP
>> select SUN4I_TIMER
>> diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
>> index 16d761475a86..d282d0a1d17d 100644
>> --- a/arch/arm64/Kconfig.platforms
>> +++ b/arch/arm64/Kconfig.platforms
>> @@ -17,6 +17,7 @@ config ARCH_SUNXI
>> bool "Allwinner sunxi 64-bit SoC Family"
>> select ARCH_HAS_RESET_CONTROLLER
>> select GENERIC_IRQ_CHIP
>> + select IRQ_DOMAIN_HIERARCHY
>> select PINCTRL
>> select RESET_CONTROLLER
>> help
>> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
>> index cc7c43932f16..41996d98c30a 100644
>> --- a/drivers/irqchip/Makefile
>> +++ b/drivers/irqchip/Makefile
>> @@ -24,6 +24,7 @@ obj-$(CONFIG_OR1K_PIC) += irq-or1k-pic.o
>> obj-$(CONFIG_ORION_IRQCHIP) += irq-orion.o
>> obj-$(CONFIG_OMAP_IRQCHIP) += irq-omap-intc.o
>> obj-$(CONFIG_ARCH_SUNXI) += irq-sun4i.o
>> +obj-$(CONFIG_ARCH_SUNXI) += irq-sun6i-r.o
>> obj-$(CONFIG_ARCH_SUNXI) += irq-sunxi-nmi.o
>> obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o
>> obj-$(CONFIG_ARM_GIC) += irq-gic.o irq-gic-common.o
>> diff --git a/drivers/irqchip/irq-sun6i-r.c b/drivers/irqchip/irq-sun6i-r.c
>> new file mode 100644
>> index 000000000000..37b6e9c60bf8
>> --- /dev/null
>> +++ b/drivers/irqchip/irq-sun6i-r.c
>> @@ -0,0 +1,220 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +//
>> +// Allwinner A31 and newer SoCs R_INTC driver
>> +//
>> +
>> +#include <linux/irq.h>
>> +#include <linux/irqchip.h>
>> +#include <linux/irqdomain.h>
>> +#include <linux/of.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_irq.h>
>> +
>> +#include <dt-bindings/interrupt-controller/arm-gic.h>
>> +
>> +#define NMI_HWIRQ 0
>> +
>> +#define SUN6I_R_INTC_NR_IRQS 16
>> +
>> +#define SUN6I_R_INTC_CTRL 0x0c
>> +#define SUN6I_R_INTC_PENDING 0x10
>> +#define SUN6I_R_INTC_ENABLE 0x40
>> +#define SUN6I_R_INTC_MASK 0x50
>> +
>> +enum {
>> + SUNXI_SRC_TYPE_LEVEL_LOW = 0,
>> + SUNXI_SRC_TYPE_EDGE_FALLING,
>> + SUNXI_SRC_TYPE_LEVEL_HIGH,
>> + SUNXI_SRC_TYPE_EDGE_RISING,
>> +};
>
> It is unusual to use an enum for values that get directly programmed
> into the HW.
These definitons match the existing driver this was split from. I will inline
these for v2.
>> +
>> +static void __iomem *base;
>> +static irq_hw_number_t parent_offset;
>> +static u32 parent_type;
>> +
>> +static void sun6i_r_intc_irq_enable(struct irq_data *data)
>> +{
>> + if (data->hwirq == NMI_HWIRQ)
>> + writel(BIT(NMI_HWIRQ), base + SUN6I_R_INTC_ENABLE);
>
> Please consider using _relaxed() accessors.
I've done this for v2.
>> +
>> + irq_chip_enable_parent(data);
>> +}
>> +
>> +static void sun6i_r_intc_irq_disable(struct irq_data *data)
>> +{
>> + if (data->hwirq == NMI_HWIRQ)
>> + writel(0, base + SUN6I_R_INTC_ENABLE);
>> +
>> + irq_chip_disable_parent(data);
>> +}
>> +
>> +static void sun6i_r_intc_irq_mask(struct irq_data *data)
>> +{
>> + if (data->hwirq == NMI_HWIRQ)
>> + writel(BIT(NMI_HWIRQ), base + SUN6I_R_INTC_MASK);
>> +
>> + irq_chip_mask_parent(data);
>> +}
>> +
>> +static void sun6i_r_intc_irq_unmask(struct irq_data *data)
>> +{
>> + if (data->hwirq == NMI_HWIRQ)
>> + writel(0, base + SUN6I_R_INTC_MASK);
>> +
>> + irq_chip_unmask_parent(data);
>> +}
>> +
>> +static void sun6i_r_intc_irq_eoi(struct irq_data *data)
>> +{
>> + if (data->hwirq == NMI_HWIRQ)
>> + writel(BIT(NMI_HWIRQ), base + SUN6I_R_INTC_PENDING);
>
> Are you sure about this? Clearing the pending bit is not quite an EOI.
> It won't hurt a level interrupt, but could be pretty deadly with
> edge signaling (you'd loose that interrupt). But does this register
> actually latch the input until you clear it? Or does it follow the
> level of its input?
For bit 0 (the only one Linux cares about), there is a latch. This latch gets
set whenever the IRQ is triggered (once for edge, continuously for level), and
it gets reset by writing 1 to PENDING.
What I've done for v2 is set this bit in .irq_ack for edge, and .irq_eoi for level.
>> +
>> + irq_chip_eoi_parent(data);
>> +}
>> +
>> +static int sun6i_r_intc_irq_set_type(struct irq_data *data, unsigned int type)
>> +{
>> + if (data->hwirq == NMI_HWIRQ) {
>> + u32 src_type;
>> +
>> + switch (type) {
>> + case IRQ_TYPE_EDGE_FALLING:
>> + src_type = SUNXI_SRC_TYPE_EDGE_FALLING;
>> + break;
>> + case IRQ_TYPE_EDGE_RISING:
>> + src_type = SUNXI_SRC_TYPE_EDGE_RISING;
>> + break;
>> + case IRQ_TYPE_LEVEL_HIGH:
>> + src_type = SUNXI_SRC_TYPE_LEVEL_HIGH;
>> + break;
>> + case IRQ_TYPE_NONE:
>
> What does "IRQ_TYPE_NONE" mean here?
It means that IRQ_TYPE_NONE was put in the specifier in the device tree (this is
copied from the other driver). Since this should never happen, I've removed this
case in v2.
>> + case IRQ_TYPE_LEVEL_LOW:
>> + src_type = SUNXI_SRC_TYPE_LEVEL_LOW;
>> + break;
>> + default:
>> + pr_err("%pOF: invalid trigger type %d for IRQ %d\n",
>> + irq_domain_get_of_node(data->domain), type,
>> + data->irq);
>> + return -EBADR;
>> + }
>> + writel(src_type, base + SUN6I_R_INTC_CTRL);
>> +
>> + irqd_set_trigger_type(data, type);
>
> It is odd to update this from a driver. Specially that you change it
> before finding out if the parent call has succeeded or not.
Yes, I'm not sure what I was doing there. I've removed this.
>> +
>> + /* Send the R_INTC -> GIC trigger type to the GIC driver. */
>> + type = parent_type;
>> + }
>> +
>> + return irq_chip_set_type_parent(data, type);
>
> Half of the above signaling modes are invalid for the GIC. Does this
> widget actually invert the signalling when the input is either
> level low or falling edge?
Yes. The signal sent to the GIC is effectively bit 0 of the PENDING register. So
it's a "1" when the IRQ is triggered, regardless of the physical pin trigger type.
>> +}
>> +
>> +static struct irq_chip sun6i_r_intc_chip = {
>> + .name = "sun6i-r-intc",
>> + .irq_enable = sun6i_r_intc_irq_enable,
>> + .irq_disable = sun6i_r_intc_irq_disable,
>> + .irq_mask = sun6i_r_intc_irq_mask,
>> + .irq_unmask = sun6i_r_intc_irq_unmask,
>
> What is the upshot of having both enable/disable and mask/unmask?
> Given that the GIC only supports the latter, I'd expect this driver
> to leave everything enabled, and only deal with mask/unmask.
This makes sense. I've done this for v2.
>> + .irq_eoi = sun6i_r_intc_irq_eoi,
>> + .irq_set_affinity = irq_chip_set_affinity_parent,
>> + .irq_retrigger = irq_chip_retrigger_hierarchy,
>> + .irq_set_type = sun6i_r_intc_irq_set_type,
>> + .irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent,
>
> Under which circumstances do you expect this to be called?
Under the same circumstances as the underlying GIC callback. It's my
understanding that irq_chip hooks always go to the outermost irqdomain in the
hierarchy. So the only way the GIC driver functions get called is if I call them
here with irq_chip_*_parent. Presumably, all of the GIC functions are there for
a reason, so I should expose them.
The same appears to be the case for .flags: flags from the inner irqdomains have
to be duplicated here, because the core interrupt handling code only looks at
the flags of desc->irq_data.chip->flags, which is the outermost irqdomain in the
hierarchy.
>> +};
>> +
>> +static int sun6i_r_intc_domain_translate(struct irq_domain *domain,
>> + struct irq_fwspec *fwspec,
>> + unsigned long *hwirq,
>> + unsigned int *type)
>> +{
>> + if (!is_of_node(fwspec->fwnode) || fwspec->param_count != 2)
>> + return -EINVAL;
>> +
>> + *hwirq = fwspec->param[0];
>> + *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
>> +
>> + return 0;
>> +}
>> +
>> +static int sun6i_r_intc_domain_alloc(struct irq_domain *domain,
>> + unsigned int virq,
>> + unsigned int nr_irqs, void *arg)
>> +{
>> + struct irq_fwspec *fwspec = arg;
>> + struct irq_fwspec gic_fwspec;
>> + irq_hw_number_t hwirq;
>> + unsigned int type;
>> + int i, ret;
>> +
>> + ret = sun6i_r_intc_domain_translate(domain, fwspec, &hwirq, &type);
>> + if (ret)
>> + return ret;
>> + if (hwirq + nr_irqs > SUN6I_R_INTC_NR_IRQS)
>> + return -EINVAL;
>> +
>> + /* Construct a GIC-compatible fwspec from this fwspec. */
>> + gic_fwspec = (struct irq_fwspec) {
>> + .fwnode = domain->parent->fwnode,
>> + .param_count = 3,
>> + .param = { GIC_SPI, parent_offset + hwirq, type },
>
> Same problem here. The GIC only supports level-high and rising-edge
> for SPIs.
This actually doesn't cause any errors. The only check that the GIC does on the
"type" parameter during .translate is verifying that it is not IRQ_TYPE_NONE,
and no additional checks are done during .alloc. In fact, nothing seems to
really care about "type" during .alloc. They just want "hwirq", and it's
convenient to use .translate to get that.
>> + };
>> +
>> + for (i = 0; i < nr_irqs; ++i)
>> + irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
>> + &sun6i_r_intc_chip, NULL);
>> +
>> + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_fwspec);
>> +}
>> +
>> +static const struct irq_domain_ops sun6i_r_intc_domain_ops = {
>> + .translate = sun6i_r_intc_domain_translate,
>> + .alloc = sun6i_r_intc_domain_alloc,
>> + .free = irq_domain_free_irqs_common,
>> +};
>> +
>> +static int __init sun6i_r_intc_init(struct device_node *node,
>> + struct device_node *parent)
>> +{
>> + struct irq_domain *domain, *parent_domain;
>> + struct of_phandle_args parent_irq;
>> + int ret;
>> +
>> + /* Extract the R_INTC -> GIC mapping from the OF node. */
>> + ret = of_irq_parse_one(node, 0, &parent_irq);
>> + if (ret)
>> + return ret;
>> + if (parent_irq.args_count != 3 || parent_irq.args[0] != GIC_SPI)
>> + return -EINVAL;
>> + parent_offset = parent_irq.args[1];
>> + parent_type = parent_irq.args[2];
>> +
>> + parent_domain = irq_find_host(parent);
>> + if (!parent_domain) {
>> + pr_err("%pOF: Failed to obtain parent domain\n", node);
>> + return -ENXIO;
>> + }
>> +
>> + base = of_io_request_and_map(node, 0, NULL);
>> + if (IS_ERR(base)) {
>> + pr_err("%pOF: Failed to map MMIO region\n", node);
>> + return PTR_ERR(base);
>> + }
>> +
>> + domain = irq_domain_add_hierarchy(parent_domain, 0,
>> + SUN6I_R_INTC_NR_IRQS, node,
>> + &sun6i_r_intc_domain_ops, NULL);
>> + if (!domain) {
>> + pr_err("%pOF: Failed to allocate domain\n", node);
>> + iounmap(base);
>> + return -ENOMEM;
>> + }
>> +
>> + /* Disable and unmask all interrupts. */
>> + writel(0, base + SUN6I_R_INTC_ENABLE);
>> + writel(0, base + SUN6I_R_INTC_MASK);
>> +
>> + /* Clear any pending interrupts. */
>> + writel(~0, base + SUN6I_R_INTC_PENDING);
>> +
>> + return 0;
>> +}
>> +IRQCHIP_DECLARE(sun6i_r_intc, "allwinner,sun6i-a31-r-intc", sun6i_r_intc_init);
>> diff --git a/drivers/irqchip/irq-sunxi-nmi.c b/drivers/irqchip/irq-sunxi-nmi.c
>> index a412b5d5d0fa..9f2bd0c5d289 100644
>> --- a/drivers/irqchip/irq-sunxi-nmi.c
>> +++ b/drivers/irqchip/irq-sunxi-nmi.c
>> @@ -27,18 +27,12 @@
>>
>> #define SUNXI_NMI_IRQ_BIT BIT(0)
>>
>> -#define SUN6I_R_INTC_CTRL 0x0c
>> -#define SUN6I_R_INTC_PENDING 0x10
>> -#define SUN6I_R_INTC_ENABLE 0x40
>> -
>> /*
>> * For deprecated sun6i-a31-sc-nmi compatible.
>> - * Registers are offset by 0x0c.
>> */
>> -#define SUN6I_R_INTC_NMI_OFFSET 0x0c
>> -#define SUN6I_NMI_CTRL (SUN6I_R_INTC_CTRL - SUN6I_R_INTC_NMI_OFFSET)
>> -#define SUN6I_NMI_PENDING (SUN6I_R_INTC_PENDING - SUN6I_R_INTC_NMI_OFFSET)
>> -#define SUN6I_NMI_ENABLE (SUN6I_R_INTC_ENABLE - SUN6I_R_INTC_NMI_OFFSET)
>> +#define SUN6I_NMI_CTRL 0x00
>> +#define SUN6I_NMI_PENDING 0x04
>> +#define SUN6I_NMI_ENABLE 0x34
>>
>> #define SUN7I_NMI_CTRL 0x00
>> #define SUN7I_NMI_PENDING 0x04
>> @@ -61,12 +55,6 @@ struct sunxi_sc_nmi_reg_offs {
>> u32 enable;
>> };
>>
>> -static const struct sunxi_sc_nmi_reg_offs sun6i_r_intc_reg_offs __initconst = {
>> - .ctrl = SUN6I_R_INTC_CTRL,
>> - .pend = SUN6I_R_INTC_PENDING,
>> - .enable = SUN6I_R_INTC_ENABLE,
>> -};
>> -
>> static const struct sunxi_sc_nmi_reg_offs sun6i_reg_offs __initconst = {
>> .ctrl = SUN6I_NMI_CTRL,
>> .pend = SUN6I_NMI_PENDING,
>> @@ -232,14 +220,6 @@ static int __init sunxi_sc_nmi_irq_init(struct
>> device_node *node,
>> return ret;
>> }
>>
>> -static int __init sun6i_r_intc_irq_init(struct device_node *node,
>> - struct device_node *parent)
>> -{
>> - return sunxi_sc_nmi_irq_init(node, &sun6i_r_intc_reg_offs);
>> -}
>> -IRQCHIP_DECLARE(sun6i_r_intc, "allwinner,sun6i-a31-r-intc",
>> - sun6i_r_intc_irq_init);
>> -
>> static int __init sun6i_sc_nmi_irq_init(struct device_node *node,
>> struct device_node *parent)
>> {
>
> Thanks,
>
> M.
Thanks again for your review,
Samuel
^ permalink raw reply
* [PATCH v5 4/8] dt-bindings: display: panel: Add ilitek ili9341 panel bindings
From: dillon.minfei @ 2020-05-25 3:45 UTC (permalink / raw)
To: linus.walleij, broonie
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-spi,
linux-stm32, dri-devel, linux-clk, dillon min
In-Reply-To: <1590378348-8115-1-git-send-email-dillon.minfei@gmail.com>
From: dillon min <dillon.minfei@gmail.com>
Add documentation for "ilitek,ili9341" panel.
Signed-off-by: dillon min <dillon.minfei@gmail.com>
---
.../bindings/display/panel/ilitek,ili9341.yaml | 69 ++++++++++++++++++++++
1 file changed, 69 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/panel/ilitek,ili9341.yaml
diff --git a/Documentation/devicetree/bindings/display/panel/ilitek,ili9341.yaml b/Documentation/devicetree/bindings/display/panel/ilitek,ili9341.yaml
new file mode 100644
index 0000000..2172f88
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/ilitek,ili9341.yaml
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/panel/ilitek,ili9341.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ilitek-9341 Display Panel
+
+maintainers:
+ - Dillon Min <dillon.minfei@gmail.com>
+
+description: |
+ Ilitek ILI9341 TFT panel driver with SPI control bus
+ This is a driver for 320x240 TFT panels, accepting a rgb input
+ streams with 16 bits or 18 bits.
+
+allOf:
+ - $ref: panel-common.yaml#
+
+properties:
+ compatible:
+ items:
+ - enum:
+ # ili9341 240*320 Color on stm32f429-disco board
+ - st,sf-tc240t-9370-t
+ - const: ilitek,ili9341
+
+ reg: true
+
+ dc-gpios:
+ maxItems: 1
+ description: Display data/command selection (D/CX)
+
+ spi-3wire: true
+
+ spi-max-frequency:
+ const: 10000000
+
+ port: true
+
+additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - dc-gpios
+ - port
+
+examples:
+ - |+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ panel: display@0 {
+ compatible = "st,sf-tc240t-9370-t",
+ "ilitek,ili9341";
+ reg = <0>;
+ spi-3wire;
+ spi-max-frequency = <10000000>;
+ dc-gpios = <&gpiod 13 0>;
+ port {
+ panel_in: endpoint {
+ remote-endpoint = <&display_out>;
+ };
+ };
+ };
+ };
+...
+
--
2.7.4
^ permalink raw reply related
* [PATCH v5 6/8] drm/panel: Add ilitek ili9341 panel driver
From: dillon.minfei @ 2020-05-25 3:45 UTC (permalink / raw)
To: linus.walleij, broonie
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-spi,
linux-stm32, dri-devel, linux-clk, dillon min
In-Reply-To: <1590378348-8115-1-git-send-email-dillon.minfei@gmail.com>
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=yes, Size: 42915 bytes --]
From: dillon min <dillon.minfei@gmail.com>
This driver combine tiny/ili9341.c mipi_dbi_interface driver
with mipi_dpi_interface driver, can support ili9341 with serial
mode or parallel rgb interface mode by register configuration.
Changes since V3:
accoding to Linus Walleij's suggestion.
1 add more comments to driver.
2 reduce magic number usage in the driver.
3 move panel configuration from common place to system configuration.
4 reuse MIPI_DCS_* as more as possible.
Signed-off-by: dillon min <dillon.minfei@gmail.com>
---
drivers/gpu/drm/panel/Kconfig | 12 +
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-ilitek-ili9341.c | 1301 ++++++++++++++++++++++++++
3 files changed, 1314 insertions(+)
create mode 100644 drivers/gpu/drm/panel/panel-ilitek-ili9341.c
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index a1723c1..c938bee 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -95,6 +95,18 @@ config DRM_PANEL_ILITEK_IL9322
Say Y here if you want to enable support for Ilitek IL9322
QVGA (320x240) RGB, YUV and ITU-T BT.656 panels.
+config DRM_PANEL_ILITEK_ILI9341
+ tristate "Ilitek ILI9341 240x320 QVGA panels"
+ depends on OF && SPI
+ depends on DRM_KMS_HELPER
+ depends on DRM_KMS_CMA_HELPER
+ depends on BACKLIGHT_CLASS_DEVICE
+ select DRM_MIPI_DBI
+ help
+ Say Y here if you want to enable support for Ilitek IL9341
+ QVGA (240x320) RGB panels. support serial & parallel rgb
+ interface.
+
config DRM_PANEL_ILITEK_ILI9881C
tristate "Ilitek ILI9881C-based panels"
depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 96a883c..16947d7 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_DRM_PANEL_ELIDA_KD35T133) += panel-elida-kd35t133.o
obj-$(CONFIG_DRM_PANEL_FEIXIN_K101_IM2BA02) += panel-feixin-k101-im2ba02.o
obj-$(CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D) += panel-feiyang-fy07024di26a30d.o
obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o
+obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o
obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o
obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o
diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9341.c b/drivers/gpu/drm/panel/panel-ilitek-ili9341.c
new file mode 100644
index 0000000..dd6f860
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-ilitek-ili9341.c
@@ -0,0 +1,1301 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ilitek ILI9341 TFT LCD drm_panel driver.
+ *
+ * This panel can be configured to support:
+ * - 16-bit parallel RGB interface
+ * - 18-bit parallel RGB interface
+ * - 4-line serial spi interface
+ *
+ * Copyright (C) 2020 Dillon Min <dillon.minfei@gmail.com>
+ * Derived from drivers/drm/gpu/panel/panel-ilitek-ili9322.c
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+#include <drm/drm_mipi_dbi.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_atomic_helper.h>
+
+#include <drm/drm_drv.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+
+#define ILI9341_RGB_INTERFACE 0xb0 /* RGB Interface Signal Control */
+#define ILI9341_FRC 0xb1 /* Frame Rate Control register */
+#define ILI9341_DFC 0xb6 /* Display Function Control register */
+#define ILI9341_POWER1 0xc0 /* Power Control 1 register */
+#define ILI9341_POWER2 0xc1 /* Power Control 2 register */
+#define ILI9341_VCOM1 0xc5 /* VCOM Control 1 register */
+#define ILI9341_VCOM2 0xc7 /* VCOM Control 2 register */
+#define ILI9341_POWERA 0xcb /* Power control A register */
+#define ILI9341_POWERB 0xcf /* Power control B register */
+#define ILI9341_PGAMMA 0xe0 /* Positive Gamma Correction register */
+#define ILI9341_NGAMMA 0xe1 /* Negative Gamma Correction register */
+#define ILI9341_DTCA 0xe8 /* Driver timing control A */
+#define ILI9341_DTCB 0xea /* Driver timing control B */
+#define ILI9341_POWER_SEQ 0xed /* Power on sequence register */
+#define ILI9341_3GAMMA_EN 0xf2 /* 3 Gamma enable register */
+#define ILI9341_INTERFACE 0xf6 /* Interface control register */
+#define ILI9341_PRC 0xf7 /* Pump ratio control register */
+#define ILI9341_ETMOD 0xb7 /* Entry mode set */
+
+#define ILI9341_MADCTL_BGR BIT(3)
+#define ILI9341_MADCTL_MV BIT(5)
+#define ILI9341_MADCTL_MX BIT(6)
+#define ILI9341_MADCTL_MY BIT(7)
+
+
+#define ILI9341_POWER_B_LEN 3
+#define ILI9341_POWER_SEQ_LEN 4
+#define ILI9341_DTCA_LEN 3
+#define ILI9341_DTCB_LEN 2
+#define ILI9341_POWER_A_LEN 5
+#define ILI9341_DFC_1_LEN 2
+#define ILI9341_FRC_LEN 2
+#define ILI9341_VCOM_1_LEN 2
+#define ILI9341_DFC_2_LEN 4
+#define ILI9341_COLUMN_ADDR_LEN 4
+#define ILI9341_PAGE_ADDR_LEN 4
+#define ILI9341_INTERFACE_LEN 3
+#define ILI9341_PGAMMA_LEN 15
+#define ILI9341_NGAMMA_LEN 15
+#define ILI9341_CA_LEN 3
+
+#define ILI9341_PIXEL_DPI_16_BITS (BIT(6)|BIT(4))
+#define ILI9341_PIXEL_DPI_18_BITS (BIT(6)|BIT(5))
+#define ILI9341_GAMMA_CURVE_1 BIT(0)
+#define ILI9341_IF_WE_MODE BIT(0)
+#define ILI9341_IF_BIG_ENDIAN 0x00
+#define ILI9341_IF_DM_RGB BIT(2)
+#define ILI9341_IF_DM_INTERNAL 0x00
+#define ILI9341_IF_DM_VSYNC BIT(3)
+#define ILI9341_IF_RM_RGB BIT(1)
+#define ILI9341_IF_RIM_RGB 0x00
+
+#define ILI9341_COLUMN_ADDR 0x00ef
+#define ILI9341_PAGE_ADDR 0x013f
+
+#define ILI9341_RGB_EPL BIT(0)
+#define ILI9341_RGB_DPL BIT(1)
+#define ILI9341_RGB_HSPL BIT(2)
+#define ILI9341_RGB_VSPL BIT(3)
+#define ILI9341_RGB_DE_MODE BIT(6)
+#define ILI9341_RGB_DISP_PATH_MEM BIT(7)
+
+#define ILI9341_DBI_VCOMH_4P6V 0x23
+#define ILI9341_DBI_PWR_2_DEFAULT 0x10
+#define ILI9341_DBI_PRC_NORMAL 0x20
+#define ILI9341_DBI_VCOM_1_VMH_4P25V 0x3e
+#define ILI9341_DBI_VCOM_1_VML_1P5V 0x28
+#define ILI9341_DBI_VCOM_2_DEC_58 0x86
+#define ILI9341_DBI_FRC_DIVA 0x00
+#define ILI9341_DBI_FRC_RTNA 0x1b
+#define ILI9341_DBI_EMS_GAS BIT(0)
+#define ILI9341_DBI_EMS_DTS BIT(1)
+#define ILI9341_DBI_EMS_GON BIT(2)
+/**
+ * ili9341_command - ili9341 command with optional parameter(s)
+ * @ili: struct ili9341
+ * @cmd: Command
+ * @seq...: Optional parameter(s)
+ *
+ * Send command to the controller.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+#define ili9341_command(ili, cmd, seq...) \
+({ \
+ u8 d[] = { seq }; \
+ _ili9341_command(ili, cmd, d, ARRAY_SIZE(d)); \
+})
+
+/**
+ * struct ili9341_config - the system specific ILI9341 configuration
+ * @max_spi_speed: 10000000
+ */
+struct ili9341_config {
+ u32 max_spi_speed;
+
+ const struct drm_display_mode mode;
+ /* TODO: need comments for this register */
+ u8 ca[ILI9341_CA_LEN];
+ /* TODO: need comments for this register */
+ u8 power_b[ILI9341_POWER_B_LEN];
+ /* TODO: need comments for this register */
+ u8 power_seq[ILI9341_POWER_SEQ_LEN];
+ /* TODO: need comments for this register */
+ u8 dtca[ILI9341_DTCA_LEN];
+ /* TODO: need comments for this register */
+ u8 dtcb[ILI9341_DTCB_LEN];
+ /* TODO: need comments for this register */
+ u8 power_a[ILI9341_POWER_A_LEN];
+ /*
+ * Frame Rate Control (In Normal Mode/Full Colors) (B1h)
+ *
+ * Formula to calculate frame frequency:
+ * Frame Rate=fosc/(Clocks per line x Division ratio x
+ * (Lines+VBP+VFP))
+ *
+ * Sets the division ratio for internal clocks of Normal mode at MCU
+ * interface.
+ *
+ * fosc : internal oscillator frequency
+ * Clocks per line : RTNA setting
+ * Division ratio : DIVA setting
+ * Lines : total driving line number
+ * VBP : back porch line number
+ * VFP : front porch line number
+ *
+ * RTNA [4:0] Frame Rate (Hz) RTNA [4:0] Frame Rate (Hz)
+ * 1 0 0 0 0 119 1 1 0 0 0 79
+ * 1 0 0 0 1 112 1 1 0 0 1 76
+ * 1 0 0 1 0 106 1 1 0 1 0 73
+ * 1 0 0 1 1 100 1 1 0 1 1 70(default)
+ * 1 0 1 0 0 95 1 1 1 0 0 68
+ * 1 0 1 0 1 90 1 1 1 0 1 65
+ * 1 0 1 1 0 86 1 1 1 0 1 63
+ * 1 0 1 1 1 83 1 1 1 1 1 61
+ *
+ * DIVA [1:0] : division ratio for internal clocks when Normal mode.
+ *
+ * DIVA [1:0] Division Ratio
+ * 0 0 fosc
+ * 0 1 fosc / 2
+ * 1 0 fosc / 4
+ * 1 1 fosc / 8
+ *
+ */
+ u8 frc[ILI9341_FRC_LEN];
+ /* TODO: need comments for this register */
+ u8 prc;
+ /*
+ * B6h DISCTRL (Display Function Control)
+ * D/CX RDX WRX D17-8 D7 D6 D5 D4 D3 D2 D1 D0 HEX
+ * Command 0 1 M XX 1 0 1 1 0 1 1 0 B6h
+ * 1st Parameter 1 1 M XX 0 0 0 0 PTG[1:0] PT[1:0] 0A
+ * 2nd Parameter 1 1 M XX REV GS SS SM ISC[3:0] 82
+ * 3rd Parameter 1 1 M XX 0 0 NL[5:0] 27
+ * 4th Parameter 1 1 M XX 0 0 PCDIV[5:0] XX
+ *
+ * PTG [1:0]: Set the scan mode in non-display area.
+ * PTG1 | PTG0 | Gate outputs in | Source outputs in | VCOM output
+ * non-display area | non-display area |
+ * 1 0 Interval scan Set with the PT[2:0] bits
+ *
+ * PT [1:0]: Determine source/VCOM output in a non-display area in the
+ * partial display mode.
+ * 1 0 AGND AGND AGND AGND
+ *
+ * REV: Select whether the liquid crystal type is normally white type
+ * or normally black type.
+ * REV Liquid crystal type
+ * 0 Normally black
+ * 1 Normally white
+ *
+ * SS: Select the shift direction of outputs from the source driver.
+ * SS Source Output Scan Direction
+ * 0 S1 -> S720
+ * 1 S720 -> S1
+ *
+ * GS: Sets the direction of scan by the gate driver in the range
+ * determined by SCN [4:0] and NL [4:0]. The scan direction
+ * determined by GS = 0 can be reversed by setting GS = 1.
+ *
+ * GS Gate Output Scan Direction
+ * 0 G1 -> G320
+ * 1 G320 -> G1
+ */
+ u8 dfc_1[ILI9341_DFC_1_LEN];
+ /*
+ * Power Control 1 (C0h)
+ * VRH [5:0]: Set the GVDD level, which is a reference level for the
+ * VCOM level and the grayscale voltage level.
+ *
+ * VRH[5:0] GVDD VRH[5:0] GVDD
+ * 0 0 0 0 0 0 Setting prohibited 1 0 0 0 0 0 4.45 V
+ * 0 0 0 0 0 1 Setting prohibited 1 0 0 0 0 1 4.50 V
+ * 0 0 0 0 1 0 Setting prohibited 1 0 0 0 1 0 4.55 V
+ * 0 0 0 0 1 1 3.00 V 1 0 0 0 1 1 4.60 V
+ * 0 0 0 1 0 0 3.05 V 1 0 0 1 0 0 4.65 V
+ * 0 0 0 1 0 1 3.10 V 1 0 0 1 0 1 4.70 V
+ * 0 0 0 1 1 0 3.15 V 1 0 0 1 1 0 4.75 V
+ * 0 0 0 1 1 1 3.20 V 1 0 0 1 1 1 4.80 V
+ * 0 0 1 0 0 0 3.25 V 1 0 1 0 0 0 4.85 V
+ * 0 0 1 0 0 1 3.30 V 1 0 1 0 0 1 4.90 V
+ * 0 0 1 0 1 0 3.35 V 1 0 1 0 1 0 4.95 V
+ * 0 0 1 0 1 1 3.40 V 1 0 1 0 1 1 5.00 V
+ * 0 0 1 1 0 0 3.45 V 1 0 1 1 0 0 5.05 V
+ * 0 0 1 1 0 1 3.50 V 1 0 1 1 0 1 5.10 V
+ * 0 0 1 1 1 0 3.55 V 1 0 1 1 1 0 5.15 V
+ * 0 0 1 1 1 1 3.60 V 1 0 1 1 1 1 5.20 V
+ * 0 1 0 0 0 0 3.65 V 1 1 0 0 0 0 5.25 V
+ * 0 1 0 0 0 1 3.70 V 1 1 0 0 0 1 5.30 V
+ * 0 1 0 0 1 0 3.75 V 1 1 0 0 1 0 5.35 V
+ * 0 1 0 0 1 1 3.80 V 1 1 0 0 1 1 5.40 V
+ * 0 1 0 1 0 0 3.85 V 1 1 0 1 0 0 5.45 V
+ * 0 1 0 1 0 1 3.90 V 1 1 0 1 0 1 5.50 V
+ * 0 1 0 1 1 0 3.95 V 1 1 0 1 1 0 5.55 V
+ * 0 1 0 1 1 1 4.00 V 1 1 0 1 1 1 5.60 V
+ * 0 1 1 0 0 0 4.05 V 1 1 1 0 0 0 5.65 V
+ * 0 1 1 0 0 1 4.10 V 1 1 1 0 0 1 5.70 V
+ * 0 1 1 0 1 0 4.15 V 1 1 1 0 1 0 5.75 V
+ * 0 1 1 0 1 1 4.20 V 1 1 1 0 1 1 5.80 V
+ * 0 1 1 1 0 0 4.25 V 1 1 1 1 0 0 5.85 V
+ * 0 1 1 1 0 1 4.30 V 1 1 1 1 0 1 5.90 V
+ * 0 1 1 1 1 0 4.35 V 1 1 1 1 1 0 5.95 V
+ * 0 1 1 1 1 1 4.40 V 1 1 1 1 1 1 6.00 V
+ */
+ u8 power_1;
+ /*
+ * Power Control 2 (C1h)
+ *
+ * BT [2:0]: Sets the factor used in the step-up circuits.
+ * Select the optimal step-up factor for the operating voltage. To
+ * reduce power consumption, set a smaller factor.
+ *
+ * BT[2:0] AVDD VGH VGL
+ * 0 0 0 VCI x 2 VCI x 7 VCI x 4
+ * 0 0 1 VCI x 3
+ * 0 1 0 VCI x 6 VCI x 4
+ * 0 1 1 VCI x 3
+ *
+ */
+ u8 power_2;
+ /*
+ * VCOM Control 1(C5h)
+ *
+ * VMH [6:0] : Set the VCOMH voltage
+ *
+ * VMH[6:0] VCOMH(V) VMH[6:0] VCOMH(V) VMH[6:0] VCOMH(V) VMH[6:0] VCOMH
+ * 0000000 2.700 0100000 3.500 1000000 4.300 1100000 5.100
+ * 0000001 2.725 0100001 3.525 1000001 4.325 1100001 5.125
+ * 0000010 2.750 0100010 3.550 1000010 4.350 1100010 5.150
+ * 0000011 2.775 0100011 3.575 1000011 4.375 1100011 5.175
+ * 0000100 2.800 0100100 3.600 1000100 4.400 1100100 5.200
+ * 0000101 2.825 0100101 3.625 1000101 4.425 1100101 5.225
+ * 0000110 2.850 0100110 3.650 1000110 4.450 1100110 5.250
+ * 0000111 2.875 0100111 3.675 1000111 4.475 1100111 5.275
+ * 0001000 2.900 0101000 3.700 1001000 4.500 1101000 5.300
+ * 0001001 2.925 0101001 3.725 1001001 4.525 1101001 5.325
+ * 0001010 2.950 0101010 3.750 1001010 4.550 1101010 5.350
+ * 0001011 2.975 0101011 3.775 1001011 4.575 1101011 5.375
+ * 0001100 3.000 0101100 3.800 1001100 4.600 1101100 5.400
+ * 0001101 3.025 0101101 3.825 1001101 4.625 1101101 5.425
+ * 0001110 3.050 0101110 3.850 1001110 4.650 1101110 5.450
+ * 0001111 3.075 0101111 3.875 1001111 4.675 1101111 5.475
+ * 0010000 3.100 0110000 3.900 1010000 4.700 1110000 5.500
+ * 0010001 3.125 0110001 3.925 1010001 4.725 1110001 5.525
+ * 0010010 3.150 0110010 3.950 1010010 4.750 1110010 5.550
+ * 0010011 3.175 0110011 3.975 1010011 4.775 1110011 5.575
+ * 0010100 3.200 0110100 4.000 1010100 4.800 1110100 5.600
+ * 0010101 3.225 0110101 4.025 1010101 4.825 1110101 5.625
+ * 0010110 3.250 0110110 4.050 1010110 4.850 1110110 5.650
+ * 0010111 3.275 0110111 4.075 1010111 4.875 1110111 5.675
+ * 0011000 3.300 0111000 4.100 1011000 4.900 1111000 5.700
+ * 0011001 3.325 0111001 4.125 1011001 4.925 1111001 5.725
+ * 0011010 3.350 0111010 4.150 1011010 4.950 1111010 5.750
+ * 0011011 3.375 0111011 4.175 1011011 4.975 1111011 5.775
+ * 0011100 3.400 0111100 4.200 1011100 5.000 1111100 5.800
+ * 0011101 3.425 0111101 4.225 1011101 5.025 1111101 5.825
+ * 0011110 3.450 0111110 4.250 1011110 5.050 1111110 5.850
+ * 0011111 3.475 0111111 4.275 1011111 5.075 1111111 5.875
+ *
+ * VML[6:0] : Set the VCOML voltage
+ *
+ * VML[6:0] VCOML(V) VML[6:0] VCOML(V) VML[6:0] VCOML(V) VML[6:0] VCOML
+ * 0000000 -2.500 0100000 -1.700 1000000 -0.900 1100000 -0.100
+ * 0000001 -2.475 0100001 -1.675 1000001 -0.875 1100001 -0.075
+ * 0000010 -2.450 0100010 -1.650 1000010 -0.850 1100010 -0.050
+ * 0000011 -2.425 0100011 -1.625 1000011 -0.825 1100011 -0.025
+ * 0000100 -2.400 0100100 -1.600 1000100 -0.800 1100100 0
+ * 0000101 -2.375 0100101 -1.575 1000101 -0.775 1100101 Reserved
+ * 0000110 -2.350 0100110 -1.550 1000110 -0.750 1100110 Reserved
+ * 0000111 -2.325 0100111 -1.525 1000111 -0.725 1100111 Reserved
+ * 0001000 -2.300 0101000 -1.500 1001000 -0.700 1101000 Reserved
+ * 0001001 -2.275 0101001 -1.475 1001001 -0.675 1101001 Reserved
+ * 0001010 -2.250 0101010 -1.450 1001010 -0.650 1101010 Reserved
+ * 0001011 -2.225 0101011 -1.425 1001011 -0.625 1101011 Reserved
+ * 0001100 -2.200 0101100 -1.400 1001100 -0.600 1101100 Reserved
+ * 0001101 -2.175 0101101 -1.375 1001101 -0.575 1101101 Reserved
+ * 0001110 -2.150 0101110 -1.350 1001110 -0.550 1101110 Reserved
+ * 0001111 -2.125 0101111 -1.325 1001111 -0.525 1101111 Reserved
+ * 0010000 -2.100 0110000 -1.300 1010000 -0.500 1110000 Reserved
+ * 0010001 -2.075 0110001 -1.275 1010001 -0.475 1110001 Reserved
+ * 0010010 -2.050 0110010 -1.250 1010010 -0.450 1110010 Reserved
+ * 0010011 -2.025 0110011 -1.225 1010011 -0.425 1110011 Reserved
+ * 0010100 -2.000 0110100 -1.200 1010100 -0.400 1110100 Reserved
+ * 0010101 -1.975 0110101 -1.175 1010101 -0.375 1110101 Reserved
+ * 0010110 -1.950 0110110 -1.150 1010110 -0.350 1110110 Reserved
+ * 0010111 -1.925 0110111 -1.125 1010111 -0.325 1110111 Reserved
+ * 0011000 -1.900 0111000 -1.100 1011000 -0.300 1111000 Reserved
+ * 0011001 -1.875 0111001 -1.075 1011001 -0.275 1111001 Reserved
+ * 0011010 -1.850 0111010 -1.050 1011010 -0.250 1111010 Reserved
+ * 0011011 -1.825 0111011 -1.025 1011011 -0.225 1111011 Reserved
+ * 0011100 -1.800 0111100 -1.000 1011100 -0.200 1111100 Reserved
+ * 0011101 -1.775 0111101 -0.975 1011101 -0.175 1111101 Reserved
+ * 0011110 -1.750 0111110 -0.950 1011110 -0.150 1111110 Reserved
+ * 0011111 -1.725 0111111 -0.925 1011111 -0.125 1111111 Reserved
+ */
+ u8 vcom_1[ILI9341_VCOM_1_LEN];
+ /*
+ * VCOM Control 2(C7h)
+ *
+ * C7h VMCTRL1 (VCOM Control 1)
+ * D/CX RDX WRX D17-8 D7 D6 D5 D4 D3 D2 D1 D0 HEX
+ * Command 0 1 M XX 1 1 0 0 0 1 1 1 C7h
+ * Parameter 1 1 M XX nVM VMF[6:0] C0
+ *
+ * nVM: nVM equals to “0” after power on reset and VCOM offset
+ * equals to program MTP value. When nVM set to “1”, setting
+ * of VMF [6:0] becomes valid and VCOMH/VCOML can be adjusted.
+ *
+ * VMF [6:0]: Set the VCOM offset voltage.
+ */
+ u8 vcom_2;
+ /*
+ * Memory Access Control (36h)
+ *
+ * 36h MADCTL (Memory Access Control)
+ * D/CX RDX WRX D17-8 D7 D6 D5 D4 D3 D2 D1 D0 HEX
+ * Command 0 1 M XX 0 0 1 1 0 1 1 0 36h
+ * Parameter 1 1 M XX MY MX MV ML BGR MH 0 0 00
+ *
+ * This command defines read/write scanning direction of frame memory.
+ * This command makes no change on the other driver status.
+ *
+ * Bit Name Description
+ * MY Row Address Order
+ * MX Column Address Order
+ * MV Row / Column Exchange These 3 bits control MCU to memory
+ * write/read direction.
+ * ML Vertical Refresh Order LCD vertical refresh direction control.
+ * BGR RGB-BGR Order Color selector switch control
+ * (0=RGB color filter panel, 1=BGR
+ * color filter panel)
+ * MH Horizontal Refresh ORDER LCD horizontal refreshing
+ * direction control.
+ *
+ * Note: When BGR bit is changed, the new setting is active
+ * immediately without update the content in Frame Memory again.
+ *
+ */
+ u8 address_mode;
+ /* TODO: need comments for this register */
+ u8 g3amma_en;
+ /* RGB Interface Signal Control (B0h)
+ *
+ * B0h IFMODE (Interface Mode Control)
+ * D/CX RDX WRX D17-8 D7 D6 D5 D4 D3 D2 D1 D0 HEX
+ * Command 0 1 M XX 1 0 1 1 0 0 0 0 B0h
+ * Parameter 1 1 M XX ByPass_MODE RCM[1] RCM[0] 0 VSPL HSPL DPL EPL 40
+ *
+ * Sets the operation status of the display interface. The setting
+ * becomes effective as soon as the command is received.
+ * EPL: DE polarity (“0”= High enable for RGB interface, “1”= Low
+ * enable for RGB interface)
+ *
+ * DPL: DOTCLK polarity set (“0”= data fetched at the rising time,
+ * “1”= data fetched at the falling time)
+ *
+ * HSPL: HSYNC polarity (“0”= Low level sync clock, “1”= High
+ * level sync clock)
+ *
+ * VSPL: VSYNC polarity (“0”= Low level sync clock, “1”= High
+ * level sync clock)
+ *
+ * RCM [1:0]: RGB interface selection (refer to the RGB interface
+ * section).
+ *
+ * ByPass_MODE: Select display data path whether Memory or Direct to
+ * Shift register when RGB Interface is used.
+ *
+ * ByPass_MODE Display Data Path
+ * 0 Direct to Shift Register (default)
+ * 1 Memory
+ */
+ u8 rgb_interface;
+ /* refer to dfc_1 */
+ u8 dfc_2[ILI9341_DFC_2_LEN];
+ /*
+ * Column Address Set (2Ah)
+ * This command is used to define area of frame memory where MCU can
+ * access. This command makes no change on the
+ * other driver status. The values of SC [15:0] and EC [15:0] are
+ * referred when RAMWR command comes. Each value
+ * represents one column line in the Frame Memory.
+ */
+ u8 column_addr[ILI9341_COLUMN_ADDR_LEN];
+ /*
+ * Page Address Set (2Bh)
+ * This command is used to define area of frame memory where MCU can
+ * access. This command makes no change on the
+ * other driver status. The values of SP [15:0] and EP [15:0] are
+ * referred when RAMWR command comes. Each value
+ * represents one Page line in the Frame Memory.
+ */
+ u8 page_addr[ILI9341_PAGE_ADDR_LEN];
+ /*
+ * Interface Control (F6h)
+ *
+ * F6h IFCTL (16bits Data Format Selection)
+ * D/CX RDX WRX D17-8 D7 D6 D5 D4 D3 D2 D1 D0 HEX
+ * Command 0 1 M XX 1 1 1 1 0 1 1 0 F6h
+ * 1stParameter 1 1 M XX MY MX MV
+ * _EOR _EOR _EOR 0 BGR_EOR 0 0 WE MODE 01
+ * 2ndParameter 1 1 M XX 0 0 EPF[1] EPF[0] 0 0 MDT[1] MDT[0] 00
+ * 3rdParameter 1 1 M XX 0 0 ENDIAN 0 DM[1] DM[0] RM RIM 00
+ *
+ */
+ u8 interface[ILI9341_INTERFACE_LEN];
+ /*
+ * This command sets the pixel format for the RGB image data used by
+ * the interface. DPI [2:0] is the pixel format select of RGB
+ * interface and DBI [2:0] is the pixel format of MCU interface. If a
+ * particular interface, either RGB interface or MCU interface, is
+ * not used then the corresponding bits in the parameter are ignored.
+ * The pixel format is shown in the table below.
+ *
+ * DPI[2:0] RGB Interface Format DBI[2:0] MCU Interface Format
+ * 0 0 0 Reserved 0 0 0 Reserved
+ * 0 0 1 Reserved 0 0 1 Reserved
+ * 0 1 0 Reserved 0 1 0 Reserved
+ * 0 1 1 Reserved 0 1 1 Reserved
+ * 1 0 0 Reserved 1 0 0 Reserved
+ * 1 0 1 16 bits / pixel 1 0 1 16 bits / pixel
+ * 1 1 0 18 bits / pixel 1 1 0 18 bits / pixel
+ * 1 1 1 Reserved 1 1 1 Reserved
+ *
+ */
+ u8 pixel_format;
+ /*
+ * This command is used to select the desired Gamma curve for the
+ * current display. A maximum of 4 fixed gamma curves can
+ * be selected. The curve is selected by setting the appropriate bit
+ * in the parameter as described in the Table:
+ *
+ * GC [7:0] Curve Selected
+ * 01h Gamma curve 1 (G2.2)
+ * 02h ---
+ * 04h ---
+ * 08h ---
+ */
+ u8 gamma_curve;
+ /*
+ * Positive Gamma Correction (E0h)
+ *
+ * Set the gray scale voltage to adjust the gamma characteristics of
+ * the TFT panel.
+ */
+ u8 pgamma[ILI9341_PGAMMA_LEN];
+ /*
+ * Negative Gamma Correction (E1h)
+ *
+ * Set the gray scale voltage to adjust the gamma characteristics of
+ * the TFT panel.
+ */
+ u8 ngamma[ILI9341_NGAMMA_LEN];
+};
+
+struct ili9341 {
+ struct device *dev;
+ const struct ili9341_config *conf;
+ struct drm_panel panel;
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *dc_gpio;
+ u32 max_spi_speed;
+ struct regulator *vcc;
+};
+
+/*
+ * The Stm32f429-disco board has a panel ili9341 connected to ltdc controller
+ */
+static const struct ili9341_config ili9341_stm32f429_disco_data = {
+ .max_spi_speed = 10000000,
+ .mode = {
+ .clock = 6100,
+ .hdisplay = 240,
+ .hsync_start = 240 + 10,/* hfp 10 */
+ .hsync_end = 240 + 10 + 10,/* hsync 10 */
+ .htotal = 240 + 10 + 10 + 20,/* hbp 20 */
+ .vdisplay = 320,
+ .vsync_start = 320 + 4,/* vfp 4 */
+ .vsync_end = 320 + 4 + 2,/* vsync 2 */
+ .vtotal = 320 + 4 + 2 + 2,/* vbp 2 */
+ .vrefresh = 60,
+ .flags = 0,
+ .width_mm = 65,
+ .height_mm = 50,
+ .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+ },
+ /* TODO: need comments for this register */
+ .ca = {0xc3, 0x08, 0x50},
+ /* TODO: need comments for this register */
+ .power_b = {0x00, 0xc1, 0x30},
+ /* TODO: need comments for this register */
+ .power_seq = {0x64, 0x03, 0x12, 0x81},
+ /* TODO: need comments for this register */
+ .dtca = {0x85, 0x00, 0x78},
+ /* TODO: need comments for this register */
+ .power_a = {0x39, 0x2c, 0x00, 0x34, 0x02},
+ /* TODO: need comments for this register */
+ .prc = 0x20,
+ /* TODO: need comments for this register */
+ .dtcb = {0x00, 0x00},
+ /* 0x00 fosc, 0x1b 70hz */
+ .frc = {0x00, 0x1b},
+ /* 0x0a Interval scan, AGND AGND AGND AGND
+ * 0xa2 Normally white, G1 -> G320, S720 -> S1,
+ * Scan Cycle 5 frames,85ms
+ */
+ .dfc_1 = {0x0a, 0xa2},
+ /* 0x10 3.65v */
+ .power_1 = 0x10,
+ /* 0x10 AVDD=vci*2, VGH=vci*7, VGL=-vci*4 */
+ .power_2 = 0x10,
+ /* 0x45 VCOMH 4.425v, 0x15 VCOML -1.975*/
+ .vcom_1 = {0x45, 0x15},
+ /* 0x90 offset voltage, VMH-48, VML-48 */
+ .vcom_2 = 0x90,
+ /* 0xc8 Row Address Order, Column Address Order
+ * BGR 1
+ */
+ .address_mode = 0xc8,
+ .g3amma_en = 0x00,
+ /* 0xc2
+ * Display Data Path: Memory
+ * RGB: DE mode
+ * DOTCLK polarity set (data fetched at the falling time)
+ */
+ .rgb_interface = ILI9341_RGB_DISP_PATH_MEM |
+ ILI9341_RGB_DE_MODE |
+ ILI9341_RGB_DPL,
+ /*
+ * 0x0a
+ * Gate outputs in non-display area: Interval scan
+ * Determine source/VCOM output in a non-display area in the partial
+ * display mode: AGND AGND AGND AGND
+ *
+ * 0xa7
+ * Scan Cycle: 15 frames
+ * fFLM = 60Hz: 255ms
+ * Liquid crystal type: Normally white
+ * Gate Output Scan Direction: G1 -> G320
+ * Source Output Scan Direction: S720 -> S1
+ *
+ * 0x27
+ * LCD Driver Line: 320 lines
+ *
+ * 0x04
+ * PCDIV: 4
+ */
+ .dfc_2 = {0x0a, 0xa7, 0x27, 0x04},
+ /* column address: 240 */
+ .column_addr = {0x00, 0x00, (ILI9341_COLUMN_ADDR >> 4) & 0xff,
+ ILI9341_COLUMN_ADDR & 0xff},
+ /* page address: 320 */
+ .page_addr = {0x00, 0x00, (ILI9341_PAGE_ADDR >> 4) & 0xff,
+ ILI9341_PAGE_ADDR & 0xff},
+ /* Memory write control: When the transfer number of data exceeds
+ * (EC-SC+1)*(EP-SP+1), the column and page number will be
+ * reset, and the exceeding data will be written into the following
+ * column and page.
+ * Display Operation Mode: RGB Interface Mode
+ * Interface for RAM Access: RGB interface
+ * 16- bit RGB interface (1 transfer/pixel)
+ */
+ .interface = {ILI9341_IF_WE_MODE, 0x00,
+ ILI9341_IF_DM_RGB | ILI9341_IF_RM_RGB},
+ /* DPI: 16 bits / pixel */
+ .pixel_format = ILI9341_PIXEL_DPI_16_BITS,
+ /* Curve Selected: Gamma curve 1 (G2.2) */
+ .gamma_curve = ILI9341_GAMMA_CURVE_1,
+ .pgamma = {0x0f, 0x29, 0x24, 0x0c, 0x0e,
+ 0x09, 0x4e, 0x78, 0x3c, 0x09,
+ 0x13, 0x05, 0x17, 0x11, 0x00},
+ .ngamma = {0x00, 0x16, 0x1b, 0x04, 0x11,
+ 0x07, 0x31, 0x33, 0x42, 0x05,
+ 0x0c, 0x0a, 0x28, 0x2f, 0x0f},
+};
+static inline struct ili9341 *panel_to_ili9341(struct drm_panel *panel)
+{
+ return container_of(panel, struct ili9341, panel);
+}
+
+int ili9341_spi_transfer(struct spi_device *spi, u32 speed_hz,
+ u8 bpw, const void *buf, size_t len)
+{
+ size_t max_chunk = spi_max_transfer_size(spi);
+ struct spi_transfer tr = {
+ .bits_per_word = bpw,
+ .speed_hz = speed_hz,
+ .len = len,
+ };
+ struct spi_message m;
+ size_t chunk;
+ int ret;
+
+ spi_message_init_with_transfers(&m, &tr, 1);
+
+ while (len) {
+ chunk = min(len, max_chunk);
+
+ tr.tx_buf = buf;
+ tr.len = chunk;
+ buf += chunk;
+ len -= chunk;
+
+ ret = spi_sync(spi, &m);
+ if (ret) {
+ dev_err(&spi->dev, "spi_sync error: %d\n", ret);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int _ili9341_command(struct ili9341 *ili, u8 cmd, const void *data,
+ size_t count)
+{
+ struct spi_device *spi = to_spi_device(ili->dev);
+ int ret = 0;
+
+ gpiod_set_value_cansleep(ili->dc_gpio, 0);
+
+ ret = ili9341_spi_transfer(spi, ili->max_spi_speed, 8,
+ (const void *)&cmd, 1);
+ if (ret || data == NULL || count == 0)
+ return ret;
+
+ gpiod_set_value_cansleep(ili->dc_gpio, 1);
+
+ return ili9341_spi_transfer(spi, ili->max_spi_speed, 8,
+ data, count);
+}
+
+static int ili9341_dpi_init(struct ili9341 *ili)
+{
+ int ret;
+ ret = _ili9341_command(ili, 0xca,
+ ili->conf->ca,
+ ILI9341_CA_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_POWERB,
+ ili->conf->power_b,
+ ILI9341_POWER_B_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_POWER_SEQ,
+ ili->conf->power_seq,
+ ILI9341_POWER_SEQ_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_DTCA,
+ ili->conf->dtca,
+ ILI9341_DTCA_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_POWERA,
+ ili->conf->power_a,
+ ILI9341_POWER_A_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_PRC,
+ &ili->conf->prc,
+ 1);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_DTCB,
+ ili->conf->dtcb,
+ ILI9341_DTCB_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_FRC,
+ ili->conf->frc,
+ ILI9341_FRC_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_DFC,
+ ili->conf->dfc_1,
+ ILI9341_DFC_1_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_POWER1,
+ &ili->conf->power_1,
+ 1);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_POWER2,
+ &ili->conf->power_2,
+ 1);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_VCOM1,
+ ili->conf->vcom_1,
+ ILI9341_VCOM_1_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_VCOM2,
+ &ili->conf->vcom_2,
+ 1);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, MIPI_DCS_SET_ADDRESS_MODE,
+ &ili->conf->address_mode,
+ 1);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_3GAMMA_EN,
+ &ili->conf->g3amma_en,
+ 1);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_RGB_INTERFACE,
+ &ili->conf->rgb_interface,
+ 1);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_DFC,
+ ili->conf->dfc_2,
+ ILI9341_DFC_2_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, MIPI_DCS_SET_COLUMN_ADDRESS,
+ ili->conf->column_addr,
+ ILI9341_COLUMN_ADDR_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, MIPI_DCS_SET_PAGE_ADDRESS,
+ ili->conf->page_addr,
+ ILI9341_PAGE_ADDR_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_INTERFACE,
+ ili->conf->interface,
+ ILI9341_INTERFACE_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, MIPI_DCS_SET_PIXEL_FORMAT,
+ &ili->conf->pixel_format,
+ 1);
+ if (ret)
+ return ret;
+
+ ret = ili9341_command(ili, MIPI_DCS_WRITE_MEMORY_START);
+ if (ret)
+ return ret;
+
+ msleep(200);
+ ret = _ili9341_command(ili, MIPI_DCS_SET_GAMMA_CURVE,
+ &ili->conf->gamma_curve,
+ 1);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_PGAMMA,
+ ili->conf->pgamma,
+ ILI9341_PGAMMA_LEN);
+ if (ret)
+ return ret;
+
+ ret = _ili9341_command(ili, ILI9341_NGAMMA,
+ ili->conf->ngamma,
+ ILI9341_NGAMMA_LEN);
+ if (ret)
+ return ret;
+
+ ret = ili9341_command(ili, MIPI_DCS_EXIT_SLEEP_MODE);
+ if (ret)
+ return ret;
+
+ msleep(200);
+ ret = ili9341_command(ili, MIPI_DCS_SET_DISPLAY_ON);
+ if (ret)
+ return ret;
+
+ ret = ili9341_command(ili, MIPI_DCS_WRITE_MEMORY_START);
+ if (ret)
+ return ret;
+
+
+ dev_info(ili->dev, "initialized display rgb interface\n");
+
+ return 0;
+}
+
+static int ili9341_dpi_power_on(struct ili9341 *ili)
+{
+ int ret = 0;
+
+ /* Assert RESET */
+ gpiod_set_value(ili->reset_gpio, 1);
+
+ /* Enable power */
+ if (!IS_ERR(ili->vcc)) {
+ ret = regulator_enable(ili->vcc);
+ if (ret < 0) {
+ dev_err(ili->dev, "unable to enable vcc\n");
+ return ret;
+ }
+ }
+ msleep(20);
+
+ /* De-assert RESET */
+ gpiod_set_value(ili->reset_gpio, 0);
+ msleep(10);
+
+ return 0;
+}
+
+static int ili9341_dpi_power_off(struct ili9341 *ili)
+{
+ /* Assert RESET */
+ gpiod_set_value(ili->reset_gpio, 1);
+
+ /* Disable power */
+ if (!IS_ERR(ili->vcc))
+ return regulator_disable(ili->vcc);
+
+ return 0;
+}
+
+static int ili9341_dpi_disable(struct drm_panel *panel)
+{
+ struct ili9341 *ili = panel_to_ili9341(panel);
+
+ ili9341_command(ili, MIPI_DCS_SET_DISPLAY_OFF);
+
+ return 0;
+}
+
+static int ili9341_dpi_unprepare(struct drm_panel *panel)
+{
+ struct ili9341 *ili = panel_to_ili9341(panel);
+
+ return ili9341_dpi_power_off(ili);
+}
+
+static int ili9341_dpi_prepare(struct drm_panel *panel)
+{
+ struct ili9341 *ili = panel_to_ili9341(panel);
+ int ret;
+
+ ret = ili9341_dpi_power_on(ili);
+ if (ret < 0)
+ return ret;
+
+ ret = ili9341_dpi_init(ili);
+ if (ret < 0)
+ ili9341_dpi_unprepare(panel);
+
+ return ret;
+}
+
+static int ili9341_dpi_enable(struct drm_panel *panel)
+{
+ struct ili9341 *ili = panel_to_ili9341(panel);
+
+ ili9341_command(ili, MIPI_DCS_SET_DISPLAY_ON);
+
+ return 0;
+}
+
+static int ili9341_dpi_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct ili9341 *ili = panel_to_ili9341(panel);
+ struct drm_device *drm = connector->dev;
+ struct drm_display_mode *mode;
+ struct drm_display_info *info;
+
+ info = &connector->display_info;
+ info->width_mm = ili->conf->mode.width_mm;
+ info->height_mm = ili->conf->mode.height_mm;
+
+ if (ili->conf->rgb_interface & ILI9341_RGB_DPL)
+ info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE;
+ else
+ info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
+
+ if (ili->conf->rgb_interface & ILI9341_RGB_EPL)
+ info->bus_flags |= DRM_BUS_FLAG_DE_HIGH;
+ else
+ info->bus_flags |= DRM_BUS_FLAG_DE_LOW;
+
+ mode = drm_mode_duplicate(drm, &ili->conf->mode);
+ if (!mode) {
+ DRM_ERROR("bad mode or failed to add mode\n");
+ return -EINVAL;
+ }
+ drm_mode_set_name(mode);
+
+ /* Set up the polarity */
+ if (ili->conf->rgb_interface & ILI9341_RGB_HSPL)
+ mode->flags |= DRM_MODE_FLAG_PHSYNC;
+ else
+ mode->flags |= DRM_MODE_FLAG_NHSYNC;
+
+ if (ili->conf->rgb_interface & ILI9341_RGB_VSPL)
+ mode->flags |= DRM_MODE_FLAG_PVSYNC;
+ else
+ mode->flags |= DRM_MODE_FLAG_NVSYNC;
+
+ drm_mode_probed_add(connector, mode);
+
+ return 1; /* Number of modes */
+}
+
+static const struct drm_panel_funcs ili9341_dpi_funcs = {
+ .disable = ili9341_dpi_disable,
+ .unprepare = ili9341_dpi_unprepare,
+ .prepare = ili9341_dpi_prepare,
+ .enable = ili9341_dpi_enable,
+ .get_modes = ili9341_dpi_get_modes,
+};
+
+static int ili9341_dpi_probe(struct spi_device *spi)
+{
+ int ret;
+ struct device *dev = &spi->dev;
+ struct ili9341 *ili;
+
+ ili = devm_kzalloc(dev, sizeof(struct ili9341), GFP_KERNEL);
+ if (!ili)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, ili);
+
+ ili->dev = dev;
+ /*
+ * Every new incarnation of this display must have a unique
+ * data entry for the system in this driver.
+ */
+ ili->conf = of_device_get_match_data(dev);
+ if (!ili->conf) {
+ dev_err(dev, "missing device configuration\n");
+ return -ENODEV;
+ }
+
+ ili->vcc = devm_regulator_get_optional(dev, "vcc");
+ if (IS_ERR(ili->vcc))
+ dev_err(dev, "get optional vcc failed\n");
+
+ ili->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ili->reset_gpio)) {
+ dev_err(dev, "failed to get RESET GPIO\n");
+ return PTR_ERR(ili->reset_gpio);
+ }
+
+ ili->dc_gpio = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW);
+ if (IS_ERR(ili->dc_gpio)) {
+ dev_err(dev, "failed to get DC GPIO\n");
+ return PTR_ERR(ili->dc_gpio);
+ }
+
+ spi->bits_per_word = 8;
+ ret = spi_setup(spi);
+ if (ret < 0) {
+ dev_err(dev, "spi setup failed.\n");
+ return ret;
+ }
+
+ ili->max_spi_speed = ili->conf->max_spi_speed;
+
+ drm_panel_init(&ili->panel, dev, &ili9341_dpi_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ return drm_panel_add(&ili->panel);
+}
+
+
+
+static void ili9341_dbi_enable(struct drm_simple_display_pipe *pipe,
+ struct drm_crtc_state *crtc_state,
+ struct drm_plane_state *plane_state)
+{
+ struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev);
+ struct mipi_dbi *dbi = &dbidev->dbi;
+ u8 addr_mode;
+ int ret, idx;
+
+ if (!drm_dev_enter(pipe->crtc.dev, &idx))
+ return;
+
+ DRM_DEBUG_KMS("\n");
+
+ ret = mipi_dbi_poweron_conditional_reset(dbidev);
+ if (ret < 0)
+ goto out_exit;
+ if (ret == 1)
+ goto out_enable;
+
+ mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF);
+
+ mipi_dbi_command(dbi, ILI9341_POWERB, 0x00, 0xc1, 0x30);
+ mipi_dbi_command(dbi, ILI9341_POWER_SEQ, 0x64, 0x03, 0x12, 0x81);
+ mipi_dbi_command(dbi, ILI9341_DTCA, 0x85, 0x00, 0x78);
+ mipi_dbi_command(dbi, ILI9341_POWERA, 0x39, 0x2c, 0x00, 0x34, 0x02);
+ mipi_dbi_command(dbi, ILI9341_PRC, ILI9341_DBI_PRC_NORMAL);
+ mipi_dbi_command(dbi, ILI9341_DTCB, 0x00, 0x00);
+
+ /* Power Control */
+ mipi_dbi_command(dbi, ILI9341_POWER1, ILI9341_DBI_VCOMH_4P6V);
+ mipi_dbi_command(dbi, ILI9341_POWER2, ILI9341_DBI_PWR_2_DEFAULT);
+ /* VCOM */
+ mipi_dbi_command(dbi, ILI9341_VCOM1, ILI9341_DBI_VCOM_1_VMH_4P25V,
+ ILI9341_DBI_VCOM_1_VML_1P5V);
+ mipi_dbi_command(dbi, ILI9341_VCOM2, ILI9341_DBI_VCOM_2_DEC_58);
+
+ /* Memory Access Control */
+ mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT,
+ MIPI_DCS_PIXEL_FMT_16BIT);
+
+ /* Frame Rate */
+ mipi_dbi_command(dbi, ILI9341_FRC, ILI9341_DBI_FRC_DIVA & 0x03,
+ ILI9341_DBI_FRC_RTNA & 0x1f);
+
+ /* Gamma */
+ mipi_dbi_command(dbi, ILI9341_3GAMMA_EN, 0x00);
+ mipi_dbi_command(dbi, MIPI_DCS_SET_GAMMA_CURVE, ILI9341_GAMMA_CURVE_1);
+ mipi_dbi_command(dbi, ILI9341_PGAMMA,
+ 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1,
+ 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00);
+ mipi_dbi_command(dbi, ILI9341_NGAMMA,
+ 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1,
+ 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f);
+
+ /* DDRAM */
+ mipi_dbi_command(dbi, ILI9341_ETMOD, ILI9341_DBI_EMS_GAS |
+ ILI9341_DBI_EMS_DTS |
+ ILI9341_DBI_EMS_GON);
+
+ /* Display */
+ mipi_dbi_command(dbi, ILI9341_DFC, 0x08, 0x82, 0x27, 0x00);
+ mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
+ msleep(100);
+
+ mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON);
+ msleep(100);
+
+out_enable:
+ switch (dbidev->rotation) {
+ default:
+ addr_mode = ILI9341_MADCTL_MX;
+ break;
+ case 90:
+ addr_mode = ILI9341_MADCTL_MV;
+ break;
+ case 180:
+ addr_mode = ILI9341_MADCTL_MY;
+ break;
+ case 270:
+ addr_mode = ILI9341_MADCTL_MV | ILI9341_MADCTL_MY |
+ ILI9341_MADCTL_MX;
+ break;
+ }
+ addr_mode |= ILI9341_MADCTL_BGR;
+ mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
+ mipi_dbi_enable_flush(dbidev, crtc_state, plane_state);
+ DRM_DEBUG_KMS("initialized display serial interface\n");
+out_exit:
+ drm_dev_exit(idx);
+}
+
+static const struct drm_simple_display_pipe_funcs ili9341_dbi_funcs = {
+ .enable = ili9341_dbi_enable,
+ .disable = mipi_dbi_pipe_disable,
+ .update = mipi_dbi_pipe_update,
+ .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb,
+};
+
+static const struct drm_display_mode ili9341_dbi_mode = {
+ DRM_SIMPLE_MODE(240, 320, 37, 49),
+};
+
+DEFINE_DRM_GEM_CMA_FOPS(ili9341_dbi_fops);
+
+static struct drm_driver ili9341_dbi_driver = {
+ .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+ .fops = &ili9341_dbi_fops,
+ .release = mipi_dbi_release,
+ DRM_GEM_CMA_VMAP_DRIVER_OPS,
+ .debugfs_init = mipi_dbi_debugfs_init,
+ .name = "ili9341",
+ .desc = "Ilitek ILI9341",
+ .date = "20180514",
+ .major = 1,
+ .minor = 0,
+};
+static int ili9341_dbi_probe(struct spi_device *spi)
+{
+ struct mipi_dbi_dev *dbidev;
+ struct drm_device *drm;
+ struct mipi_dbi *dbi;
+ struct gpio_desc *dc;
+ struct device *dev = &spi->dev;
+ u32 rotation = 0;
+ int ret;
+
+ dbidev = kzalloc(sizeof(*dbidev), GFP_KERNEL);
+ if (!dbidev)
+ return -ENOMEM;
+
+ dbi = &dbidev->dbi;
+ drm = &dbidev->drm;
+ ret = devm_drm_dev_init(dev, drm, &ili9341_dbi_driver);
+ if (ret) {
+ kfree(dbidev);
+ return ret;
+ }
+
+ drm_mode_config_init(drm);
+
+ dbi->reset = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(dbi->reset)) {
+ DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n");
+ return PTR_ERR(dbi->reset);
+ }
+
+ dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW);
+ if (IS_ERR(dc)) {
+ DRM_DEV_ERROR(dev, "Failed to get gpio 'dc'\n");
+ return PTR_ERR(dc);
+ }
+
+ dbidev->regulator = devm_regulator_get_optional(dev, "vcc");
+ if (IS_ERR(dbidev->regulator))
+ dev_err(dev, "get optional vcc failed\n");
+
+ dbidev->backlight = devm_of_find_backlight(dev);
+ if (IS_ERR(dbidev->backlight))
+ return PTR_ERR(dbidev->backlight);
+
+ device_property_read_u32(dev, "rotation", &rotation);
+
+ ret = mipi_dbi_spi_init(spi, dbi, dc);
+ if (ret)
+ return ret;
+
+ ret = mipi_dbi_dev_init(dbidev, &ili9341_dbi_funcs,
+ &ili9341_dbi_mode, rotation);
+ if (ret)
+ return ret;
+
+ drm_mode_config_reset(drm);
+
+ ret = drm_dev_register(drm, 0);
+ if (ret)
+ return ret;
+
+ spi_set_drvdata(spi, drm);
+
+ drm_fbdev_generic_setup(drm, 0);
+
+ return 0;
+}
+static int ili9341_probe(struct spi_device *spi)
+{
+ const struct spi_device_id *id = spi_get_device_id(spi);
+
+ if (!strcmp(id->name, "sf-tc240t-9370-t"))
+ return ili9341_dpi_probe(spi);
+ else if (!strcmp(id->name, "yx240qv29"))
+ return ili9341_dbi_probe(spi);
+
+ return -1;
+}
+
+static int ili9341_remove(struct spi_device *spi)
+{
+ const struct spi_device_id *id = spi_get_device_id(spi);
+ struct ili9341 *ili = spi_get_drvdata(spi);
+ struct drm_device *drm = spi_get_drvdata(spi);
+
+ if (!strcmp(id->name, "sf-tc240t-9370-t")) {
+ ili9341_dpi_power_off(ili);
+ drm_panel_remove(&ili->panel);
+ } else if (!strcmp(id->name, "yx240qv29")) {
+ drm_dev_unplug(drm);
+ drm_atomic_helper_shutdown(drm);
+ }
+ return 0;
+}
+
+static void ili9341_shutdown(struct spi_device *spi)
+{
+ const struct spi_device_id *id = spi_get_device_id(spi);
+
+ if (!strcmp(id->name, "yx240qv29"))
+ drm_atomic_helper_shutdown(spi_get_drvdata(spi));
+}
+
+static const struct of_device_id ili9341_of_match[] = {
+ {
+ .compatible = "st,sf-tc240t-9370-t",
+ .data = &ili9341_stm32f429_disco_data,
+ },
+ {
+ /* porting from tiny/ili9341.c
+ * for original mipi dbi compitable
+ */
+ .compatible = "adafruit,yx240qv29",
+ .data = NULL,
+ },
+};
+MODULE_DEVICE_TABLE(of, ili9341_of_match);
+
+static const struct spi_device_id ili9341_id[] = {
+ { "yx240qv29", 0 },
+ { "sf-tc240t-9370-t", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ili9341_id);
+
+static struct spi_driver ili9341_driver = {
+ .probe = ili9341_probe,
+ .remove = ili9341_remove,
+ .shutdown = ili9341_shutdown,
+ .id_table = ili9341_id,
+ .driver = {
+ .name = "panel-ilitek-ili9341",
+ .of_match_table = ili9341_of_match,
+ },
+};
+module_spi_driver(ili9341_driver);
+
+MODULE_AUTHOR("Dillon Min <dillon.minfei@gmail.com>");
+MODULE_DESCRIPTION("ILI9341 LCD panel driver");
+MODULE_LICENSE("GPL v2");
--
2.7.4
^ permalink raw reply related
* [PATCH v5 7/8] spi: stm32: Add 'SPI_SIMPLEX_RX', 'SPI_3WIRE_RX' support for stm32f4
From: dillon.minfei @ 2020-05-25 3:45 UTC (permalink / raw)
To: linus.walleij, broonie
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-spi,
linux-stm32, dri-devel, linux-clk, dillon min
In-Reply-To: <1590378348-8115-1-git-send-email-dillon.minfei@gmail.com>
From: dillon min <dillon.minfei@gmail.com>
in l3gd20 driver startup, there is a setup failed error return from
stm32 spi driver
"
[ 2.687630] st-gyro-spi spi0.0: supply vdd not found, using dummy
regulator
[ 2.696869] st-gyro-spi spi0.0: supply vddio not found, using dummy
regulator
[ 2.706707] spi_stm32 40015000.spi: SPI transfer setup failed
[ 2.713741] st-gyro-spi spi0.0: SPI transfer failed: -22
[ 2.721096] spi_master spi0: failed to transfer one message from queue
[ 2.729268] iio iio:device0: failed to read Who-Am-I register.
[ 2.737504] st-gyro-spi: probe of spi0.0 failed with error -22
"
after debug into spi-stm32 driver, st-gyro-spi split two steps to read
l3gd20 id
first: send command to l3gd20 with read id command in tx_buf, rx_buf
is null.
second: read id with tx_buf is null, rx_buf not null.
so, for second step, stm32 driver recongise this process as 'SPI_SIMPLE_RX'
from stm32_spi_communication_type(), but there is no related process for this
type in stm32f4_spi_set_mode(), then we get error from
stm32_spi_transfer_one_setup().
we can use two method to fix this bug.
1, use stm32 spi's "In unidirectional receive-only mode (BIDIMODE=0 and
RXONLY=1)". but as our code running in sdram, the read latency is too large
to get so many receive overrun error in interrupts handler.
2, use stm32 spi's "In full-duplex (BIDIMODE=0 and RXONLY=0)", as tx_buf is
null, so add flag 'SPI_MASTER_MUST_TX' to spi master.
Change since V4:
1 remove dummy data sent out by stm32 spi driver
2 add flag 'SPI_MASTER_MUST_TX' to spi master
Signed-off-by: dillon min <dillon.minfei@gmail.com>
---
Hi Mark,
This changes add 'SPI_MASTER_MUST_TX' for stm32 spi controller
thanks.
drivers/spi/spi-stm32.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/drivers/spi/spi-stm32.c b/drivers/spi/spi-stm32.c
index 44ac6eb3..4c643df 100644
--- a/drivers/spi/spi-stm32.c
+++ b/drivers/spi/spi-stm32.c
@@ -811,7 +811,9 @@ static irqreturn_t stm32f4_spi_irq_event(int irq, void *dev_id)
mask |= STM32F4_SPI_SR_TXE;
}
- if (!spi->cur_usedma && spi->cur_comm == SPI_FULL_DUPLEX) {
+ if (!spi->cur_usedma && (spi->cur_comm == SPI_FULL_DUPLEX ||
+ spi->cur_comm == SPI_SIMPLEX_RX ||
+ spi->cur_comm == SPI_3WIRE_RX)) {
/* TXE flag is set and is handled when RXNE flag occurs */
sr &= ~STM32F4_SPI_SR_TXE;
mask |= STM32F4_SPI_SR_RXNE | STM32F4_SPI_SR_OVR;
@@ -850,7 +852,7 @@ static irqreturn_t stm32f4_spi_irq_event(int irq, void *dev_id)
stm32f4_spi_read_rx(spi);
if (spi->rx_len == 0)
end = true;
- else /* Load data for discontinuous mode */
+ else if (spi->tx_buf)/* Load data for discontinuous mode */
stm32f4_spi_write_tx(spi);
}
@@ -1151,7 +1153,9 @@ static int stm32f4_spi_transfer_one_irq(struct stm32_spi *spi)
/* Enable the interrupts relative to the current communication mode */
if (spi->cur_comm == SPI_SIMPLEX_TX || spi->cur_comm == SPI_3WIRE_TX) {
cr2 |= STM32F4_SPI_CR2_TXEIE;
- } else if (spi->cur_comm == SPI_FULL_DUPLEX) {
+ } else if (spi->cur_comm == SPI_FULL_DUPLEX ||
+ spi->cur_comm == SPI_SIMPLEX_RX ||
+ spi->cur_comm == SPI_3WIRE_RX) {
/* In transmit-only mode, the OVR flag is set in the SR register
* since the received data are never read. Therefore set OVR
* interrupt only when rx buffer is available.
@@ -1462,10 +1466,16 @@ static int stm32f4_spi_set_mode(struct stm32_spi *spi, unsigned int comm_type)
stm32_spi_set_bits(spi, STM32F4_SPI_CR1,
STM32F4_SPI_CR1_BIDIMODE |
STM32F4_SPI_CR1_BIDIOE);
- } else if (comm_type == SPI_FULL_DUPLEX) {
+ } else if (comm_type == SPI_FULL_DUPLEX ||
+ comm_type == SPI_SIMPLEX_RX) {
stm32_spi_clr_bits(spi, STM32F4_SPI_CR1,
STM32F4_SPI_CR1_BIDIMODE |
STM32F4_SPI_CR1_BIDIOE);
+ } else if (comm_type == SPI_3WIRE_RX) {
+ stm32_spi_set_bits(spi, STM32F4_SPI_CR1,
+ STM32F4_SPI_CR1_BIDIMODE);
+ stm32_spi_clr_bits(spi, STM32F4_SPI_CR1,
+ STM32F4_SPI_CR1_BIDIOE);
} else {
return -EINVAL;
}
@@ -1906,6 +1916,7 @@ static int stm32_spi_probe(struct platform_device *pdev)
master->prepare_message = stm32_spi_prepare_msg;
master->transfer_one = stm32_spi_transfer_one;
master->unprepare_message = stm32_spi_unprepare_msg;
+ master->flags = SPI_MASTER_MUST_TX;
spi->dma_tx = dma_request_chan(spi->dev, "tx");
if (IS_ERR(spi->dma_tx)) {
--
2.7.4
^ permalink raw reply related
* [PATCH v5 8/8] spi: flags 'SPI_CONTROLLER_MUST_RX' and 'SPI_CONTROLLER_MUST_TX' can't be coexit with 'SPI_3WIRE' mode
From: dillon.minfei @ 2020-05-25 3:45 UTC (permalink / raw)
To: linus.walleij, broonie
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-spi,
linux-stm32, dri-devel, linux-clk, dillon min
In-Reply-To: <1590378348-8115-1-git-send-email-dillon.minfei@gmail.com>
From: dillon min <dillon.minfei@gmail.com>
since chip spi driver need get the transfer direction by 'tx_buf' and
'rx_buf' of 'struct spi_transfer' in 'SPI_3WIRE' mode.
so, we need bypass 'SPI_CONTROLLER_MUST_RX' and 'SPI_CONTROLLER_MUST_TX'
feature in 'SPI_3WIRE' mode
Signed-off-by: dillon min <dillon.minfei@gmail.com>
---
drivers/spi/spi.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index c92c894..f884411 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -1023,7 +1023,8 @@ static int spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg)
void *tmp;
unsigned int max_tx, max_rx;
- if (ctlr->flags & (SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX)) {
+ if ((ctlr->flags & (SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX))
+ && !(msg->spi->mode & SPI_3WIRE)) {
max_tx = 0;
max_rx = 0;
--
2.7.4
^ permalink raw reply related
* [PATCH v5 5/8] clk: stm32: Fix stm32f429's ltdc driver hang in set clock rate, fix duplicated ltdc clock register to 'clk_core' case ltdc's clock turn off by clk_disable_unused()
From: dillon.minfei @ 2020-05-25 3:45 UTC (permalink / raw)
To: linus.walleij, broonie
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-spi,
linux-stm32, dri-devel, linux-clk, dillon min
In-Reply-To: <1590378348-8115-1-git-send-email-dillon.minfei@gmail.com>
From: dillon min <dillon.minfei@gmail.com>
ltdc set clock rate crashed
'post_div_data[]''s pll_num is PLL_I2S, PLL_SAI (number is 1,2). but,
as pll_num is offset of 'clks[]' input to clk_register_pll_div(), which
is FCLK, CLK_LSI, defined in 'include/dt-bindings/clock/stm32fx-clock.h'
so, this is a null object at the register time.
then, in ltdc's clock is_enabled(), enable(), will call to_clk_gate().
will return a null object, cause kernel crashed.
need change pll_num to PLL_VCO_I2S, PLL_VCO_SAI for 'post_div_data[]'
duplicated ltdc clock
'stm32f429_gates[]' has a member 'ltdc' register to 'clk_core', but no
upper driver use it, ltdc driver use the lcd-tft defined in
'stm32f429_aux_clk[]'. after system startup, as stm32f429_gates[]'s ltdc
enable_count is zero, so turn off by clk_disable_unused()
Changes since V3:
1 drop last wrong changes about 'CLK_IGNORE_UNUSED' patch
2 fix PLL_SAI mismatch with PLL_VCO_SAI
Signed-off-by: dillon min <dillon.minfei@gmail.com>
---
drivers/clk/clk-stm32f4.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/drivers/clk/clk-stm32f4.c b/drivers/clk/clk-stm32f4.c
index 18117ce..fa62e99 100644
--- a/drivers/clk/clk-stm32f4.c
+++ b/drivers/clk/clk-stm32f4.c
@@ -129,7 +129,6 @@ static const struct stm32f4_gate_data stm32f429_gates[] __initconst = {
{ STM32F4_RCC_APB2ENR, 20, "spi5", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 21, "spi6", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 22, "sai1", "apb2_div" },
- { STM32F4_RCC_APB2ENR, 26, "ltdc", "apb2_div" },
};
static const struct stm32f4_gate_data stm32f469_gates[] __initconst = {
@@ -557,13 +556,13 @@ static const struct clk_div_table post_divr_table[] = {
#define MAX_POST_DIV 3
static const struct stm32f4_pll_post_div_data post_div_data[MAX_POST_DIV] = {
- { CLK_I2SQ_PDIV, PLL_I2S, "plli2s-q-div", "plli2s-q",
+ { CLK_I2SQ_PDIV, PLL_VCO_I2S, "plli2s-q-div", "plli2s-q",
CLK_SET_RATE_PARENT, STM32F4_RCC_DCKCFGR, 0, 5, 0, NULL},
- { CLK_SAIQ_PDIV, PLL_SAI, "pllsai-q-div", "pllsai-q",
+ { CLK_SAIQ_PDIV, PLL_VCO_SAI, "pllsai-q-div", "pllsai-q",
CLK_SET_RATE_PARENT, STM32F4_RCC_DCKCFGR, 8, 5, 0, NULL },
- { NO_IDX, PLL_SAI, "pllsai-r-div", "pllsai-r", CLK_SET_RATE_PARENT,
+ { NO_IDX, PLL_VCO_SAI, "pllsai-r-div", "pllsai-r", CLK_SET_RATE_PARENT,
STM32F4_RCC_DCKCFGR, 16, 2, 0, post_divr_table },
};
--
2.7.4
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox