diff options
author | Ram Chandra Jangir <rjangi@codeaurora.org> | 2016-05-21 14:48:31 +0530 |
---|---|---|
committer | John Crispin <john@phrozen.org> | 2016-05-27 15:50:17 +0200 |
commit | 5e49c5795697868f6317f17fda5a2b04f299e116 (patch) | |
tree | bf6ebcc54ae88fa08f268f02a8f118939fe6bf6a /target/linux/ipq806x/patches-4.4 | |
parent | 955c341d3bec0eb4971a03924e99156367255d7b (diff) | |
download | mtk-20170518-5e49c5795697868f6317f17fda5a2b04f299e116.zip mtk-20170518-5e49c5795697868f6317f17fda5a2b04f299e116.tar.gz mtk-20170518-5e49c5795697868f6317f17fda5a2b04f299e116.tar.bz2 |
ipq806x: Add support for linux-4.4
1)Changes
- Rebased the patches for linux-4.4.7
- Added patch to fix spi nor fifo and dma support
- Added patch to configure watchdog barktime
2)Testing
Tested on IPQ AP148 Board:
a. NOR boot and NAND boot
b. ethernet network and ath10k wifi
c. ubi sysupgrade
UnTested
dwc3 usb has not been validated on IPQ board(AP148)
3)Known Issues:
Once we flash ubi image on AP148, and if we reset the board, uboot on
first boot creates PEB and LEB for dynamic sized partitions, which is incorrect
and not what linux expects which causes errors when trying to mount rootfs.
In order to test this, we can use the below steps:
a. Flash the ubi image on board and don't reset the board
b. load the kernel fit image in RAM and boot from there.
Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org>
Diffstat (limited to 'target/linux/ipq806x/patches-4.4')
47 files changed, 10905 insertions, 0 deletions
diff --git a/target/linux/ipq806x/patches-4.4/020-add-ap148-bootargs.patch b/target/linux/ipq806x/patches-4.4/020-add-ap148-bootargs.patch new file mode 100644 index 0000000..9c33bad --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/020-add-ap148-bootargs.patch @@ -0,0 +1,61 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -4,14 +4,6 @@ + model = "Qualcomm IPQ8064/AP148"; + compatible = "qcom,ipq8064-ap148", "qcom,ipq8064"; + +- aliases { +- serial0 = &gsbi4_serial; +- }; +- +- chosen { +- stdout-path = "serial0:115200n8"; +- }; +- + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; +@@ -22,6 +14,14 @@ + }; + }; + ++ alias { ++ serial0 = &uart4; ++ }; ++ ++ chosen { ++ linux,stdout-path = "serial0:115200n8"; ++ }; ++ + soc { + pinmux@800000 { + i2c4_pins: i2c4_pinmux { +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -159,7 +159,7 @@ + + syscon-tcsr = <&tcsr>; + +- serial@12490000 { ++ uart2: serial@12490000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x12490000 0x1000>, + <0x12480000 0x1000>; +@@ -197,7 +197,7 @@ + + syscon-tcsr = <&tcsr>; + +- gsbi4_serial: serial@16340000 { ++ uart4: serial@16340000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x16340000 0x1000>, + <0x16300000 0x1000>; +@@ -234,7 +234,7 @@ + + syscon-tcsr = <&tcsr>; + +- serial@1a240000 { ++ uart5: serial@1a240000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x1a240000 0x1000>, + <0x1a200000 0x1000>; diff --git a/target/linux/ipq806x/patches-4.4/021-add-ap148-partitions.patch b/target/linux/ipq806x/patches-4.4/021-add-ap148-partitions.patch new file mode 100644 index 0000000..bfdb30f --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/021-add-ap148-partitions.patch @@ -0,0 +1,19 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -77,15 +77,7 @@ + spi-max-frequency = <50000000>; + reg = <0>; + +- partition@0 { +- label = "rootfs"; +- reg = <0x0 0x1000000>; +- }; +- +- partition@1 { +- label = "scratch"; +- reg = <0x1000000 0x1000000>; +- }; ++ linux,part-probe = "qcom-smem"; + }; + }; + }; diff --git a/target/linux/ipq806x/patches-4.4/022-add-db149-dts.patch b/target/linux/ipq806x/patches-4.4/022-add-db149-dts.patch new file mode 100644 index 0000000..4d3e827 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/022-add-db149-dts.patch @@ -0,0 +1,160 @@ +From f26cc3733bdd697bd81ae505fc133fa7c9b6ea19 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Tue, 7 Apr 2015 19:58:58 -0700 +Subject: [PATCH] ARM: dts: qcom: add initial DB149 device-tree + +Add basic DB149 (IPQ806x based platform) device-tree. It supports UART, +SATA, USB2, USB3 and NOR flash. + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + arch/arm/boot/dts/Makefile | 1 + + arch/arm/boot/dts/qcom-ipq8064-db149.dts | 132 +++++++++++++++++++++++++++++++ + 2 files changed, 133 insertions(+) + create mode 100644 arch/arm/boot/dts/qcom-ipq8064-db149.dts + +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -506,6 +506,7 @@ + qcom-apq8084-ifc6540.dtb \ + qcom-apq8084-mtp.dtb \ + qcom-ipq8064-ap148.dtb \ ++ qcom-ipq8064-db149.dtb \ + qcom-msm8660-surf.dtb \ + qcom-msm8960-cdp.dtb \ + qcom-msm8974-sony-xperia-honami.dtb +--- /dev/null ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -0,0 +1,132 @@ ++#include "qcom-ipq8064-v1.0.dtsi" ++ ++/ { ++ model = "Qualcomm IPQ8064/DB149"; ++ compatible = "qcom,ipq8064-db149", "qcom,ipq8064"; ++ ++ reserved-memory { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ rsvd@41200000 { ++ reg = <0x41200000 0x300000>; ++ no-map; ++ }; ++ }; ++ ++ alias { ++ serial0 = &uart2; ++ }; ++ ++ chosen { ++ linux,stdout-path = "serial0:115200n8"; ++ }; ++ ++ soc { ++ pinmux@800000 { ++ i2c4_pins: i2c4_pinmux { ++ pins = "gpio12", "gpio13"; ++ function = "gsbi4"; ++ bias-disable; ++ }; ++ ++ spi_pins: spi_pins { ++ mux { ++ pins = "gpio18", "gpio19", "gpio21"; ++ function = "gsbi5"; ++ drive-strength = <10>; ++ bias-none; ++ }; ++ }; ++ }; ++ ++ gsbi2: gsbi@12480000 { ++ qcom,mode = <GSBI_PROT_I2C_UART>; ++ status = "ok"; ++ uart2: serial@12490000 { ++ status = "ok"; ++ }; ++ }; ++ ++ gsbi5: gsbi@1a200000 { ++ qcom,mode = <GSBI_PROT_SPI>; ++ status = "ok"; ++ ++ spi4: spi@1a280000 { ++ status = "ok"; ++ spi-max-frequency = <50000000>; ++ ++ pinctrl-0 = <&spi_pins>; ++ pinctrl-names = "default"; ++ ++ cs-gpios = <&qcom_pinmux 20 0>; ++ ++ flash: m25p80@0 { ++ compatible = "s25fl256s1"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ spi-max-frequency = <50000000>; ++ reg = <0>; ++ m25p,fast-read; ++ ++ partition@0 { ++ label = "lowlevel_init"; ++ reg = <0x0 0x1b0000>; ++ }; ++ ++ partition@1 { ++ label = "u-boot"; ++ reg = <0x1b0000 0x80000>; ++ }; ++ ++ partition@2 { ++ label = "u-boot-env"; ++ reg = <0x230000 0x40000>; ++ }; ++ ++ partition@3 { ++ label = "caldata"; ++ reg = <0x270000 0x40000>; ++ }; ++ ++ partition@4 { ++ label = "firmware"; ++ reg = <0x2b0000 0x1d50000>; ++ }; ++ }; ++ }; ++ }; ++ ++ sata-phy@1b400000 { ++ status = "ok"; ++ }; ++ ++ sata@29000000 { ++ status = "ok"; ++ }; ++ ++ phy@100f8800 { /* USB3 port 1 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@100f8830 { /* USB3 port 1 SS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8800 { /* USB3 port 0 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8830 { /* USB3 port 0 SS phy */ ++ status = "ok"; ++ }; ++ ++ usb30@0 { ++ status = "ok"; ++ }; ++ ++ usb30@1 { ++ status = "ok"; ++ }; ++ }; ++}; diff --git a/target/linux/ipq806x/patches-4.4/023-ARM-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch b/target/linux/ipq806x/patches-4.4/023-ARM-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch new file mode 100644 index 0000000..b8c527b --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/023-ARM-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch @@ -0,0 +1,52 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -47,14 +47,12 @@ + status = "ok"; + }; + +- i2c4: i2c@16380000 { +- status = "ok"; +- +- clock-frequency = <200000>; +- +- pinctrl-0 = <&i2c4_pins>; +- pinctrl-names = "default"; +- }; ++ /* ++ * The i2c device on gsbi4 should not be enabled. ++ * On ipq806x designs gsbi4 i2c is meant for exclusive ++ * RPM usage. Turning this on in kernel manifests as ++ * i2c failure for the RPM. ++ */ + }; + + gsbi5: gsbi@1a200000 { +--- a/drivers/clk/qcom/gcc-ipq806x.c ++++ b/drivers/clk/qcom/gcc-ipq806x.c +@@ -294,7 +294,7 @@ + .parent_names = gcc_pxo_pll8, + .num_parents = 2, + .ops = &clk_rcg_ops, +- .flags = CLK_SET_PARENT_GATE, ++ .flags = CLK_SET_PARENT_GATE | CLK_IGNORE_UNUSED, + }, + }, + }; +@@ -312,7 +312,7 @@ + }, + .num_parents = 1, + .ops = &clk_branch_ops, +- .flags = CLK_SET_RATE_PARENT, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + }, + }, + }; +@@ -890,7 +890,7 @@ + .hw.init = &(struct clk_init_data){ + .name = "gsbi1_h_clk", + .ops = &clk_branch_ops, +- .flags = CLK_IS_ROOT, ++ .flags = CLK_IS_ROOT | CLK_IGNORE_UNUSED, + }, + }, + }; diff --git a/target/linux/ipq806x/patches-4.4/024-ap148-add-memory-node.patch b/target/linux/ipq806x/patches-4.4/024-ap148-add-memory-node.patch new file mode 100644 index 0000000..f026ed9 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/024-ap148-add-memory-node.patch @@ -0,0 +1,14 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -4,6 +4,11 @@ + model = "Qualcomm IPQ8064/AP148"; + compatible = "qcom,ipq8064-ap148", "qcom,ipq8064"; + ++ memory@0 { ++ reg = <0x42000000 0x1e000000>; ++ device_type = "memory"; ++ }; ++ + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; diff --git a/target/linux/ipq806x/patches-4.4/033-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch b/target/linux/ipq806x/patches-4.4/033-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch new file mode 100644 index 0000000..e1d317d --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/033-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch @@ -0,0 +1,33 @@ +From c7c482da19a5e4ba7101198c21c2434056b0b2da Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Thu, 13 Aug 2015 09:45:26 -0700 +Subject: [PATCH 1/3] ARM: qcom: add SFPB nodes to IPQ806x dts + +Add one new node to the ipq806x.dtsi file to declare & register the +hardware spinlock devices. This mechanism is required to be used by +other drivers such as SMEM. + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + arch/arm/boot/dts/qcom-ipq8064.dtsi | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -329,5 +329,16 @@ + #reset-cells = <1>; + }; + ++ sfpb_mutex_block: syscon@1200600 { ++ compatible = "syscon"; ++ reg = <0x01200600 0x100>; ++ }; ++ }; ++ ++ sfpb_mutex: sfpb-mutex { ++ compatible = "qcom,sfpb-mutex"; ++ syscon = <&sfpb_mutex_block 4 4>; ++ ++ #hwlock-cells = <1>; + }; + }; diff --git a/target/linux/ipq806x/patches-4.4/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch b/target/linux/ipq806x/patches-4.4/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch new file mode 100644 index 0000000..06b1405 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch @@ -0,0 +1,36 @@ +From f212be3a6134db8dd7c5f6f0987536a669401fae Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Fri, 14 Aug 2015 11:17:20 -0700 +Subject: [PATCH 2/3] ARM: qcom: add SMEM device node to IPQ806x dts + +SMEM is used on IPQ806x to store various board related information such +as boot device and flash partition layout. We'll declare it as a device +so we can make use of it thanks to the new SMEM soc driver. + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + arch/arm/boot/dts/qcom-ipq8064.dtsi | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -55,7 +55,7 @@ + no-map; + }; + +- smem@41000000 { ++ smem: smem@41000000 { + reg = <0x41000000 0x200000>; + no-map; + }; +@@ -341,4 +341,10 @@ + + #hwlock-cells = <1>; + }; ++ ++ smem { ++ compatible = "qcom,smem"; ++ memory-region = <&smem>; ++ hwlocks = <&sfpb_mutex 3>; ++ }; + }; diff --git a/target/linux/ipq806x/patches-4.4/037-mtd-add-SMEM-parser-for-QCOM-platforms.patch b/target/linux/ipq806x/patches-4.4/037-mtd-add-SMEM-parser-for-QCOM-platforms.patch new file mode 100644 index 0000000..d80eb86 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/037-mtd-add-SMEM-parser-for-QCOM-platforms.patch @@ -0,0 +1,282 @@ +From 61e8e1b1af77f24339da3f0822a76fa65ed635c6 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Thu, 13 Aug 2015 09:53:14 -0700 +Subject: [PATCH] mtd: add SMEM parser for QCOM platforms + +On QCOM platforms using MTD devices storage (such as IPQ806x), SMEM is +used to store partition layout. This new parser can now be used to read +SMEM and use it to register an MTD layout according to its content. + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org> +--- + drivers/mtd/Kconfig | 7 ++ + drivers/mtd/Makefile | 1 + + drivers/mtd/qcom_smem_part.c | 228 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 236 insertions(+) + create mode 100644 drivers/mtd/qcom_smem_part.c + +diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig +index a03ad29..debc887 100644 +--- a/drivers/mtd/Kconfig ++++ b/drivers/mtd/Kconfig +@@ -190,6 +190,13 @@ + You will still need the parsing functions to be called by the driver + for your particular device. It won't happen automatically. + ++config MTD_QCOM_SMEM_PARTS ++ tristate "QCOM SMEM partitioning support" ++ depends on QCOM_SMEM ++ help ++ This provides partitions parser for QCOM devices using SMEM ++ such as IPQ806x. ++ + comment "User Modules And Translation Layers" + + # +diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile +index 99bb9a1..2a44a64 100644 +--- a/drivers/mtd/Makefile ++++ b/drivers/mtd/Makefile +@@ -16,6 +16,7 @@ + obj-$(CONFIG_MTD_BCM63XX_PARTS) += bcm63xxpart.o + obj-$(CONFIG_MTD_BCM47XX_PARTS) += bcm47xxpart.o + obj-$(CONFIG_MTD_MYLOADER_PARTS) += myloader.o ++obj-$(CONFIG_MTD_QCOM_SMEM_PARTS) += qcom_smem_part.o + + # 'Users' - code which presents functionality to userspace. + obj-$(CONFIG_MTD_BLKDEVS) += mtd_blkdevs.o +diff --git a/drivers/mtd/qcom_smem_part.c b/drivers/mtd/qcom_smem_part.c +new file mode 100644 +index 0000000..f9c1bca +--- /dev/null ++++ b/drivers/mtd/qcom_smem_part.c +@@ -0,0 +1,228 @@ ++/* ++ * Copyright (c) 2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/device.h> ++#include <linux/slab.h> ++ ++#include <linux/mtd/mtd.h> ++#include <linux/mtd/partitions.h> ++#include <linux/spi/spi.h> ++#include <linux/module.h> ++ ++#include <linux/soc/qcom/smem.h> ++ ++/* Processor/host identifier for the application processor */ ++#define SMEM_HOST_APPS 0 ++ ++/* SMEM items index */ ++#define SMEM_AARM_PARTITION_TABLE 9 ++#define SMEM_BOOT_FLASH_TYPE 421 ++#define SMEM_BOOT_FLASH_BLOCK_SIZE 424 ++ ++/* SMEM Flash types */ ++#define SMEM_FLASH_NAND 2 ++#define SMEM_FLASH_SPI 6 ++ ++#define SMEM_PART_NAME_SZ 16 ++#define SMEM_PARTS_MAX 32 ++ ++struct smem_partition { ++ char name[SMEM_PART_NAME_SZ]; ++ __le32 start; ++ __le32 size; ++ __le32 attr; ++}; ++ ++struct smem_partition_table { ++ u8 magic[8]; ++ __le32 version; ++ __le32 len; ++ struct smem_partition parts[SMEM_PARTS_MAX]; ++}; ++ ++/* SMEM Magic values in partition table */ ++static const u8 SMEM_PTABLE_MAGIC[] = { ++ 0xaa, 0x73, 0xee, 0x55, ++ 0xdb, 0xbd, 0x5e, 0xe3, ++}; ++ ++static int qcom_smem_get_flash_blksz(u64 **smem_blksz) ++{ ++ size_t size; ++ ++ *smem_blksz = qcom_smem_get(SMEM_HOST_APPS, SMEM_BOOT_FLASH_BLOCK_SIZE, ++ &size); ++ ++ if (IS_ERR(*smem_blksz)) { ++ pr_err("Unable to read flash blksz from SMEM\n"); ++ return -ENOENT; ++ } ++ ++ if (size != sizeof(**smem_blksz)) { ++ pr_err("Invalid flash blksz size in SMEM\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int qcom_smem_get_flash_type(u64 **smem_flash_type) ++{ ++ size_t size; ++ ++ *smem_flash_type = qcom_smem_get(SMEM_HOST_APPS, SMEM_BOOT_FLASH_TYPE, ++ &size); ++ ++ if (IS_ERR(*smem_flash_type)) { ++ pr_err("Unable to read flash type from SMEM\n"); ++ return -ENOENT; ++ } ++ ++ if (size != sizeof(**smem_flash_type)) { ++ pr_err("Invalid flash type size in SMEM\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int qcom_smem_get_flash_partitions(struct smem_partition_table **pparts) ++{ ++ size_t size; ++ ++ *pparts = qcom_smem_get(SMEM_HOST_APPS, SMEM_AARM_PARTITION_TABLE, ++ &size); ++ ++ if (IS_ERR(*pparts)) { ++ pr_err("Unable to read partition table from SMEM\n"); ++ return -ENOENT; ++ } ++ ++ return 0; ++} ++ ++static int of_dev_node_match(struct device *dev, void *data) ++{ ++ return dev->of_node == data; ++} ++ ++static bool is_spi_device(struct device_node *np) ++{ ++ struct device *dev; ++ ++ dev = bus_find_device(&spi_bus_type, NULL, np, of_dev_node_match); ++ if (!dev) ++ return false; ++ ++ put_device(dev); ++ return true; ++} ++ ++static int parse_qcom_smem_partitions(struct mtd_info *master, ++ struct mtd_partition **pparts, ++ struct mtd_part_parser_data *data) ++{ ++ struct smem_partition_table *smem_parts; ++ u64 *smem_flash_type, *smem_blksz; ++ struct mtd_partition *mtd_parts; ++ struct device_node *of_node = data->of_node; ++ int i, ret; ++ ++ /* ++ * SMEM will only store the partition table of the boot device. ++ * If this is not the boot device, do not return any partition. ++ */ ++ ret = qcom_smem_get_flash_type(&smem_flash_type); ++ if (ret < 0) ++ return ret; ++ ++ if ((*smem_flash_type == SMEM_FLASH_NAND && !mtd_type_is_nand(master)) ++ || (*smem_flash_type == SMEM_FLASH_SPI && !is_spi_device(of_node))) ++ return 0; ++ ++ /* ++ * Just for sanity purpose, make sure the block size in SMEM matches the ++ * block size of the MTD device ++ */ ++ ret = qcom_smem_get_flash_blksz(&smem_blksz); ++ if (ret < 0) ++ return ret; ++ ++ if (*smem_blksz != master->erasesize) { ++ pr_err("SMEM block size differs from MTD block size\n"); ++ return -EINVAL; ++ } ++ ++ /* Get partition pointer from SMEM */ ++ ret = qcom_smem_get_flash_partitions(&smem_parts); ++ if (ret < 0) ++ return ret; ++ ++ if (memcmp(SMEM_PTABLE_MAGIC, smem_parts->magic, ++ sizeof(SMEM_PTABLE_MAGIC))) { ++ pr_err("SMEM partition magic invalid\n"); ++ return -EINVAL; ++ } ++ ++ /* Allocate and populate the mtd structures */ ++ mtd_parts = kcalloc(le32_to_cpu(smem_parts->len), sizeof(*mtd_parts), ++ GFP_KERNEL); ++ if (!mtd_parts) ++ return -ENOMEM; ++ ++ for (i = 0; i < smem_parts->len; i++) { ++ struct smem_partition *s_part = &smem_parts->parts[i]; ++ struct mtd_partition *m_part = &mtd_parts[i]; ++ ++ m_part->name = s_part->name; ++ m_part->size = le32_to_cpu(s_part->size) * (*smem_blksz); ++ m_part->offset = le32_to_cpu(s_part->start) * (*smem_blksz); ++ ++ /* ++ * The last SMEM partition may have its size marked as ++ * something like 0xffffffff, which means "until the end of the ++ * flash device". In this case, truncate it. ++ */ ++ if (m_part->offset + m_part->size > master->size) ++ m_part->size = master->size - m_part->offset; ++ } ++ ++ *pparts = mtd_parts; ++ ++ return smem_parts->len; ++} ++ ++static struct mtd_part_parser qcom_smem_parser = { ++ .owner = THIS_MODULE, ++ .parse_fn = parse_qcom_smem_partitions, ++ .name = "qcom-smem", ++}; ++ ++static int __init qcom_smem_parser_init(void) ++{ ++ register_mtd_parser(&qcom_smem_parser); ++ return 0; ++} ++ ++static void __exit qcom_smem_parser_exit(void) ++{ ++ deregister_mtd_parser(&qcom_smem_parser); ++} ++ ++module_init(qcom_smem_parser_init); ++module_exit(qcom_smem_parser_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Mathieu Olivari <mathieu@codeaurora.org>"); ++MODULE_DESCRIPTION("Parsing code for SMEM based partition tables"); diff --git a/target/linux/ipq806x/patches-4.4/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch b/target/linux/ipq806x/patches-4.4/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch new file mode 100644 index 0000000..68f2b39 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch @@ -0,0 +1,510 @@ +--- a/drivers/phy/Kconfig ++++ b/drivers/phy/Kconfig +@@ -390,4 +390,15 @@ + Enable this to support the Broadcom Cygnus PCIe PHY. + If unsure, say N. + ++config PHY_QCOM_DWC3 ++ tristate "QCOM DWC3 USB PHY support" ++ depends on ARCH_QCOM ++ depends on HAS_IOMEM ++ depends on OF ++ select GENERIC_PHY ++ help ++ This option enables support for the Synopsis PHYs present inside the ++ Qualcomm USB3.0 DWC3 controller. This driver supports both HS and SS ++ PHY controllers. ++ + endmenu +--- a/drivers/phy/Makefile ++++ b/drivers/phy/Makefile +@@ -48,3 +48,4 @@ obj-$(CONFIG_PHY_TUSB1210) += + obj-$(CONFIG_PHY_BRCMSTB_SATA) += phy-brcmstb-sata.o + obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o + obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o ++obj-$(CONFIG_PHY_QCOM_DWC3) += phy-qcom-dwc3.o +--- /dev/null ++++ b/drivers/phy/phy-qcom-dwc3.c +@@ -0,0 +1,482 @@ ++/* Copyright (c) 2013-2014, Code Aurora Forum. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/clk.h> ++#include <linux/err.h> ++#include <linux/io.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/phy/phy.h> ++#include <linux/platform_device.h> ++#include <linux/delay.h> ++ ++/** ++ * USB QSCRATCH Hardware registers ++ */ ++#define QSCRATCH_GENERAL_CFG (0x08) ++#define HSUSB_PHY_CTRL_REG (0x10) ++ ++/* PHY_CTRL_REG */ ++#define HSUSB_CTRL_DMSEHV_CLAMP BIT(24) ++#define HSUSB_CTRL_USB2_SUSPEND BIT(23) ++#define HSUSB_CTRL_UTMI_CLK_EN BIT(21) ++#define HSUSB_CTRL_UTMI_OTG_VBUS_VALID BIT(20) ++#define HSUSB_CTRL_USE_CLKCORE BIT(18) ++#define HSUSB_CTRL_DPSEHV_CLAMP BIT(17) ++#define HSUSB_CTRL_COMMONONN BIT(11) ++#define HSUSB_CTRL_ID_HV_CLAMP BIT(9) ++#define HSUSB_CTRL_OTGSESSVLD_CLAMP BIT(8) ++#define HSUSB_CTRL_CLAMP_EN BIT(7) ++#define HSUSB_CTRL_RETENABLEN BIT(1) ++#define HSUSB_CTRL_POR BIT(0) ++ ++/* QSCRATCH_GENERAL_CFG */ ++#define HSUSB_GCFG_XHCI_REV BIT(2) ++ ++/** ++ * USB QSCRATCH Hardware registers ++ */ ++#define SSUSB_PHY_CTRL_REG (0x00) ++#define SSUSB_PHY_PARAM_CTRL_1 (0x04) ++#define SSUSB_PHY_PARAM_CTRL_2 (0x08) ++#define CR_PROTOCOL_DATA_IN_REG (0x0c) ++#define CR_PROTOCOL_DATA_OUT_REG (0x10) ++#define CR_PROTOCOL_CAP_ADDR_REG (0x14) ++#define CR_PROTOCOL_CAP_DATA_REG (0x18) ++#define CR_PROTOCOL_READ_REG (0x1c) ++#define CR_PROTOCOL_WRITE_REG (0x20) ++ ++/* PHY_CTRL_REG */ ++#define SSUSB_CTRL_REF_USE_PAD BIT(28) ++#define SSUSB_CTRL_TEST_POWERDOWN BIT(27) ++#define SSUSB_CTRL_LANE0_PWR_PRESENT BIT(24) ++#define SSUSB_CTRL_SS_PHY_EN BIT(8) ++#define SSUSB_CTRL_SS_PHY_RESET BIT(7) ++ ++/* SSPHY control registers */ ++#define SSPHY_CTRL_RX_OVRD_IN_HI(lane) (0x1006 + 0x100 * lane) ++#define SSPHY_CTRL_TX_OVRD_DRV_LO(lane) (0x1002 + 0x100 * lane) ++ ++/* RX OVRD IN HI bits */ ++#define RX_OVRD_IN_HI_RX_RESET_OVRD BIT(13) ++#define RX_OVRD_IN_HI_RX_RX_RESET BIT(12) ++#define RX_OVRD_IN_HI_RX_EQ_OVRD BIT(11) ++#define RX_OVRD_IN_HI_RX_EQ_MASK 0x0700 ++#define RX_OVRD_IN_HI_RX_EQ_SHIFT 8 ++#define RX_OVRD_IN_HI_RX_EQ_EN_OVRD BIT(7) ++#define RX_OVRD_IN_HI_RX_EQ_EN BIT(6) ++#define RX_OVRD_IN_HI_RX_LOS_FILTER_OVRD BIT(5) ++#define RX_OVRD_IN_HI_RX_LOS_FILTER_MASK 0x0018 ++#define RX_OVRD_IN_HI_RX_RATE_OVRD BIT(2) ++#define RX_OVRD_IN_HI_RX_RATE_MASK 0x0003 ++ ++/* TX OVRD DRV LO register bits */ ++#define TX_OVRD_DRV_LO_AMPLITUDE_MASK 0x007F ++#define TX_OVRD_DRV_LO_PREEMPH_MASK 0x3F80 ++#define TX_OVRD_DRV_LO_PREEMPH_SHIFT 7 ++#define TX_OVRD_DRV_LO_EN BIT(14) ++ ++struct qcom_dwc3_usb_phy { ++ void __iomem *base; ++ struct device *dev; ++ struct phy *phy; ++ ++ int (*phy_init)(struct qcom_dwc3_usb_phy *phy_dwc3); ++ int (*phy_exit)(struct qcom_dwc3_usb_phy *phy_dwc3); ++ ++ struct clk *xo_clk; ++ struct clk *ref_clk; ++}; ++ ++/** ++ * Write register and read back masked value to confirm it is written ++ * ++ * @base - QCOM DWC3 PHY base virtual address. ++ * @offset - register offset. ++ * @mask - register bitmask specifying what should be updated ++ * @val - value to write. ++ */ ++static inline void qcom_dwc3_phy_write_readback( ++ struct qcom_dwc3_usb_phy *phy_dwc3, u32 offset, ++ const u32 mask, u32 val) ++{ ++ u32 write_val, tmp = readl(phy_dwc3->base + offset); ++ ++ tmp &= ~mask; /* retain other bits */ ++ write_val = tmp | val; ++ ++ writel(write_val, phy_dwc3->base + offset); ++ ++ /* Read back to see if val was written */ ++ tmp = readl(phy_dwc3->base + offset); ++ tmp &= mask; /* clear other bits */ ++ ++ if (tmp != val) ++ dev_err(phy_dwc3->dev, "write: %x to QSCRATCH: %x FAILED\n", ++ val, offset); ++} ++ ++static int wait_for_latch(void __iomem *addr) ++{ ++ u32 retry = 10; ++ ++ while (true) { ++ if (!readl(addr)) ++ break; ++ ++ if (--retry == 0) ++ return -ETIMEDOUT; ++ ++ usleep_range(10, 20); ++ } ++ ++ return 0; ++} ++ ++/** ++ * Write SSPHY register ++ * ++ * @base - QCOM DWC3 PHY base virtual address. ++ * @addr - SSPHY address to write. ++ * @val - value to write. ++ */ ++static int qcom_dwc3_ss_write_phycreg(void __iomem *base, u32 addr, u32 val) ++{ ++ int ret; ++ ++ writel(addr, base + CR_PROTOCOL_DATA_IN_REG); ++ writel(0x1, base + CR_PROTOCOL_CAP_ADDR_REG); ++ ++ ret = wait_for_latch(base + CR_PROTOCOL_CAP_ADDR_REG); ++ if (ret) ++ goto err_wait; ++ ++ writel(val, base + CR_PROTOCOL_DATA_IN_REG); ++ writel(0x1, base + CR_PROTOCOL_CAP_DATA_REG); ++ ++ ret = wait_for_latch(base + CR_PROTOCOL_CAP_DATA_REG); ++ if (ret) ++ goto err_wait; ++ ++ writel(0x1, base + CR_PROTOCOL_WRITE_REG); ++ ++ ret = wait_for_latch(base + CR_PROTOCOL_WRITE_REG); ++ ++err_wait: ++ return ret; ++} ++ ++/** ++ * Read SSPHY register. ++ * ++ * @base - QCOM DWC3 PHY base virtual address. ++ * @addr - SSPHY address to read. ++ */ ++static int qcom_dwc3_ss_read_phycreg(void __iomem *base, u32 addr, u32 *val) ++{ ++ int ret; ++ bool first_read = true; ++ ++ writel(addr, base + CR_PROTOCOL_DATA_IN_REG); ++ writel(0x1, base + CR_PROTOCOL_CAP_ADDR_REG); ++ ++ ret = wait_for_latch(base + CR_PROTOCOL_CAP_ADDR_REG); ++ if (ret) ++ goto err_wait; ++ ++ /* ++ * Due to hardware bug, first read of SSPHY register might be ++ * incorrect. Hence as workaround, SW should perform SSPHY register ++ * read twice, but use only second read and ignore first read. ++ */ ++retry: ++ writel(0x1, base + CR_PROTOCOL_READ_REG); ++ ++ ret = wait_for_latch(base + CR_PROTOCOL_READ_REG); ++ if (ret) ++ goto err_wait; ++ ++ if (first_read) { ++ readl(base + CR_PROTOCOL_DATA_OUT_REG); ++ first_read = false; ++ goto retry; ++ } ++ ++ *val = readl(base + CR_PROTOCOL_DATA_OUT_REG); ++ ++err_wait: ++ return ret; ++} ++ ++static int qcom_dwc3_phy_power_on(struct phy *phy) ++{ ++ int ret; ++ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); ++ ++ ret = clk_prepare_enable(phy_dwc3->xo_clk); ++ if (ret) ++ return ret; ++ ++ ret = clk_prepare_enable(phy_dwc3->ref_clk); ++ if (ret) ++ clk_disable_unprepare(phy_dwc3->xo_clk); ++ ++ return ret; ++} ++ ++static int qcom_dwc3_phy_power_off(struct phy *phy) ++{ ++ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); ++ ++ clk_disable_unprepare(phy_dwc3->ref_clk); ++ clk_disable_unprepare(phy_dwc3->xo_clk); ++ ++ return 0; ++} ++ ++static int qcom_dwc3_hs_phy_init(struct qcom_dwc3_usb_phy *phy_dwc3) ++{ ++ u32 val; ++ ++ /* ++ * HSPHY Initialization: Enable UTMI clock, select 19.2MHz fsel ++ * enable clamping, and disable RETENTION (power-on default is ENABLED) ++ */ ++ val = HSUSB_CTRL_DPSEHV_CLAMP | HSUSB_CTRL_DMSEHV_CLAMP | ++ HSUSB_CTRL_RETENABLEN | HSUSB_CTRL_COMMONONN | ++ HSUSB_CTRL_OTGSESSVLD_CLAMP | HSUSB_CTRL_ID_HV_CLAMP | ++ HSUSB_CTRL_DPSEHV_CLAMP | HSUSB_CTRL_UTMI_OTG_VBUS_VALID | ++ HSUSB_CTRL_UTMI_CLK_EN | HSUSB_CTRL_CLAMP_EN | 0x70; ++ ++ /* use core clock if external reference is not present */ ++ if (!phy_dwc3->xo_clk) ++ val |= HSUSB_CTRL_USE_CLKCORE; ++ ++ writel(val, phy_dwc3->base + HSUSB_PHY_CTRL_REG); ++ usleep_range(2000, 2200); ++ ++ /* Disable (bypass) VBUS and ID filters */ ++ writel(HSUSB_GCFG_XHCI_REV, phy_dwc3->base + QSCRATCH_GENERAL_CFG); ++ ++ return 0; ++} ++ ++static int qcom_dwc3_ss_phy_init(struct qcom_dwc3_usb_phy *phy_dwc3) ++{ ++ int ret; ++ u32 data = 0; ++ ++ /* reset phy */ ++ data = readl_relaxed(phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ writel_relaxed(data | SSUSB_CTRL_SS_PHY_RESET, ++ phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ usleep_range(2000, 2200); ++ writel_relaxed(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ ++ /* clear REF_PAD if we don't have XO clk */ ++ if (!phy_dwc3->xo_clk) ++ data &= ~SSUSB_CTRL_REF_USE_PAD; ++ else ++ data |= SSUSB_CTRL_REF_USE_PAD; ++ ++ writel_relaxed(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ msleep(30); ++ ++ data |= SSUSB_CTRL_SS_PHY_EN | SSUSB_CTRL_LANE0_PWR_PRESENT; ++ writel_relaxed(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ ++ /* ++ * Fix RX Equalization setting as follows ++ * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0 ++ * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1 ++ * LANE0.RX_OVRD_IN_HI.RX_EQ set to 3 ++ * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1 ++ */ ++ ret = qcom_dwc3_ss_read_phycreg(phy_dwc3->base, ++ SSPHY_CTRL_RX_OVRD_IN_HI(0), &data); ++ if (ret) ++ goto err_phy_trans; ++ ++ data &= ~RX_OVRD_IN_HI_RX_EQ_EN; ++ data |= RX_OVRD_IN_HI_RX_EQ_EN_OVRD; ++ data &= ~RX_OVRD_IN_HI_RX_EQ_MASK; ++ data |= 0x3 << RX_OVRD_IN_HI_RX_EQ_SHIFT; ++ data |= RX_OVRD_IN_HI_RX_EQ_OVRD; ++ ret = qcom_dwc3_ss_write_phycreg(phy_dwc3->base, ++ SSPHY_CTRL_RX_OVRD_IN_HI(0), data); ++ if (ret) ++ goto err_phy_trans; ++ ++ /* ++ * Set EQ and TX launch amplitudes as follows ++ * LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22 ++ * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127 ++ * LANE0.TX_OVRD_DRV_LO.EN set to 1. ++ */ ++ ret = qcom_dwc3_ss_read_phycreg(phy_dwc3->base, ++ SSPHY_CTRL_TX_OVRD_DRV_LO(0), &data); ++ if (ret) ++ goto err_phy_trans; ++ ++ data &= ~TX_OVRD_DRV_LO_PREEMPH_MASK; ++ data |= 0x16 << TX_OVRD_DRV_LO_PREEMPH_SHIFT; ++ data &= ~TX_OVRD_DRV_LO_AMPLITUDE_MASK; ++ data |= 0x7f; ++ data |= TX_OVRD_DRV_LO_EN; ++ ret = qcom_dwc3_ss_write_phycreg(phy_dwc3->base, ++ SSPHY_CTRL_TX_OVRD_DRV_LO(0), data); ++ if (ret) ++ goto err_phy_trans; ++ ++ /* ++ * Set the QSCRATCH PHY_PARAM_CTRL1 parameters as follows ++ * TX_FULL_SWING [26:20] amplitude to 127 ++ * TX_DEEMPH_3_5DB [13:8] to 22 ++ * LOS_BIAS [2:0] to 0x5 ++ */ ++ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_PARAM_CTRL_1, ++ 0x07f03f07, 0x07f01605); ++ ++err_phy_trans: ++ return ret; ++} ++ ++static int qcom_dwc3_ss_phy_exit(struct qcom_dwc3_usb_phy *phy_dwc3) ++{ ++ /* Sequence to put SSPHY in low power state: ++ * 1. Clear REF_PHY_EN in PHY_CTRL_REG ++ * 2. Clear REF_USE_PAD in PHY_CTRL_REG ++ * 3. Set TEST_POWERED_DOWN in PHY_CTRL_REG to enable PHY retention ++ * 4. Disable SSPHY ref clk ++ */ ++ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG, ++ SSUSB_CTRL_SS_PHY_EN, 0x0); ++ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG, ++ SSUSB_CTRL_REF_USE_PAD, 0x0); ++ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG, ++ 0x0, SSUSB_CTRL_TEST_POWERDOWN); ++ ++ return 0; ++} ++ ++static int qcom_dwc3_phy_init(struct phy *phy) ++{ ++ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); ++ ++ if (phy_dwc3->phy_init) ++ return phy_dwc3->phy_init(phy_dwc3); ++ ++ return 0; ++} ++ ++static int qcom_dwc3_phy_exit(struct phy *phy) ++{ ++ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); ++ ++ if (phy_dwc3->phy_exit) ++ return qcom_dwc3_ss_phy_exit(phy_dwc3); ++ ++ return 0; ++} ++ ++static struct phy_ops qcom_dwc3_phy_ops = { ++ .init = qcom_dwc3_phy_init, ++ .exit = qcom_dwc3_phy_exit, ++ .power_on = qcom_dwc3_phy_power_on, ++ .power_off = qcom_dwc3_phy_power_off, ++ .owner = THIS_MODULE, ++}; ++ ++static const struct of_device_id qcom_dwc3_phy_table[] = { ++ { .compatible = "qcom,dwc3-hs-usb-phy", }, ++ { .compatible = "qcom,dwc3-ss-usb-phy", }, ++ { /* Sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, qcom_dwc3_phy_table); ++ ++static int qcom_dwc3_phy_probe(struct platform_device *pdev) ++{ ++ struct qcom_dwc3_usb_phy *phy_dwc3; ++ struct phy_provider *phy_provider; ++ struct resource *res; ++ ++ phy_dwc3 = devm_kzalloc(&pdev->dev, sizeof(*phy_dwc3), GFP_KERNEL); ++ if (!phy_dwc3) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, phy_dwc3); ++ ++ phy_dwc3->dev = &pdev->dev; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ phy_dwc3->base = devm_ioremap_resource(phy_dwc3->dev, res); ++ if (IS_ERR(phy_dwc3->base)) ++ return PTR_ERR(phy_dwc3->base); ++ ++ phy_dwc3->ref_clk = devm_clk_get(phy_dwc3->dev, "ref"); ++ if (IS_ERR(phy_dwc3->ref_clk)) { ++ dev_dbg(phy_dwc3->dev, "cannot get reference clock\n"); ++ return PTR_ERR(phy_dwc3->ref_clk); ++ } ++ ++ if (of_device_is_compatible(pdev->dev.of_node, ++ "qcom,dwc3-hs-usb-phy")) { ++ clk_set_rate(phy_dwc3->ref_clk, 60000000); ++ phy_dwc3->phy_init = qcom_dwc3_hs_phy_init; ++ } else if (of_device_is_compatible(pdev->dev.of_node, ++ "qcom,dwc3-ss-usb-phy")) { ++ phy_dwc3->phy_init = qcom_dwc3_ss_phy_init; ++ phy_dwc3->phy_exit = qcom_dwc3_ss_phy_exit; ++ clk_set_rate(phy_dwc3->ref_clk, 125000000); ++ } else { ++ dev_err(phy_dwc3->dev, "Unknown phy\n"); ++ return -EINVAL; ++ } ++ ++ phy_dwc3->xo_clk = devm_clk_get(phy_dwc3->dev, "xo"); ++ if (IS_ERR(phy_dwc3->xo_clk)) { ++ dev_dbg(phy_dwc3->dev, "cannot get TCXO clock\n"); ++ phy_dwc3->xo_clk = NULL; ++ } ++ ++ phy_dwc3->phy = devm_phy_create(phy_dwc3->dev, NULL, &qcom_dwc3_phy_ops); ++ ++ if (IS_ERR(phy_dwc3->phy)) ++ return PTR_ERR(phy_dwc3->phy); ++ ++ phy_set_drvdata(phy_dwc3->phy, phy_dwc3); ++ ++ phy_provider = devm_of_phy_provider_register(phy_dwc3->dev, ++ of_phy_simple_xlate); ++ ++ if (IS_ERR(phy_provider)) ++ return PTR_ERR(phy_provider); ++ ++ return 0; ++} ++ ++static struct platform_driver qcom_dwc3_phy_driver = { ++ .probe = qcom_dwc3_phy_probe, ++ .driver = { ++ .name = "qcom-dwc3-usb-phy", ++ .owner = THIS_MODULE, ++ .of_match_table = qcom_dwc3_phy_table, ++ }, ++}; ++ ++module_platform_driver(qcom_dwc3_phy_driver); ++ ++MODULE_ALIAS("platform:phy-qcom-dwc3"); ++MODULE_LICENSE("GPL v2"); ++MODULE_AUTHOR("Andy Gross <agross@codeaurora.org>"); ++MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>"); ++MODULE_DESCRIPTION("DesignWare USB3 QCOM PHY driver"); diff --git a/target/linux/ipq806x/patches-4.4/101-ARM-qcom-add-USB-nodes-to-ipq806x-ap148.patch b/target/linux/ipq806x/patches-4.4/101-ARM-qcom-add-USB-nodes-to-ipq806x-ap148.patch new file mode 100644 index 0000000..6e6f10d --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/101-ARM-qcom-add-USB-nodes-to-ipq806x-ap148.patch @@ -0,0 +1,125 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -92,5 +92,29 @@ + sata@29000000 { + status = "ok"; + }; ++ ++ phy@100f8800 { /* USB3 port 1 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@100f8830 { /* USB3 port 1 SS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8800 { /* USB3 port 0 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8830 { /* USB3 port 0 SS phy */ ++ status = "ok"; ++ }; ++ ++ usb30@0 { ++ status = "ok"; ++ }; ++ ++ usb30@1 { ++ status = "ok"; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -333,6 +333,90 @@ + compatible = "syscon"; + reg = <0x01200600 0x100>; + }; ++ ++ hs_phy_1: phy@100f8800 { ++ compatible = "qcom,dwc3-hs-usb-phy"; ++ reg = <0x100f8800 0x30>; ++ clocks = <&gcc USB30_1_UTMI_CLK>; ++ clock-names = "ref"; ++ #phy-cells = <0>; ++ ++ status = "disabled"; ++ }; ++ ++ ss_phy_1: phy@100f8830 { ++ compatible = "qcom,dwc3-ss-usb-phy"; ++ reg = <0x100f8830 0x30>; ++ clocks = <&gcc USB30_1_MASTER_CLK>; ++ clock-names = "ref"; ++ #phy-cells = <0>; ++ ++ status = "disabled"; ++ }; ++ ++ hs_phy_0: phy@110f8800 { ++ compatible = "qcom,dwc3-hs-usb-phy"; ++ reg = <0x110f8800 0x30>; ++ clocks = <&gcc USB30_0_UTMI_CLK>; ++ clock-names = "ref"; ++ #phy-cells = <0>; ++ ++ status = "disabled"; ++ }; ++ ++ ss_phy_0: phy@110f8830 { ++ compatible = "qcom,dwc3-ss-usb-phy"; ++ reg = <0x110f8830 0x30>; ++ clocks = <&gcc USB30_0_MASTER_CLK>; ++ clock-names = "ref"; ++ #phy-cells = <0>; ++ ++ status = "disabled"; ++ }; ++ ++ usb3_0: usb30@0 { ++ compatible = "qcom,dwc3"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ clocks = <&gcc USB30_0_MASTER_CLK>; ++ clock-names = "core"; ++ ++ ranges; ++ ++ status = "disabled"; ++ ++ dwc3@11000000 { ++ compatible = "snps,dwc3"; ++ reg = <0x11000000 0xcd00>; ++ interrupts = <0 110 0x4>; ++ phys = <&hs_phy_0>, <&ss_phy_0>; ++ phy-names = "usb2-phy", "usb3-phy"; ++ tx-fifo-resize; ++ dr_mode = "host"; ++ }; ++ }; ++ ++ usb3_1: usb30@1 { ++ compatible = "qcom,dwc3"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ clocks = <&gcc USB30_1_MASTER_CLK>; ++ clock-names = "core"; ++ ++ ranges; ++ ++ status = "disabled"; ++ ++ dwc3@10000000 { ++ compatible = "snps,dwc3"; ++ reg = <0x10000000 0xcd00>; ++ interrupts = <0 205 0x4>; ++ phys = <&hs_phy_1>, <&ss_phy_1>; ++ phy-names = "usb2-phy", "usb3-phy"; ++ tx-fifo-resize; ++ dr_mode = "host"; ++ }; ++ }; + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-4.4/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch b/target/linux/ipq806x/patches-4.4/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch new file mode 100644 index 0000000..41f91fa --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch @@ -0,0 +1,263 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,3/5] DT: PCI: qcom: Document PCIe devicetree bindings +From: Stanimir Varbanov <svarbanov@mm-sol.com> +X-Patchwork-Id: 6326181 +Message-Id: <1430743338-10441-4-git-send-email-svarbanov@mm-sol.com> +To: Rob Herring <robh+dt@kernel.org>, Kumar Gala <galak@codeaurora.org>, + Mark Rutland <mark.rutland@arm.com>, + Grant Likely <grant.likely@linaro.org>, + Bjorn Helgaas <bhelgaas@google.com>, + Kishon Vijay Abraham I <kishon@ti.com>, + Russell King <linux@arm.linux.org.uk>, Arnd Bergmann <arnd@arndb.de> +Cc: linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, + linux-pci@vger.kernel.org, Mathieu Olivari <mathieu@codeaurora.org>, + Srinivas Kandagatla <srinivas.kandagatla@linaro.org>, + Stanimir Varbanov <svarbanov@mm-sol.com> +Date: Mon, 4 May 2015 15:42:16 +0300 + +Document Qualcomm PCIe driver devicetree bindings. + +Signed-off-by: Stanimir Varbanov <svarbanov@mm-sol.com> + +--- +.../devicetree/bindings/pci/qcom,pcie.txt | 231 ++++++++++++++++++++ + 1 files changed, 231 insertions(+), 0 deletions(-) + create mode 100644 Documentation/devicetree/bindings/pci/qcom,pcie.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/pci/qcom,pcie.txt +@@ -0,0 +1,231 @@ ++* Qualcomm PCI express root complex ++ ++- compatible: ++ Usage: required ++ Value type: <stringlist> ++ Definition: Value shall include ++ - "qcom,pcie-v0" for apq/ipq8064 ++ - "qcom,pcie-v1" for apq8084 ++ ++- reg: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: Register ranges as listed in the reg-names property ++ ++- reg-names: ++ Usage: required ++ Value type: <stringlist> ++ Definition: Must include the following entries ++ - "parf" Qualcomm specific registers ++ - "dbi" Designware PCIe registers ++ - "elbi" External local bus interface registers ++ - "config" PCIe configuration space ++ ++- device_type: ++ Usage: required ++ Value type: <string> ++ Definition: Should be "pci". As specified in designware-pcie.txt ++ ++- #address-cells: ++ Usage: required ++ Value type: <u32> ++ Definition: Should be set to 3. As specified in designware-pcie.txt ++ ++- #size-cells: ++ Usage: required ++ Value type: <u32> ++ Definition: Should be set 2. As specified in designware-pcie.txt ++ ++- ranges: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: As specified in designware-pcie.txt ++ ++- interrupts: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: MSI interrupt ++ ++- interrupt-names: ++ Usage: required ++ Value type: <stringlist> ++ Definition: Should contain "msi" ++ ++- #interrupt-cells: ++ Usage: required ++ Value type: <u32> ++ Definition: Should be 1. As specified in designware-pcie.txt ++ ++- interrupt-map-mask: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: As specified in designware-pcie.txt ++ ++- interrupt-map: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: As specified in designware-pcie.txt ++ ++- clocks: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: List of phandle and clock specifier pairs as listed ++ in clock-names property ++ ++- clock-names: ++ Usage: required ++ Value type: <stringlist> ++ Definition: Should contain the following entries ++ * should be populated for v0 and v1 ++ - "iface" Configuration AHB clock ++ ++ * should be populated for v0 ++ - "core" Clocks the pcie hw block ++ - "phy" Clocks the pcie PHY block ++ ++ * should be populated for v1 ++ - "aux" Auxiliary (AUX) clock ++ - "bus_master" Master AXI clock ++ - "bus_slave" Slave AXI clock ++ ++- resets: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: List of phandle and reset specifier pairs as listed ++ in reset-names property ++ ++- reset-names: ++ Usage: required ++ Value type: <stringlist> ++ Definition: Should contain the following entries ++ * should be populated for v0 ++ - "axi" AXI reset ++ - "ahb" AHB reset ++ - "por" POR reset ++ - "pci" PCI reset ++ - "phy" PHY reset ++ ++ * should be populated for v1 ++ - "core" Core reset ++ ++- power-domains: ++ Usage: required (for v1 only) ++ Value type: <prop-encoded-array> ++ Definition: A phandle and power domain specifier pair to the ++ power domain which is responsible for collapsing ++ and restoring power to the peripheral ++ ++- <name>-supply: ++ Usage: required ++ Value type: <phandle> ++ Definition: List of phandles to the power supply regulator(s) ++ * should be populated for v0 and v1 ++ - "vdda" core analog power supply ++ ++ * should be populated for v0 ++ - "vdda_phy" analog power supply for PHY ++ - "vdda_refclk" analog power supply for IC which generate ++ reference clock ++ ++- phys: ++ Usage: required (for v1 only) ++ Value type: <phandle> ++ Definition: List of phandle(s) as listed in phy-names property ++ ++- phy-names: ++ Usage: required (for v1 only) ++ Value type: <stringlist> ++ Definition: Should contain "pciephy" ++ ++- <name>-gpio: ++ Usage: optional ++ Value type: <prop-encoded-array> ++ Definition: List of phandle and gpio specifier pairs. Should contain ++ - "perst" PCIe endpoint reset signal line ++ - "pewake" PCIe endpoint wake signal line ++ ++- pinctrl-0: ++ Usage: required ++ Value type: <phandle> ++ Definition: List of phandles pointing at a pin(s) configuration ++ ++- pinctrl-names ++ Usage: required ++ Value type: <stringlist> ++ Definition: List of names of pinctrl-0 state ++ ++* Example for v0 ++ pcie0: pci@1b500000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b500000 0x1000 ++ 0x1b502000 0x80 ++ 0x1b600000 0x100 ++ 0x0ff00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <0>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ranges = <0x81000000 0 0 0x0fe00000 0 0x00100000 /* I/O */ ++ 0x82000000 0 0x00000000 0x08000000 0 0x07e00000>; /* memory */ ++ interrupts = <GIC_SPI 35 IRQ_TYPE_NONE>; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ clocks = <&gcc PCIE_A_CLK>, ++ <&gcc PCIE_H_CLK>, ++ <&gcc PCIE_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ resets = <&gcc PCIE_ACLK_RESET>, ++ <&gcc PCIE_HCLK_RESET>, ++ <&gcc PCIE_POR_RESET>, ++ <&gcc PCIE_PCI_RESET>, ++ <&gcc PCIE_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ }; ++ ++* Example for v1 ++ pcie0@fc520000 { ++ compatible = "qcom,pcie-v1"; ++ reg = <0xfc520000 0x2000>, ++ <0xff000000 0x1000>, ++ <0xff001000 0x1000>, ++ <0xff002000 0x2000>; ++ reg-names = "parf", "dbi", "elbi", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <0>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ranges = <0x81000000 0 0 0xff200000 0 0x00100000 /* I/O */ ++ 0x82000000 0 0x00300000 0xff300000 0 0x00d00000>; /* memory */ ++ interrupts = <GIC_SPI 243 IRQ_TYPE_NONE>; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 244 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 245 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 247 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 248 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ clocks = <&gcc GCC_PCIE_0_CFG_AHB_CLK>, ++ <&gcc GCC_PCIE_0_MSTR_AXI_CLK>, ++ <&gcc GCC_PCIE_0_SLV_AXI_CLK>, ++ <&gcc GCC_PCIE_0_AUX_CLK>; ++ clock-names = "iface", "master_bus", "slave_bus", "aux"; ++ resets = <&gcc GCC_PCIE_0_BCR>; ++ reset-names = "core"; ++ power-domains = <&gcc PCIE0_GDSC>; ++ vdda-supply = <&pma8084_l3>; ++ phys = <&pciephy0>; ++ phy-names = "pciephy"; ++ perst-gpio = <&tlmm 70 GPIO_ACTIVE_LOW>; ++ pinctrl-0 = <&pcie0_pins_default>; ++ pinctrl-names = "default"; ++ }; diff --git a/target/linux/ipq806x/patches-4.4/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch b/target/linux/ipq806x/patches-4.4/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch new file mode 100644 index 0000000..ad1a1b9 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch @@ -0,0 +1,752 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,4/5] PCI: qcom: Add Qualcomm PCIe controller driver +From: Stanimir Varbanov <svarbanov@mm-sol.com> +X-Patchwork-Id: 6326161 +Message-Id: <1430743338-10441-5-git-send-email-svarbanov@mm-sol.com> +To: Rob Herring <robh+dt@kernel.org>, Kumar Gala <galak@codeaurora.org>, + Mark Rutland <mark.rutland@arm.com>, + Grant Likely <grant.likely@linaro.org>, + Bjorn Helgaas <bhelgaas@google.com>, + Kishon Vijay Abraham I <kishon@ti.com>, + Russell King <linux@arm.linux.org.uk>, Arnd Bergmann <arnd@arndb.de> +Cc: linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, + linux-pci@vger.kernel.org, Mathieu Olivari <mathieu@codeaurora.org>, + Srinivas Kandagatla <srinivas.kandagatla@linaro.org>, + Stanimir Varbanov <svarbanov@mm-sol.com> +Date: Mon, 4 May 2015 15:42:17 +0300 + +The PCIe driver reuse the Designware common code for host +and MSI initialization, and also program the Qualcomm +application specific registers. + +Signed-off-by: Stanimir Varbanov <svarbanov@mm-sol.com> + +--- +MAINTAINERS | 7 + + drivers/pci/host/Kconfig | 9 + + drivers/pci/host/Makefile | 1 + + drivers/pci/host/pcie-qcom.c | 677 ++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 694 insertions(+), 0 deletions(-) + create mode 100644 drivers/pci/host/pcie-qcom.c + +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -8253,6 +8253,13 @@ + F: Documentation/devicetree/bindings/pci/hisilicon-pcie.txt + F: drivers/pci/host/pcie-hisi.c + ++PCIE DRIVER FOR QUALCOMM MSM ++M: Stanimir Varbanov <svarbanov@mm-sol.com> ++L: linux-pci@vger.kernel.org ++L: linux-arm-msm@vger.kernel.org ++S: Maintained ++F: drivers/pci/host/*qcom* ++ + PCMCIA SUBSYSTEM + P: Linux PCMCIA Team + L: linux-pcmcia@lists.infradead.org +--- a/drivers/pci/host/Kconfig ++++ b/drivers/pci/host/Kconfig +@@ -173,4 +173,13 @@ + help + Say Y here if you want PCIe controller support on HiSilicon HIP05 SoC + ++config PCIE_QCOM ++ bool "Qualcomm PCIe controller" ++ depends on ARCH_QCOM && OF || (ARM && COMPILE_TEST) ++ select PCIE_DW ++ select PCIEPORTBUS ++ help ++ Say Y here to enable PCIe controller support on Qualcomm SoCs. The ++ PCIe controller use Designware core plus Qualcomm specific hardware ++ wrappers. + endmenu +--- /dev/null ++++ b/drivers/pci/host/pcie-qcom.c +@@ -0,0 +1,676 @@ ++/* ++ * Copyright (c) 2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/clk.h> ++#include <linux/delay.h> ++#include <linux/gpio.h> ++#include <linux/interrupt.h> ++#include <linux/io.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/of_gpio.h> ++#include <linux/pci.h> ++#include <linux/platform_device.h> ++#include <linux/phy/phy.h> ++#include <linux/regulator/consumer.h> ++#include <linux/reset.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++ ++#include "pcie-designware.h" ++ ++#define PCIE20_PARF_PHY_CTRL 0x40 ++#define PCIE20_PARF_PHY_REFCLK 0x4C ++#define PCIE20_PARF_DBI_BASE_ADDR 0x168 ++#define PCIE20_PARF_SLV_ADDR_SPACE_SIZE 0x16c ++#define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT 0x178 ++ ++#define PCIE20_ELBI_SYS_CTRL 0x04 ++#define PCIE20_ELBI_SYS_STTS 0x08 ++#define XMLH_LINK_UP BIT(10) ++ ++#define PCIE20_CAP 0x70 ++#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) ++ ++#define PERST_DELAY_MIN_US 1000 ++#define PERST_DELAY_MAX_US 1005 ++ ++#define LINKUP_DELAY_MIN_US 5000 ++#define LINKUP_DELAY_MAX_US 5100 ++#define LINKUP_RETRIES_COUNT 20 ++ ++#define PCIE_V0 0 /* apq8064 */ ++#define PCIE_V1 1 /* apq8084 */ ++ ++struct qcom_pcie_resources_v0 { ++ struct clk *iface_clk; ++ struct clk *core_clk; ++ struct clk *phy_clk; ++ struct reset_control *pci_reset; ++ struct reset_control *axi_reset; ++ struct reset_control *ahb_reset; ++ struct reset_control *por_reset; ++ struct reset_control *phy_reset; ++ struct regulator *vdda; ++ struct regulator *vdda_phy; ++ struct regulator *vdda_refclk; ++}; ++ ++struct qcom_pcie_resources_v1 { ++ struct clk *iface; ++ struct clk *aux; ++ struct clk *master_bus; ++ struct clk *slave_bus; ++ struct reset_control *core; ++ struct regulator *vdda; ++}; ++ ++union pcie_resources { ++ struct qcom_pcie_resources_v0 v0; ++ struct qcom_pcie_resources_v1 v1; ++}; ++ ++struct qcom_pcie { ++ struct pcie_port pp; ++ struct device *dev; ++ union pcie_resources res; ++ void __iomem *parf; ++ void __iomem *dbi; ++ void __iomem *elbi; ++ struct phy *phy; ++ struct gpio_desc *reset; ++ unsigned int version; ++}; ++ ++#define to_qcom_pcie(x) container_of(x, struct qcom_pcie, pp) ++ ++static inline void ++writel_masked(void __iomem *addr, u32 clear_mask, u32 set_mask) ++{ ++ u32 val = readl(addr); ++ ++ val &= ~clear_mask; ++ val |= set_mask; ++ writel(val, addr); ++} ++ ++static void qcom_ep_reset_assert_deassert(struct qcom_pcie *pcie, int assert) ++{ ++ int val, active_low; ++ ++ if (IS_ERR_OR_NULL(pcie->reset)) ++ return; ++ ++ active_low = gpiod_is_active_low(pcie->reset); ++ ++ if (assert) ++ val = !!active_low; ++ else ++ val = !active_low; ++ ++ gpiod_set_value(pcie->reset, val); ++ ++ usleep_range(PERST_DELAY_MIN_US, PERST_DELAY_MAX_US); ++} ++ ++static void qcom_ep_reset_assert(struct qcom_pcie *pcie) ++{ ++ qcom_ep_reset_assert_deassert(pcie, 1); ++} ++ ++static void qcom_ep_reset_deassert(struct qcom_pcie *pcie) ++{ ++ qcom_ep_reset_assert_deassert(pcie, 0); ++} ++ ++static irqreturn_t qcom_pcie_msi_irq_handler(int irq, void *arg) ++{ ++ struct pcie_port *pp = arg; ++ ++ return dw_handle_msi_irq(pp); ++} ++ ++static int qcom_pcie_link_up(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ u32 val = readl(pcie->dbi + PCIE20_CAP_LINKCTRLSTATUS); ++ ++ return val & BIT(29) ? 1 : 0; ++} ++ ++static void qcom_pcie_disable_resources_v0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ ++ reset_control_assert(res->pci_reset); ++ reset_control_assert(res->axi_reset); ++ reset_control_assert(res->ahb_reset); ++ reset_control_assert(res->por_reset); ++ reset_control_assert(res->pci_reset); ++ clk_disable_unprepare(res->iface_clk); ++ clk_disable_unprepare(res->core_clk); ++ clk_disable_unprepare(res->phy_clk); ++ regulator_disable(res->vdda); ++ regulator_disable(res->vdda_phy); ++ regulator_disable(res->vdda_refclk); ++} ++ ++static void qcom_pcie_disable_resources_v1(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v1 *res = &pcie->res.v1; ++ ++ reset_control_assert(res->core); ++ clk_disable_unprepare(res->slave_bus); ++ clk_disable_unprepare(res->master_bus); ++ clk_disable_unprepare(res->iface); ++ clk_disable_unprepare(res->aux); ++ regulator_disable(res->vdda); ++} ++ ++static int qcom_pcie_enable_resources_v0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ struct device *dev = pcie->dev; ++ int ret; ++ ++ ret = regulator_enable(res->vdda); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda regulator\n"); ++ return ret; ++ } ++ ++ ret = regulator_enable(res->vdda_refclk); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda_refclk regulator\n"); ++ goto err_refclk; ++ } ++ ++ ret = regulator_enable(res->vdda_phy); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda_phy regulator\n"); ++ goto err_vdda_phy; ++ } ++ ++ ret = clk_prepare_enable(res->iface_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable iface clock\n"); ++ goto err_iface; ++ } ++ ++ ret = clk_prepare_enable(res->core_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable core clock\n"); ++ goto err_clk_core; ++ } ++ ++ ret = clk_prepare_enable(res->phy_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable phy clock\n"); ++ goto err_clk_phy; ++ } ++ ++ ret = reset_control_deassert(res->ahb_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert ahb reset\n"); ++ goto err_reset_ahb; ++ } ++ ++ return 0; ++ ++err_reset_ahb: ++ clk_disable_unprepare(res->phy_clk); ++err_clk_phy: ++ clk_disable_unprepare(res->core_clk); ++err_clk_core: ++ clk_disable_unprepare(res->iface_clk); ++err_iface: ++ regulator_disable(res->vdda_phy); ++err_vdda_phy: ++ regulator_disable(res->vdda_refclk); ++err_refclk: ++ regulator_disable(res->vdda); ++ return ret; ++} ++ ++static int qcom_pcie_enable_resources_v1(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v1 *res = &pcie->res.v1; ++ struct device *dev = pcie->dev; ++ int ret; ++ ++ ret = reset_control_deassert(res->core); ++ if (ret) { ++ dev_err(dev, "cannot deassert core reset\n"); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(res->aux); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable aux clock\n"); ++ goto err_res; ++ } ++ ++ ret = clk_prepare_enable(res->iface); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable iface clock\n"); ++ goto err_aux; ++ } ++ ++ ret = clk_prepare_enable(res->master_bus); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable master_bus clock\n"); ++ goto err_iface; ++ } ++ ++ ret = clk_prepare_enable(res->slave_bus); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable slave_bus clock\n"); ++ goto err_master; ++ } ++ ++ ret = regulator_enable(res->vdda); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda regulator\n"); ++ goto err_slave; ++ } ++ ++ return 0; ++ ++err_slave: ++ clk_disable_unprepare(res->slave_bus); ++err_master: ++ clk_disable_unprepare(res->master_bus); ++err_iface: ++ clk_disable_unprepare(res->iface); ++err_aux: ++ clk_disable_unprepare(res->aux); ++err_res: ++ reset_control_assert(res->core); ++ ++ return ret; ++} ++ ++static int qcom_pcie_get_resources_v0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ struct device *dev = pcie->dev; ++ ++ res->vdda = devm_regulator_get(dev, "vdda"); ++ if (IS_ERR(res->vdda)) ++ return PTR_ERR(res->vdda); ++ ++ res->vdda_phy = devm_regulator_get(dev, "vdda_phy"); ++ if (IS_ERR(res->vdda_phy)) ++ return PTR_ERR(res->vdda_phy); ++ ++ res->vdda_refclk = devm_regulator_get(dev, "vdda_refclk"); ++ if (IS_ERR(res->vdda_refclk)) ++ return PTR_ERR(res->vdda_refclk); ++ ++ res->iface_clk = devm_clk_get(dev, "iface"); ++ if (IS_ERR(res->iface_clk)) ++ return PTR_ERR(res->iface_clk); ++ ++ res->core_clk = devm_clk_get(dev, "core"); ++ if (IS_ERR(res->core_clk)) ++ return PTR_ERR(res->core_clk); ++ ++ res->phy_clk = devm_clk_get(dev, "phy"); ++ if (IS_ERR(res->phy_clk)) ++ return PTR_ERR(res->phy_clk); ++ ++ res->pci_reset = devm_reset_control_get(dev, "pci"); ++ if (IS_ERR(res->pci_reset)) ++ return PTR_ERR(res->pci_reset); ++ ++ res->axi_reset = devm_reset_control_get(dev, "axi"); ++ if (IS_ERR(res->axi_reset)) ++ return PTR_ERR(res->axi_reset); ++ ++ res->ahb_reset = devm_reset_control_get(dev, "ahb"); ++ if (IS_ERR(res->ahb_reset)) ++ return PTR_ERR(res->ahb_reset); ++ ++ res->por_reset = devm_reset_control_get(dev, "por"); ++ if (IS_ERR(res->por_reset)) ++ return PTR_ERR(res->por_reset); ++ ++ res->phy_reset = devm_reset_control_get(dev, "phy"); ++ if (IS_ERR(res->phy_reset)) ++ return PTR_ERR(res->phy_reset); ++ ++ return 0; ++} ++ ++static int qcom_pcie_get_resources_v1(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v1 *res = &pcie->res.v1; ++ struct device *dev = pcie->dev; ++ ++ res->vdda = devm_regulator_get(dev, "vdda"); ++ if (IS_ERR(res->vdda)) ++ return PTR_ERR(res->vdda); ++ ++ res->iface = devm_clk_get(dev, "iface"); ++ if (IS_ERR(res->iface)) ++ return PTR_ERR(res->iface); ++ ++ res->aux = devm_clk_get(dev, "aux"); ++ if (IS_ERR(res->aux) && PTR_ERR(res->aux) == -EPROBE_DEFER) ++ return -EPROBE_DEFER; ++ else if (IS_ERR(res->aux)) ++ res->aux = NULL; ++ ++ res->master_bus = devm_clk_get(dev, "master_bus"); ++ if (IS_ERR(res->master_bus)) ++ return PTR_ERR(res->master_bus); ++ ++ res->slave_bus = devm_clk_get(dev, "slave_bus"); ++ if (IS_ERR(res->slave_bus)) ++ return PTR_ERR(res->slave_bus); ++ ++ res->core = devm_reset_control_get(dev, "core"); ++ if (IS_ERR(res->core)) ++ return PTR_ERR(res->core); ++ ++ return 0; ++} ++ ++static int qcom_pcie_enable_link_training(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ struct device *dev = pp->dev; ++ int retries; ++ u32 val; ++ ++ /* enable link training */ ++ writel_masked(pcie->elbi + PCIE20_ELBI_SYS_CTRL, 0, BIT(0)); ++ ++ /* wait for up to 100ms for the link to come up */ ++ retries = LINKUP_RETRIES_COUNT; ++ do { ++ val = readl(pcie->elbi + PCIE20_ELBI_SYS_STTS); ++ if (val & XMLH_LINK_UP) ++ break; ++ usleep_range(LINKUP_DELAY_MIN_US, LINKUP_DELAY_MAX_US); ++ } while (retries--); ++ ++ if (retries < 0 || !dw_pcie_link_up(pp)) { ++ dev_err(dev, "link initialization failed\n"); ++ return -ENXIO; ++ } ++ ++ return 0; ++} ++ ++static void qcom_pcie_host_init_v1(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ int ret; ++ ++ qcom_ep_reset_assert(pcie); ++ ++ ret = qcom_pcie_enable_resources_v1(pcie); ++ if (ret) ++ return; ++ ++ /* change DBI base address */ ++ writel(0, pcie->parf + PCIE20_PARF_DBI_BASE_ADDR); ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) ++ writel_masked(pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT, ++ 0, BIT(31)); ++ ++ ret = phy_init(pcie->phy); ++ if (ret) ++ goto err_res; ++ ++ ret = phy_power_on(pcie->phy); ++ if (ret) ++ goto err_phy; ++ ++ dw_pcie_setup_rc(pp); ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) ++ dw_pcie_msi_init(pp); ++ ++ qcom_ep_reset_deassert(pcie); ++ ++ ret = qcom_pcie_enable_link_training(pp); ++ if (ret) ++ goto err; ++ ++ return; ++ ++err: ++ qcom_ep_reset_assert(pcie); ++ phy_power_off(pcie->phy); ++err_phy: ++ phy_exit(pcie->phy); ++err_res: ++ qcom_pcie_disable_resources_v1(pcie); ++} ++ ++static void qcom_pcie_host_init_v0(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ struct device *dev = pcie->dev; ++ int ret; ++ ++ qcom_ep_reset_assert(pcie); ++ ++ ret = qcom_pcie_enable_resources_v0(pcie); ++ if (ret) ++ return; ++ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, BIT(0), 0); ++ ++ /* enable external reference clock */ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, 0, BIT(16)); ++ ++ ret = reset_control_deassert(res->phy_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert phy reset\n"); ++ return; ++ } ++ ++ ret = reset_control_deassert(res->pci_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert pci reset\n"); ++ return; ++ } ++ ++ ret = reset_control_deassert(res->por_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert por reset\n"); ++ return; ++ } ++ ++ ret = reset_control_deassert(res->axi_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert axi reset\n"); ++ return; ++ } ++ ++ /* wait 150ms for clock acquisition */ ++ usleep_range(10000, 15000); ++ ++ dw_pcie_setup_rc(pp); ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) ++ dw_pcie_msi_init(pp); ++ ++ qcom_ep_reset_deassert(pcie); ++ ++ ret = qcom_pcie_enable_link_training(pp); ++ if (ret) ++ goto err; ++ ++ return; ++err: ++ qcom_ep_reset_assert(pcie); ++ qcom_pcie_disable_resources_v0(pcie); ++} ++ ++static void qcom_pcie_host_init(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ ++ if (pcie->version == PCIE_V0) ++ return qcom_pcie_host_init_v0(pp); ++ else ++ return qcom_pcie_host_init_v1(pp); ++} ++ ++static int ++qcom_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, u32 *val) ++{ ++ /* the device class is not reported correctly from the register */ ++ if (where == PCI_CLASS_REVISION && size == 4) { ++ *val = readl(pp->dbi_base + PCI_CLASS_REVISION); ++ *val &= ~(0xffff << 16); ++ *val |= PCI_CLASS_BRIDGE_PCI << 16; ++ return PCIBIOS_SUCCESSFUL; ++ } ++ ++ return dw_pcie_cfg_read(pp->dbi_base + where, size, val); ++} ++ ++static struct pcie_host_ops qcom_pcie_ops = { ++ .link_up = qcom_pcie_link_up, ++ .host_init = qcom_pcie_host_init, ++ .rd_own_conf = qcom_pcie_rd_own_conf, ++}; ++ ++static const struct of_device_id qcom_pcie_match[] = { ++ { .compatible = "qcom,pcie-v0", .data = (void *)PCIE_V0 }, ++ { .compatible = "qcom,pcie-v1", .data = (void *)PCIE_V1 }, ++ { } ++}; ++ ++static int qcom_pcie_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct of_device_id *match; ++ struct resource *res; ++ struct qcom_pcie *pcie; ++ struct pcie_port *pp; ++ int ret; ++ ++ match = of_match_node(qcom_pcie_match, dev->of_node); ++ if (!match) ++ return -ENXIO; ++ ++ pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); ++ if (!pcie) ++ return -ENOMEM; ++ ++ pcie->version = (unsigned int)match->data; ++ ++ pcie->reset = devm_gpiod_get_optional(dev, "perst", GPIOD_OUT_LOW); ++ if (IS_ERR(pcie->reset) && PTR_ERR(pcie->reset) == -EPROBE_DEFER) ++ return PTR_ERR(pcie->reset); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "parf"); ++ pcie->parf = devm_ioremap_resource(dev, res); ++ if (IS_ERR(pcie->parf)) ++ return PTR_ERR(pcie->parf); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi"); ++ pcie->dbi = devm_ioremap_resource(dev, res); ++ if (IS_ERR(pcie->dbi)) ++ return PTR_ERR(pcie->dbi); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi"); ++ pcie->elbi = devm_ioremap_resource(dev, res); ++ if (IS_ERR(pcie->elbi)) ++ return PTR_ERR(pcie->elbi); ++ ++ pcie->phy = devm_phy_optional_get(dev, "pciephy"); ++ if (IS_ERR(pcie->phy)) ++ return PTR_ERR(pcie->phy); ++ ++ pcie->dev = dev; ++ ++ if (pcie->version == PCIE_V0) ++ ret = qcom_pcie_get_resources_v0(pcie); ++ else ++ ret = qcom_pcie_get_resources_v1(pcie); ++ ++ if (ret) ++ return ret; ++ ++ pp = &pcie->pp; ++ pp->dev = dev; ++ pp->dbi_base = pcie->dbi; ++ pp->root_bus_nr = -1; ++ pp->ops = &qcom_pcie_ops; ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) { ++ pp->msi_irq = platform_get_irq_byname(pdev, "msi"); ++ if (pp->msi_irq < 0) { ++ dev_err(dev, "cannot get msi irq\n"); ++ return pp->msi_irq; ++ } ++ ++ ret = devm_request_irq(dev, pp->msi_irq, ++ qcom_pcie_msi_irq_handler, ++ IRQF_SHARED, "qcom-pcie-msi", pp); ++ if (ret) { ++ dev_err(dev, "cannot request msi irq\n"); ++ return ret; ++ } ++ } ++ ++ ret = dw_pcie_host_init(pp); ++ if (ret) { ++ dev_err(dev, "cannot initialize host\n"); ++ return ret; ++ } ++ ++ platform_set_drvdata(pdev, pcie); ++ ++ return 0; ++} ++ ++static int qcom_pcie_remove(struct platform_device *pdev) ++{ ++ struct qcom_pcie *pcie = platform_get_drvdata(pdev); ++ ++ qcom_ep_reset_assert(pcie); ++ phy_power_off(pcie->phy); ++ phy_exit(pcie->phy); ++ if (pcie->version == PCIE_V0) ++ qcom_pcie_disable_resources_v0(pcie); ++ else ++ qcom_pcie_disable_resources_v1(pcie); ++ ++ return 0; ++} ++ ++static struct platform_driver qcom_pcie_driver = { ++ .probe = qcom_pcie_probe, ++ .remove = qcom_pcie_remove, ++ .driver = { ++ .name = "qcom-pcie", ++ .of_match_table = qcom_pcie_match, ++ }, ++}; ++ ++module_platform_driver(qcom_pcie_driver); ++ ++MODULE_AUTHOR("Stanimir Varbanov <svarbanov@mm-sol.com>"); ++MODULE_DESCRIPTION("Qualcomm PCIe root complex driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:qcom-pcie"); +--- a/drivers/pci/host/Makefile ++++ b/drivers/pci/host/Makefile +@@ -20,3 +20,4 @@ + obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o + obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o + obj-$(CONFIG_PCI_HISI) += pcie-hisi.o ++obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o diff --git a/target/linux/ipq806x/patches-4.4/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch b/target/linux/ipq806x/patches-4.4/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch new file mode 100644 index 0000000..1246bfe --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch @@ -0,0 +1,244 @@ +From 5b40516b2f5fb9b2a7d6d3e2e924f12ec9d183a8 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Tue, 21 Apr 2015 19:01:42 -0700 +Subject: [PATCH 8/9] ARM: dts: qcom: add pcie nodes to ipq806x platforms + +qcom-pcie driver now supports version 0 of the controller. This change +adds the corresponding entries to the IPQ806x dtsi file and +corresponding platform (AP148). + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 30 ++++++++ + arch/arm/boot/dts/qcom-ipq8064.dtsi | 124 +++++++++++++++++++++++++++++++ + 2 files changed, 154 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -116,5 +116,15 @@ + usb30@1 { + status = "ok"; + }; ++ ++ pcie0: pci@1b500000 { ++ status = "ok"; ++ phy-tx0-term-offset = <7>; ++ }; ++ ++ pcie1: pci@1b700000 { ++ status = "ok"; ++ phy-tx0-term-offset = <7>; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -128,5 +128,17 @@ + usb30@1 { + status = "ok"; + }; ++ ++ pcie0: pci@1b500000 { ++ status = "ok"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ status = "ok"; ++ }; ++ ++ pcie2: pci@1b900000 { ++ status = "ok"; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -4,6 +4,9 @@ + #include <dt-bindings/clock/qcom,gcc-ipq806x.h> + #include <dt-bindings/clock/qcom,lcc-ipq806x.h> + #include <dt-bindings/soc/qcom,gsbi.h> ++#include <dt-bindings/reset/qcom,gcc-ipq806x.h> ++#include <dt-bindings/interrupt-controller/arm-gic.h> ++#include <dt-bindings/gpio/gpio.h> + + / { + model = "Qualcomm IPQ8064"; +@@ -99,6 +102,33 @@ + interrupt-controller; + #interrupt-cells = <2>; + interrupts = <0 16 0x4>; ++ ++ pcie0_pins: pcie0_pinmux { ++ mux { ++ pins = "gpio3"; ++ function = "pcie1_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ pcie1_pins: pcie1_pinmux { ++ mux { ++ pins = "gpio48"; ++ function = "pcie2_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ pcie2_pins: pcie2_pinmux { ++ mux { ++ pins = "gpio63"; ++ function = "pcie3_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; + }; + + intc: interrupt-controller@2000000 { +@@ -417,6 +447,144 @@ + dr_mode = "host"; + }; + }; ++ ++ pcie0: pci@1b500000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b500000 0x1000 ++ 0x1b502000 0x80 ++ 0x1b600000 0x100 ++ 0x0ff00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <0>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ++ ranges = <0x81000000 0 0x0fe00000 0x0fe00000 0 0x00100000 /* downstream I/O */ ++ 0x82000000 0 0x08000000 0x08000000 0 0x07e00000>; /* non-prefetchable memory */ ++ ++ interrupts = <GIC_SPI 35 IRQ_TYPE_NONE>; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ ++ clocks = <&gcc PCIE_A_CLK>, ++ <&gcc PCIE_H_CLK>, ++ <&gcc PCIE_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ ++ resets = <&gcc PCIE_ACLK_RESET>, ++ <&gcc PCIE_HCLK_RESET>, ++ <&gcc PCIE_POR_RESET>, ++ <&gcc PCIE_PCI_RESET>, ++ <&gcc PCIE_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ ++ pinctrl-0 = <&pcie0_pins>; ++ pinctrl-names = "default"; ++ ++ perst-gpio = <&qcom_pinmux 3 GPIO_ACTIVE_LOW>; ++ ++ status = "disabled"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b700000 0x1000 ++ 0x1b702000 0x80 ++ 0x1b800000 0x100 ++ 0x31f00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <1>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ++ ranges = <0x81000000 0 0x31e00000 0x31e00000 0 0x00100000 /* downstream I/O */ ++ 0x82000000 0 0x2e000000 0x2e000000 0 0x03e00000>; /* non-prefetchable memory */ ++ ++ interrupts = <GIC_SPI 57 IRQ_TYPE_NONE>; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 58 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 59 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 60 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 61 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ ++ clocks = <&gcc PCIE_1_A_CLK>, ++ <&gcc PCIE_1_H_CLK>, ++ <&gcc PCIE_1_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ ++ resets = <&gcc PCIE_1_ACLK_RESET>, ++ <&gcc PCIE_1_HCLK_RESET>, ++ <&gcc PCIE_1_POR_RESET>, ++ <&gcc PCIE_1_PCI_RESET>, ++ <&gcc PCIE_1_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ ++ pinctrl-0 = <&pcie1_pins>; ++ pinctrl-names = "default"; ++ ++ perst-gpio = <&qcom_pinmux 48 GPIO_ACTIVE_LOW>; ++ ++ status = "disabled"; ++ }; ++ ++ pcie2: pci@1b900000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b900000 0x1000 ++ 0x1b902000 0x80 ++ 0x1ba00000 0x100 ++ 0x35f00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <2>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ++ ranges = <0x81000000 0 0x35e00000 0x35e00000 0 0x00100000 /* downstream I/O */ ++ 0x82000000 0 0x32000000 0x32000000 0 0x03e00000>; /* non-prefetchable memory */ ++ ++ interrupts = <GIC_SPI 71 IRQ_TYPE_NONE>; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 72 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 73 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 74 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 75 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ ++ clocks = <&gcc PCIE_2_A_CLK>, ++ <&gcc PCIE_2_H_CLK>, ++ <&gcc PCIE_2_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ ++ resets = <&gcc PCIE_2_ACLK_RESET>, ++ <&gcc PCIE_2_HCLK_RESET>, ++ <&gcc PCIE_2_POR_RESET>, ++ <&gcc PCIE_2_PCI_RESET>, ++ <&gcc PCIE_2_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ ++ pinctrl-0 = <&pcie2_pins>; ++ pinctrl-names = "default"; ++ ++ perst-gpio = <&qcom_pinmux 63 GPIO_ACTIVE_LOW>; ++ ++ status = "disabled"; ++ }; + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-4.4/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch b/target/linux/ipq806x/patches-4.4/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch new file mode 100644 index 0000000..e2d3135 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch @@ -0,0 +1,29 @@ +From f004aa1dec6e2e206be025de15b115d60f2b21e3 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Tue, 21 Apr 2015 19:09:07 -0700 +Subject: [PATCH 9/9] ARM: qcom: automatically select PCI_DOMAINS if PCI is + enabled + +If multiple PCIe devices are present in the system, the kernel will +panic at boot time when trying to scan the PCI buses. This happens on +IPQ806x based platforms, which has 3 PCIe ports. + +Enabling this option allows the kernel to assign the pci-domains +according to the device-tree content. This allows multiple PCIe +controllers to coexist in the system. + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + arch/arm/mach-qcom/Kconfig | 1 + + 1 file changed, 1 insertion(+) + +--- a/arch/arm/mach-qcom/Kconfig ++++ b/arch/arm/mach-qcom/Kconfig +@@ -5,6 +5,7 @@ menuconfig ARCH_QCOM + select ARM_AMBA + select PINCTRL + select QCOM_SCM if SMP ++ select PCI_DOMAINS if PCI + help + Support for Qualcomm's devicetree based systems. + diff --git a/target/linux/ipq806x/patches-4.4/114-pcie-add-ctlr-init.patch b/target/linux/ipq806x/patches-4.4/114-pcie-add-ctlr-init.patch new file mode 100644 index 0000000..6328113 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/114-pcie-add-ctlr-init.patch @@ -0,0 +1,311 @@ +--- a/drivers/pci/host/pcie-qcom.c ++++ b/drivers/pci/host/pcie-qcom.c +@@ -29,8 +29,53 @@ + + #include "pcie-designware.h" + ++/* DBI registers */ ++#define PCIE20_CAP 0x70 ++#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) ++ ++#define PCIE20_AXI_MSTR_RESP_COMP_CTRL0 0x818 ++#define PCIE20_AXI_MSTR_RESP_COMP_CTRL1 0x81c ++ ++#define PCIE20_PLR_IATU_VIEWPORT 0x900 ++#define PCIE20_PLR_IATU_REGION_OUTBOUND (0x0 << 31) ++#define PCIE20_PLR_IATU_REGION_INDEX(x) (x << 0) ++ ++#define PCIE20_PLR_IATU_CTRL1 0x904 ++#define PCIE20_PLR_IATU_TYPE_CFG0 (0x4 << 0) ++#define PCIE20_PLR_IATU_TYPE_MEM (0x0 << 0) ++ ++#define PCIE20_PLR_IATU_CTRL2 0x908 ++#define PCIE20_PLR_IATU_ENABLE BIT(31) ++ ++#define PCIE20_PLR_IATU_LBAR 0x90C ++#define PCIE20_PLR_IATU_UBAR 0x910 ++#define PCIE20_PLR_IATU_LAR 0x914 ++#define PCIE20_PLR_IATU_LTAR 0x918 ++#define PCIE20_PLR_IATU_UTAR 0x91c ++ ++#define MSM_PCIE_DEV_CFG_ADDR 0x01000000 ++ ++/* PARF registers */ ++#define PCIE20_PARF_PCS_DEEMPH 0x34 ++#define PCS_DEEMPH_TX_DEEMPH_GEN1(x) (x << 16) ++#define PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(x) (x << 8) ++#define PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(x) (x << 0) ++ ++#define PCIE20_PARF_PCS_SWING 0x38 ++#define PCS_SWING_TX_SWING_FULL(x) (x << 8) ++#define PCS_SWING_TX_SWING_LOW(x) (x << 0) ++ + #define PCIE20_PARF_PHY_CTRL 0x40 ++#define PHY_CTRL_PHY_TX0_TERM_OFFSET_MASK (0x1f << 16) ++#define PHY_CTRL_PHY_TX0_TERM_OFFSET(x) (x << 16) ++ + #define PCIE20_PARF_PHY_REFCLK 0x4C ++#define REF_SSP_EN BIT(16) ++#define REF_USE_PAD BIT(12) ++ ++#define PCIE20_PARF_CONFIG_BITS 0x50 ++#define PHY_RX0_EQ(x) (x << 24) ++ + #define PCIE20_PARF_DBI_BASE_ADDR 0x168 + #define PCIE20_PARF_SLV_ADDR_SPACE_SIZE 0x16c + #define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT 0x178 +@@ -39,9 +84,6 @@ + #define PCIE20_ELBI_SYS_STTS 0x08 + #define XMLH_LINK_UP BIT(10) + +-#define PCIE20_CAP 0x70 +-#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) +- + #define PERST_DELAY_MIN_US 1000 + #define PERST_DELAY_MAX_US 1005 + +@@ -56,14 +98,18 @@ struct qcom_pcie_resources_v0 { + struct clk *iface_clk; + struct clk *core_clk; + struct clk *phy_clk; ++ struct clk *aux_clk; ++ struct clk *ref_clk; + struct reset_control *pci_reset; + struct reset_control *axi_reset; + struct reset_control *ahb_reset; + struct reset_control *por_reset; + struct reset_control *phy_reset; ++ struct reset_control *ext_reset; + struct regulator *vdda; + struct regulator *vdda_phy; + struct regulator *vdda_refclk; ++ uint8_t phy_tx0_term_offset; + }; + + struct qcom_pcie_resources_v1 { +@@ -106,20 +152,10 @@ writel_masked(void __iomem *addr, u32 cl + + static void qcom_ep_reset_assert_deassert(struct qcom_pcie *pcie, int assert) + { +- int val, active_low; +- + if (IS_ERR_OR_NULL(pcie->reset)) + return; + +- active_low = gpiod_is_active_low(pcie->reset); +- +- if (assert) +- val = !!active_low; +- else +- val = !active_low; +- +- gpiod_set_value(pcie->reset, val); +- ++ gpiod_set_value(pcie->reset, assert); + usleep_range(PERST_DELAY_MIN_US, PERST_DELAY_MAX_US); + } + +@@ -156,10 +192,13 @@ static void qcom_pcie_disable_resources_ + reset_control_assert(res->axi_reset); + reset_control_assert(res->ahb_reset); + reset_control_assert(res->por_reset); +- reset_control_assert(res->pci_reset); ++ reset_control_assert(res->phy_reset); ++ reset_control_assert(res->ext_reset); + clk_disable_unprepare(res->iface_clk); + clk_disable_unprepare(res->core_clk); + clk_disable_unprepare(res->phy_clk); ++ clk_disable_unprepare(res->aux_clk); ++ clk_disable_unprepare(res->ref_clk); + regulator_disable(res->vdda); + regulator_disable(res->vdda_phy); + regulator_disable(res->vdda_refclk); +@@ -201,6 +240,12 @@ static int qcom_pcie_enable_resources_v0 + goto err_vdda_phy; + } + ++ ret = reset_control_deassert(res->ext_reset); ++ if (ret) { ++ dev_err(dev, "cannot assert ext reset\n"); ++ goto err_reset_ext; ++ } ++ + ret = clk_prepare_enable(res->iface_clk); + if (ret) { + dev_err(dev, "cannot prepare/enable iface clock\n"); +@@ -219,21 +264,40 @@ static int qcom_pcie_enable_resources_v0 + goto err_clk_phy; + } + ++ ret = clk_prepare_enable(res->aux_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable aux clock\n"); ++ goto err_clk_aux; ++ } ++ ++ ret = clk_prepare_enable(res->ref_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable ref clock\n"); ++ goto err_clk_ref; ++ } ++ + ret = reset_control_deassert(res->ahb_reset); + if (ret) { + dev_err(dev, "cannot deassert ahb reset\n"); + goto err_reset_ahb; + } ++ udelay(1); + + return 0; + + err_reset_ahb: ++ clk_disable_unprepare(res->ref_clk); ++err_clk_ref: ++ clk_disable_unprepare(res->aux_clk); ++err_clk_aux: + clk_disable_unprepare(res->phy_clk); + err_clk_phy: + clk_disable_unprepare(res->core_clk); + err_clk_core: + clk_disable_unprepare(res->iface_clk); + err_iface: ++ reset_control_assert(res->ext_reset); ++err_reset_ext: + regulator_disable(res->vdda_phy); + err_vdda_phy: + regulator_disable(res->vdda_refclk); +@@ -329,6 +393,14 @@ static int qcom_pcie_get_resources_v0(st + if (IS_ERR(res->phy_clk)) + return PTR_ERR(res->phy_clk); + ++ res->aux_clk = devm_clk_get(dev, "aux"); ++ if (IS_ERR(res->aux_clk)) ++ return PTR_ERR(res->aux_clk); ++ ++ res->ref_clk = devm_clk_get(dev, "ref"); ++ if (IS_ERR(res->ref_clk)) ++ return PTR_ERR(res->ref_clk); ++ + res->pci_reset = devm_reset_control_get(dev, "pci"); + if (IS_ERR(res->pci_reset)) + return PTR_ERR(res->pci_reset); +@@ -349,6 +421,14 @@ static int qcom_pcie_get_resources_v0(st + if (IS_ERR(res->phy_reset)) + return PTR_ERR(res->phy_reset); + ++ res->ext_reset = devm_reset_control_get(dev, "ext"); ++ if (IS_ERR(res->ext_reset)) ++ return PTR_ERR(res->ext_reset); ++ ++ if (of_property_read_u8(dev->of_node, "phy-tx0-term-offset", ++ &res->phy_tx0_term_offset)) ++ res->phy_tx0_term_offset = 0; ++ + return 0; + } + +@@ -461,6 +541,57 @@ err_res: + qcom_pcie_disable_resources_v1(pcie); + } + ++static void qcom_pcie_prog_viewport_cfg0(struct qcom_pcie *pcie, u32 busdev) ++{ ++ struct pcie_port *pp = &pcie->pp; ++ ++ /* ++ * program and enable address translation region 0 (device config ++ * address space); region type config; ++ * axi config address range to device config address range ++ */ ++ writel(PCIE20_PLR_IATU_REGION_OUTBOUND | ++ PCIE20_PLR_IATU_REGION_INDEX(0), ++ pcie->dbi + PCIE20_PLR_IATU_VIEWPORT); ++ ++ writel(PCIE20_PLR_IATU_TYPE_CFG0, pcie->dbi + PCIE20_PLR_IATU_CTRL1); ++ writel(PCIE20_PLR_IATU_ENABLE, pcie->dbi + PCIE20_PLR_IATU_CTRL2); ++ writel(pp->cfg0_base, pcie->dbi + PCIE20_PLR_IATU_LBAR); ++ writel((pp->cfg0_base >> 32), pcie->dbi + PCIE20_PLR_IATU_UBAR); ++ writel((pp->cfg0_base + pp->cfg0_size - 1), ++ pcie->dbi + PCIE20_PLR_IATU_LAR); ++ writel(busdev, pcie->dbi + PCIE20_PLR_IATU_LTAR); ++ writel(0, pcie->dbi + PCIE20_PLR_IATU_UTAR); ++} ++ ++static void qcom_pcie_prog_viewport_mem2_outbound(struct qcom_pcie *pcie) ++{ ++ struct pcie_port *pp = &pcie->pp; ++ ++ /* ++ * program and enable address translation region 2 (device resource ++ * address space); region type memory; ++ * axi device bar address range to device bar address range ++ */ ++ writel(PCIE20_PLR_IATU_REGION_OUTBOUND | ++ PCIE20_PLR_IATU_REGION_INDEX(2), ++ pcie->dbi + PCIE20_PLR_IATU_VIEWPORT); ++ ++ writel(PCIE20_PLR_IATU_TYPE_MEM, pcie->dbi + PCIE20_PLR_IATU_CTRL1); ++ writel(PCIE20_PLR_IATU_ENABLE, pcie->dbi + PCIE20_PLR_IATU_CTRL2); ++ writel(pp->mem_base, pcie->dbi + PCIE20_PLR_IATU_LBAR); ++ writel((pp->mem_base >> 32), pcie->dbi + PCIE20_PLR_IATU_UBAR); ++ writel(pp->mem_base + pp->mem_size - 1, ++ pcie->dbi + PCIE20_PLR_IATU_LAR); ++ writel(pp->mem_bus_addr, pcie->dbi + PCIE20_PLR_IATU_LTAR); ++ writel(upper_32_bits(pp->mem_bus_addr), ++ pcie->dbi + PCIE20_PLR_IATU_UTAR); ++ ++ /* 256B PCIE buffer setting */ ++ writel(0x1, pcie->dbi + PCIE20_AXI_MSTR_RESP_COMP_CTRL0); ++ writel(0x1, pcie->dbi + PCIE20_AXI_MSTR_RESP_COMP_CTRL1); ++} ++ + static void qcom_pcie_host_init_v0(struct pcie_port *pp) + { + struct qcom_pcie *pcie = to_qcom_pcie(pp); +@@ -470,15 +601,34 @@ static void qcom_pcie_host_init_v0(struc + + qcom_ep_reset_assert(pcie); + ++ reset_control_assert(res->ahb_reset); ++ + ret = qcom_pcie_enable_resources_v0(pcie); + if (ret) + return; + + writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, BIT(0), 0); + +- /* enable external reference clock */ +- writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, 0, BIT(16)); ++ /* Set Tx termination offset */ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, ++ PHY_CTRL_PHY_TX0_TERM_OFFSET_MASK, ++ PHY_CTRL_PHY_TX0_TERM_OFFSET(res->phy_tx0_term_offset)); ++ ++ /* PARF programming */ ++ writel(PCS_DEEMPH_TX_DEEMPH_GEN1(0x18) | ++ PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(0x18) | ++ PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(0x22), ++ pcie->parf + PCIE20_PARF_PCS_DEEMPH); ++ writel(PCS_SWING_TX_SWING_FULL(0x78) | ++ PCS_SWING_TX_SWING_LOW(0x78), ++ pcie->parf + PCIE20_PARF_PCS_SWING); ++ writel(PHY_RX0_EQ(0x4), pcie->parf + PCIE20_PARF_CONFIG_BITS); ++ ++ /* Enable reference clock */ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, ++ REF_USE_PAD, REF_SSP_EN); + ++ /* De-assert PHY, PCIe, POR and AXI resets */ + ret = reset_control_deassert(res->phy_reset); + if (ret) { + dev_err(dev, "cannot deassert phy reset\n"); +@@ -517,6 +667,9 @@ static void qcom_pcie_host_init_v0(struc + if (ret) + goto err; + ++ qcom_pcie_prog_viewport_cfg0(pcie, MSM_PCIE_DEV_CFG_ADDR); ++ qcom_pcie_prog_viewport_mem2_outbound(pcie); ++ + return; + err: + qcom_ep_reset_assert(pcie); diff --git a/target/linux/ipq806x/patches-4.4/115-add-pcie-aux-clk-dts.patch b/target/linux/ipq806x/patches-4.4/115-add-pcie-aux-clk-dts.patch new file mode 100644 index 0000000..8ceace9 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/115-add-pcie-aux-clk-dts.patch @@ -0,0 +1,80 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -476,15 +476,21 @@ + + clocks = <&gcc PCIE_A_CLK>, + <&gcc PCIE_H_CLK>, +- <&gcc PCIE_PHY_CLK>; +- clock-names = "core", "iface", "phy"; ++ <&gcc PCIE_PHY_CLK>, ++ <&gcc PCIE_AUX_CLK>, ++ <&gcc PCIE_ALT_REF_CLK>; ++ clock-names = "core", "iface", "phy", "aux", "ref"; ++ ++ assigned-clocks = <&gcc PCIE_ALT_REF_CLK>; ++ assigned-clock-rates = <100000000>; + + resets = <&gcc PCIE_ACLK_RESET>, + <&gcc PCIE_HCLK_RESET>, + <&gcc PCIE_POR_RESET>, + <&gcc PCIE_PCI_RESET>, +- <&gcc PCIE_PHY_RESET>; +- reset-names = "axi", "ahb", "por", "pci", "phy"; ++ <&gcc PCIE_PHY_RESET>, ++ <&gcc PCIE_EXT_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy", "ext"; + + pinctrl-0 = <&pcie0_pins>; + pinctrl-names = "default"; +@@ -522,15 +528,21 @@ + + clocks = <&gcc PCIE_1_A_CLK>, + <&gcc PCIE_1_H_CLK>, +- <&gcc PCIE_1_PHY_CLK>; +- clock-names = "core", "iface", "phy"; ++ <&gcc PCIE_1_PHY_CLK>, ++ <&gcc PCIE_1_AUX_CLK>, ++ <&gcc PCIE_1_ALT_REF_CLK>; ++ clock-names = "core", "iface", "phy", "aux", "ref"; ++ ++ assigned-clocks = <&gcc PCIE_1_ALT_REF_CLK>; ++ assigned-clock-rates = <100000000>; + + resets = <&gcc PCIE_1_ACLK_RESET>, + <&gcc PCIE_1_HCLK_RESET>, + <&gcc PCIE_1_POR_RESET>, + <&gcc PCIE_1_PCI_RESET>, +- <&gcc PCIE_1_PHY_RESET>; +- reset-names = "axi", "ahb", "por", "pci", "phy"; ++ <&gcc PCIE_1_PHY_RESET>, ++ <&gcc PCIE_1_EXT_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy", "ext"; + + pinctrl-0 = <&pcie1_pins>; + pinctrl-names = "default"; +@@ -568,15 +580,21 @@ + + clocks = <&gcc PCIE_2_A_CLK>, + <&gcc PCIE_2_H_CLK>, +- <&gcc PCIE_2_PHY_CLK>; +- clock-names = "core", "iface", "phy"; ++ <&gcc PCIE_2_PHY_CLK>, ++ <&gcc PCIE_2_AUX_CLK>, ++ <&gcc PCIE_2_ALT_REF_CLK>; ++ clock-names = "core", "iface", "phy", "aux", "ref"; ++ ++ assigned-clocks = <&gcc PCIE_2_ALT_REF_CLK>; ++ assigned-clock-rates = <100000000>; + + resets = <&gcc PCIE_2_ACLK_RESET>, + <&gcc PCIE_2_HCLK_RESET>, + <&gcc PCIE_2_POR_RESET>, + <&gcc PCIE_2_PCI_RESET>, +- <&gcc PCIE_2_PHY_RESET>; +- reset-names = "axi", "ahb", "por", "pci", "phy"; ++ <&gcc PCIE_2_PHY_RESET>, ++ <&gcc PCIE_2_EXT_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy", "ext"; + + pinctrl-0 = <&pcie2_pins>; + pinctrl-names = "default"; diff --git a/target/linux/ipq806x/patches-4.4/126-add-rpm-to-ipq8064-dts.patch b/target/linux/ipq806x/patches-4.4/126-add-rpm-to-ipq8064-dts.patch new file mode 100644 index 0000000..7daa931 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/126-add-rpm-to-ipq8064-dts.patch @@ -0,0 +1,87 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -2,6 +2,7 @@ + + #include "skeleton.dtsi" + #include <dt-bindings/clock/qcom,gcc-ipq806x.h> ++#include <dt-bindings/mfd/qcom-rpm.h> + #include <dt-bindings/clock/qcom,lcc-ipq806x.h> + #include <dt-bindings/soc/qcom,gsbi.h> + #include <dt-bindings/reset/qcom,gcc-ipq806x.h> +@@ -93,6 +94,63 @@ + reg-names = "lpass-lpaif"; + }; + ++ rpm@108000 { ++ compatible = "qcom,rpm-ipq8064"; ++ reg = <0x108000 0x1000>; ++ qcom,ipc = <&l2cc 0x8 2>; ++ ++ interrupts = <0 19 0>, ++ <0 21 0>, ++ <0 22 0>; ++ interrupt-names = "ack", ++ "err", ++ "wakeup"; ++ ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ smb208_s1a: smb208-s1a { ++ compatible = "qcom,rpm-smb208"; ++ reg = <QCOM_RPM_SMB208_S1a>; ++ ++ regulator-min-microvolt = <1050000>; ++ regulator-max-microvolt = <1150000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ ++ }; ++ ++ smb208_s1b: smb208-s1b { ++ compatible = "qcom,rpm-smb208"; ++ reg = <QCOM_RPM_SMB208_S1b>; ++ ++ regulator-min-microvolt = <1050000>; ++ regulator-max-microvolt = <1150000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ }; ++ ++ smb208_s2a: smb208-s2a { ++ compatible = "qcom,rpm-smb208"; ++ reg = <QCOM_RPM_SMB208_S2a>; ++ ++ regulator-min-microvolt = < 800000>; ++ regulator-max-microvolt = <1250000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ }; ++ ++ smb208_s2b: smb208-s2b { ++ compatible = "qcom,rpm-smb208"; ++ reg = <QCOM_RPM_SMB208_S2b>; ++ ++ regulator-min-microvolt = < 800000>; ++ regulator-max-microvolt = <1250000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ }; ++ }; ++ + qcom_pinmux: pinmux@800000 { + compatible = "qcom,ipq8064-pinctrl"; + reg = <0x800000 0x4000>; +@@ -164,6 +222,12 @@ + reg = <0x02098000 0x1000>, <0x02008000 0x1000>; + }; + ++ l2cc: clock-controller@2011000 { ++ compatible = "qcom,kpss-gcc", "syscon"; ++ reg = <0x2011000 0x1000>; ++ clock-output-names = "acpu_l2_aux"; ++ }; ++ + saw0: regulator@2089000 { + compatible = "qcom,saw2"; + reg = <0x02089000 0x1000>, <0x02009000 0x1000>; diff --git a/target/linux/ipq806x/patches-4.4/133-ARM-Add-Krait-L2-register-accessor-functions.patch b/target/linux/ipq806x/patches-4.4/133-ARM-Add-Krait-L2-register-accessor-functions.patch new file mode 100644 index 0000000..01bd749 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/133-ARM-Add-Krait-L2-register-accessor-functions.patch @@ -0,0 +1,144 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,01/13] ARM: Add Krait L2 register accessor functions +From: Stephen Boyd <sboyd@codeaurora.org> +X-Patchwork-Id: 6063051 +Message-Id: <1426920332-9340-2-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org> +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar <viresh.kumar@linaro.org>, + Mark Rutland <mark.rutland@arm.com>, Russell King <linux@arm.linux.org.uk>, + Courtney Cavin <courtney.cavin@sonymobile.com> +Date: Fri, 20 Mar 2015 23:45:20 -0700 + +Krait CPUs have a handful of L2 cache controller registers that +live behind a cp15 based indirection register. First you program +the indirection register (l2cpselr) to point the L2 'window' +register (l2cpdr) at what you want to read/write. Then you +read/write the 'window' register to do what you want. The +l2cpselr register is not banked per-cpu so we must lock around +accesses to it to prevent other CPUs from re-pointing l2cpdr +underneath us. + +Cc: Mark Rutland <mark.rutland@arm.com> +Cc: Russell King <linux@arm.linux.org.uk> +Cc: Courtney Cavin <courtney.cavin@sonymobile.com> +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> + +--- +arch/arm/common/Kconfig | 3 ++ + arch/arm/common/Makefile | 1 + + arch/arm/common/krait-l2-accessors.c | 58 +++++++++++++++++++++++++++++++ + arch/arm/include/asm/krait-l2-accessors.h | 20 +++++++++++ + 4 files changed, 82 insertions(+) + create mode 100644 arch/arm/common/krait-l2-accessors.c + create mode 100644 arch/arm/include/asm/krait-l2-accessors.h + +--- a/arch/arm/common/Kconfig ++++ b/arch/arm/common/Kconfig +@@ -9,6 +9,9 @@ + bool + select ZONE_DMA + ++config KRAIT_L2_ACCESSORS ++ bool ++ + config SHARP_LOCOMO + bool + +--- a/arch/arm/common/Makefile ++++ b/arch/arm/common/Makefile +@@ -7,6 +7,7 @@ + obj-$(CONFIG_ICST) += icst.o + obj-$(CONFIG_SA1111) += sa1111.o + obj-$(CONFIG_DMABOUNCE) += dmabounce.o ++obj-$(CONFIG_KRAIT_L2_ACCESSORS) += krait-l2-accessors.o + obj-$(CONFIG_SHARP_LOCOMO) += locomo.o + obj-$(CONFIG_SHARP_PARAM) += sharpsl_param.o + obj-$(CONFIG_SHARP_SCOOP) += scoop.o +--- /dev/null ++++ b/arch/arm/common/krait-l2-accessors.c +@@ -0,0 +1,58 @@ ++/* ++ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/spinlock.h> ++#include <linux/export.h> ++ ++#include <asm/barrier.h> ++#include <asm/krait-l2-accessors.h> ++ ++static DEFINE_RAW_SPINLOCK(krait_l2_lock); ++ ++void krait_set_l2_indirect_reg(u32 addr, u32 val) ++{ ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&krait_l2_lock, flags); ++ /* ++ * Select the L2 window by poking l2cpselr, then write to the window ++ * via l2cpdr. ++ */ ++ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr)); ++ isb(); ++ asm volatile ("mcr p15, 3, %0, c15, c0, 7 @ l2cpdr" : : "r" (val)); ++ isb(); ++ ++ raw_spin_unlock_irqrestore(&krait_l2_lock, flags); ++} ++EXPORT_SYMBOL(krait_set_l2_indirect_reg); ++ ++u32 krait_get_l2_indirect_reg(u32 addr) ++{ ++ u32 val; ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&krait_l2_lock, flags); ++ /* ++ * Select the L2 window by poking l2cpselr, then read from the window ++ * via l2cpdr. ++ */ ++ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr)); ++ isb(); ++ asm volatile ("mrc p15, 3, %0, c15, c0, 7 @ l2cpdr" : "=r" (val)); ++ ++ raw_spin_unlock_irqrestore(&krait_l2_lock, flags); ++ ++ return val; ++} ++EXPORT_SYMBOL(krait_get_l2_indirect_reg); +--- /dev/null ++++ b/arch/arm/include/asm/krait-l2-accessors.h +@@ -0,0 +1,20 @@ ++/* ++ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#ifndef __ASMARM_KRAIT_L2_ACCESSORS_H ++#define __ASMARM_KRAIT_L2_ACCESSORS_H ++ ++extern void krait_set_l2_indirect_reg(u32 addr, u32 val); ++extern u32 krait_get_l2_indirect_reg(u32 addr); ++ ++#endif diff --git a/target/linux/ipq806x/patches-4.4/134-clk-mux-Split-out-register-accessors-for-reuse.patch b/target/linux/ipq806x/patches-4.4/134-clk-mux-Split-out-register-accessors-for-reuse.patch new file mode 100644 index 0000000..acf5820 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/134-clk-mux-Split-out-register-accessors-for-reuse.patch @@ -0,0 +1,184 @@ +From 4c28a15ea536281c8d619e5c6716ade914c79a6e Mon Sep 17 00:00:00 2001 +From: Stephen Boyd <sboyd@codeaurora.org> +Date: Fri, 20 Mar 2015 23:45:21 -0700 +Subject: [PATCH 1/2] clk: mux: Split out register accessors for reuse + +We want to reuse the logic in clk-mux.c for other clock drivers +that don't use readl as register accessors. Fortunately, there +really isn't much to the mux code besides the table indirection +and quirk flags if you assume any bit shifting and masking has +been done already. Pull that logic out into reusable functions +that operate on an optional table and some flags so that other +drivers can use the same logic. + +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> +Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org> +--- + drivers/clk/clk-mux.c | 74 +++++++++++++++++++++++++++----------------- + include/linux/clk-provider.h | 9 ++++-- + 2 files changed, 53 insertions(+), 30 deletions(-) + +diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c +index 7129c86..b03a34d 100644 +--- a/drivers/clk/clk-mux.c ++++ b/drivers/clk/clk-mux.c +@@ -28,35 +28,24 @@ + + #define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw) + +-static u8 clk_mux_get_parent(struct clk_hw *hw) ++unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val, ++ unsigned int *table, unsigned long flags) + { +- struct clk_mux *mux = to_clk_mux(hw); + int num_parents = clk_hw_get_num_parents(hw); +- u32 val; + +- /* +- * FIXME need a mux-specific flag to determine if val is bitwise or numeric +- * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1 +- * to 0x7 (index starts at one) +- * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so +- * val = 0x4 really means "bit 2, index starts at bit 0" +- */ +- val = clk_readl(mux->reg) >> mux->shift; +- val &= mux->mask; +- +- if (mux->table) { ++ if (table) { + int i; + + for (i = 0; i < num_parents; i++) +- if (mux->table[i] == val) ++ if (table[i] == val) + return i; + return -EINVAL; + } + +- if (val && (mux->flags & CLK_MUX_INDEX_BIT)) ++ if (val && (flags & CLK_MUX_INDEX_BIT)) + val = ffs(val) - 1; + +- if (val && (mux->flags & CLK_MUX_INDEX_ONE)) ++ if (val && (flags & CLK_MUX_INDEX_ONE)) + val--; + + if (val >= num_parents) +@@ -64,24 +53,53 @@ static u8 clk_mux_get_parent(struct clk_hw *hw) + + return val; + } ++EXPORT_SYMBOL_GPL(clk_mux_get_parent); + +-static int clk_mux_set_parent(struct clk_hw *hw, u8 index) ++static u8 _clk_mux_get_parent(struct clk_hw *hw) + { + struct clk_mux *mux = to_clk_mux(hw); + u32 val; +- unsigned long flags = 0; + +- if (mux->table) +- index = mux->table[index]; ++ /* ++ * FIXME need a mux-specific flag to determine if val is bitwise or numeric ++ * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1 ++ * to 0x7 (index starts at one) ++ * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so ++ * val = 0x4 really means "bit 2, index starts at bit 0" ++ */ ++ val = clk_readl(mux->reg) >> mux->shift; ++ val &= mux->mask; + +- else { +- if (mux->flags & CLK_MUX_INDEX_BIT) +- index = 1 << index; ++ return clk_mux_get_parent(hw, val, mux->table, mux->flags); ++} ++ ++unsigned int clk_mux_reindex(u8 index, unsigned int *table, ++ unsigned long flags) ++{ ++ unsigned int val = index; + +- if (mux->flags & CLK_MUX_INDEX_ONE) +- index++; ++ if (table) { ++ val = table[val]; ++ } else { ++ if (flags & CLK_MUX_INDEX_BIT) ++ val = 1 << index; ++ ++ if (flags & CLK_MUX_INDEX_ONE) ++ val++; + } + ++ return val; ++} ++EXPORT_SYMBOL_GPL(clk_mux_reindex); ++ ++static int clk_mux_set_parent(struct clk_hw *hw, u8 index) ++{ ++ struct clk_mux *mux = to_clk_mux(hw); ++ u32 val; ++ unsigned long flags = 0; ++ ++ index = clk_mux_reindex(index, mux->table, mux->flags); ++ + if (mux->lock) + spin_lock_irqsave(mux->lock, flags); + else +@@ -105,7 +123,7 @@ static int clk_mux_set_parent(struct clk_hw *hw, u8 index) + } + + const struct clk_ops clk_mux_ops = { +- .get_parent = clk_mux_get_parent, ++ .get_parent = _clk_mux_get_parent, + .set_parent = clk_mux_set_parent, + .determine_rate = __clk_mux_determine_rate, + }; +@@ -120,7 +138,7 @@ struct clk *clk_register_mux_table(struct device *dev, const char *name, + const char * const *parent_names, u8 num_parents, + unsigned long flags, + void __iomem *reg, u8 shift, u32 mask, +- u8 clk_mux_flags, u32 *table, spinlock_t *lock) ++ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock) + { + struct clk_mux *mux; + struct clk *clk; +diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h +index c56988a..b6b17b5 100644 +--- a/include/linux/clk-provider.h ++++ b/include/linux/clk-provider.h +@@ -432,7 +432,7 @@ void clk_unregister_divider(struct clk *clk); + struct clk_mux { + struct clk_hw hw; + void __iomem *reg; +- u32 *table; ++ unsigned int *table; + u32 mask; + u8 shift; + u8 flags; +@@ -448,6 +448,11 @@ struct clk_mux { + extern const struct clk_ops clk_mux_ops; + extern const struct clk_ops clk_mux_ro_ops; + ++unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val, ++ unsigned int *table, unsigned long flags); ++unsigned int clk_mux_reindex(u8 index, unsigned int *table, ++ unsigned long flags); ++ + struct clk *clk_register_mux(struct device *dev, const char *name, + const char * const *parent_names, u8 num_parents, + unsigned long flags, +@@ -458,7 +463,7 @@ struct clk *clk_register_mux_table(struct device *dev, const char *name, + const char * const *parent_names, u8 num_parents, + unsigned long flags, + void __iomem *reg, u8 shift, u32 mask, +- u8 clk_mux_flags, u32 *table, spinlock_t *lock); ++ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock); + + void clk_unregister_mux(struct clk *clk); + +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/135-clk-Avoid-sending-high-rates-to-downstream-clocks-du.patch b/target/linux/ipq806x/patches-4.4/135-clk-Avoid-sending-high-rates-to-downstream-clocks-du.patch new file mode 100644 index 0000000..5df0a56 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/135-clk-Avoid-sending-high-rates-to-downstream-clocks-du.patch @@ -0,0 +1,127 @@ +From 39d42ce5031d2a4f92fa203b87acfbab340b15a2 Mon Sep 17 00:00:00 2001 +From: Stephen Boyd <sboyd@codeaurora.org> +Date: Fri, 20 Mar 2015 23:45:22 -0700 +Subject: [PATCH 2/2] clk: Avoid sending high rates to downstream clocks during + set_rate + +If a clock is on and we call clk_set_rate() on it we may get into +a situation where the clock temporarily increases in rate +dramatically while we walk the tree and call .set_rate() ops. For +example, consider a case where a PLL feeds into a divider. +Initially the divider is set to divide by 1 and the PLL is +running fairly slow (100MHz). The downstream consumer of the +divider output can only handle rates =< 400 MHz, but the divider +can only choose between divisors of 1 and 4. + + +-----+ +----------------+ + | PLL |-->| div 1 or div 4 |---> consumer device + +-----+ +----------------+ + +To achieve a rate of 400MHz on the output of the divider, we +would have to set the rate of the PLL to 1.6 GHz and then divide +it by 4. The current code would set the PLL to 1.6GHz first while +the divider is still set to 1, thus causing the downstream +consumer of the clock to receive a few clock cycles of 1.6GHz +clock (far beyond it's maximum acceptable rate). We should be +changing the divider first before increasing the PLL rate to +avoid this problem. + +Therefore, set the rate of any child clocks that are increasing +in rate from their current rate so that they can increase their +dividers if necessary. We assume that there isn't such a thing as +minimum rate requirements. + +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> +Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org> +--- + drivers/clk/clk.c | 34 ++++++++++++++++++++++------------ + 1 file changed, 22 insertions(+), 12 deletions(-) + +diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c +index f13c3f4..8404c3c 100644 +--- a/drivers/clk/clk.c ++++ b/drivers/clk/clk.c +@@ -1427,21 +1427,24 @@ static struct clk_core *clk_propagate_rate_change(struct clk_core *core, + * walk down a subtree and set the new rates notifying the rate + * change on the way + */ +-static void clk_change_rate(struct clk_core *core) ++static void ++clk_change_rate(struct clk_core *core, unsigned long best_parent_rate) + { + struct clk_core *child; + struct hlist_node *tmp; + unsigned long old_rate; +- unsigned long best_parent_rate = 0; + bool skip_set_rate = false; + struct clk_core *old_parent; + +- old_rate = core->rate; ++ hlist_for_each_entry(child, &core->children, child_node) { ++ /* Skip children who will be reparented to another clock */ ++ if (child->new_parent && child->new_parent != core) ++ continue; ++ if (child->new_rate > child->rate) ++ clk_change_rate(child, core->new_rate); ++ } + +- if (core->new_parent) +- best_parent_rate = core->new_parent->rate; +- else if (core->parent) +- best_parent_rate = core->parent->rate; ++ old_rate = core->rate; + + if (core->new_parent && core->new_parent != core->parent) { + old_parent = __clk_set_parent_before(core, core->new_parent); +@@ -1467,7 +1470,7 @@ static void clk_change_rate(struct clk_core *core) + + trace_clk_set_rate_complete(core, core->new_rate); + +- core->rate = clk_recalc(core, best_parent_rate); ++ core->rate = core->new_rate; + + if (core->notifier_count && old_rate != core->rate) + __clk_notify(core, POST_RATE_CHANGE, old_rate, core->rate); +@@ -1483,12 +1486,13 @@ static void clk_change_rate(struct clk_core *core) + /* Skip children who will be reparented to another clock */ + if (child->new_parent && child->new_parent != core) + continue; +- clk_change_rate(child); ++ if (child->new_rate != child->rate) ++ clk_change_rate(child, core->new_rate); + } + + /* handle the new child who might not be in core->children yet */ +- if (core->new_child) +- clk_change_rate(core->new_child); ++ if (core->new_child && core->new_child->new_rate != core->new_child->rate) ++ clk_change_rate(core->new_child, core->new_rate); + } + + static int clk_core_set_rate_nolock(struct clk_core *core, +@@ -1497,6 +1501,7 @@ static int clk_core_set_rate_nolock(struct clk_core *core, + struct clk_core *top, *fail_clk; + unsigned long rate = req_rate; + int ret = 0; ++ unsigned long parent_rate; + + if (!core) + return 0; +@@ -1522,8 +1527,13 @@ static int clk_core_set_rate_nolock(struct clk_core *core, + return -EBUSY; + } + ++ if (top->parent) ++ parent_rate = top->parent->rate; ++ else ++ parent_rate = 0; ++ + /* change the rates */ +- clk_change_rate(top); ++ clk_change_rate(top, parent_rate); + + core->req_rate = req_rate; + +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/136-clk-Add-safe-switch-hook.patch b/target/linux/ipq806x/patches-4.4/136-clk-Add-safe-switch-hook.patch new file mode 100644 index 0000000..e969f6b --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/136-clk-Add-safe-switch-hook.patch @@ -0,0 +1,158 @@ +From f7a00ea959be31f9b742042294a359d508edce94 Mon Sep 17 00:00:00 2001 +From: Stephen Boyd <sboyd@codeaurora.org> +Date: Fri, 20 Mar 2015 23:45:23 -0700 +Subject: [PATCH] clk: Add safe switch hook + +Sometimes clocks can't accept their parent source turning off +while the source is reprogrammed to a different rate. Most +notably CPU clocks require a way to switch away from the current +PLL they're running on, reprogram that PLL to a new rate, and +then switch back to the PLL with the new rate once they're done. +Add a hook that drivers can implement allowing them to return a +'safe parent' that they can switch their parent to while the +upstream source is reprogrammed to support this. + +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> +Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org> +--- + drivers/clk/clk.c | 61 ++++++++++++++++++++++++++++++++++++++------ + include/linux/clk-provider.h | 1 + + 2 files changed, 54 insertions(+), 8 deletions(-) + +diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c +index 8404c3c..a29319a 100644 +--- a/drivers/clk/clk.c ++++ b/drivers/clk/clk.c +@@ -51,9 +51,12 @@ struct clk_core { + struct clk_core **parents; + u8 num_parents; + u8 new_parent_index; ++ u8 safe_parent_index; + unsigned long rate; + unsigned long req_rate; ++ unsigned long old_rate; + unsigned long new_rate; ++ struct clk_core *safe_parent; + struct clk_core *new_parent; + struct clk_core *new_child; + unsigned long flags; +@@ -1271,7 +1274,8 @@ out: + static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate, + struct clk_core *new_parent, u8 p_index) + { +- struct clk_core *child; ++ struct clk_core *child, *parent; ++ struct clk_hw *parent_hw; + + core->new_rate = new_rate; + core->new_parent = new_parent; +@@ -1281,6 +1285,18 @@ static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate, + if (new_parent && new_parent != core->parent) + new_parent->new_child = core; + ++ if (core->ops->get_safe_parent) { ++ parent_hw = core->ops->get_safe_parent(core->hw); ++ if (parent_hw) { ++ parent = parent_hw->core; ++ p_index = clk_fetch_parent_index(core, parent); ++ core->safe_parent_index = p_index; ++ core->safe_parent = parent; ++ } ++ } else { ++ core->safe_parent = NULL; ++ } ++ + hlist_for_each_entry(child, &core->children, child_node) { + child->new_rate = clk_recalc(child, new_rate); + clk_calc_subtree(child, child->new_rate, NULL, 0); +@@ -1393,14 +1409,43 @@ static struct clk_core *clk_propagate_rate_change(struct clk_core *core, + unsigned long event) + { + struct clk_core *child, *tmp_clk, *fail_clk = NULL; ++ struct clk_core *old_parent; + int ret = NOTIFY_DONE; + +- if (core->rate == core->new_rate) ++ if (core->rate == core->new_rate && event != POST_RATE_CHANGE) + return NULL; + ++ switch (event) { ++ case PRE_RATE_CHANGE: ++ if (core->safe_parent) ++ core->ops->set_parent(core->hw, core->safe_parent_index); ++ core->old_rate = core->rate; ++ break; ++ case POST_RATE_CHANGE: ++ if (core->safe_parent) { ++ old_parent = __clk_set_parent_before(core, ++ core->new_parent); ++ if (core->ops->set_rate_and_parent) { ++ core->ops->set_rate_and_parent(core->hw, ++ core->new_rate, ++ core->new_parent ? ++ core->new_parent->rate : 0, ++ core->new_parent_index); ++ } else if (core->ops->set_parent) { ++ core->ops->set_parent(core->hw, ++ core->new_parent_index); ++ } ++ __clk_set_parent_after(core, core->new_parent, ++ old_parent); ++ } ++ break; ++ } ++ + if (core->notifier_count) { +- ret = __clk_notify(core, event, core->rate, core->new_rate); +- if (ret & NOTIFY_STOP_MASK) ++ if (event != POST_RATE_CHANGE || core->old_rate != core->rate) ++ ret = __clk_notify(core, event, core->old_rate, ++ core->new_rate); ++ if (ret & NOTIFY_STOP_MASK && event != POST_RATE_CHANGE) + fail_clk = core; + } + +@@ -1446,7 +1491,8 @@ clk_change_rate(struct clk_core *core, unsigned long best_parent_rate) + + old_rate = core->rate; + +- if (core->new_parent && core->new_parent != core->parent) { ++ if (core->new_parent && core->new_parent != core->parent && ++ !core->safe_parent) { + old_parent = __clk_set_parent_before(core, core->new_parent); + trace_clk_set_parent(core, core->new_parent); + +@@ -1472,9 +1518,6 @@ clk_change_rate(struct clk_core *core, unsigned long best_parent_rate) + + core->rate = core->new_rate; + +- if (core->notifier_count && old_rate != core->rate) +- __clk_notify(core, POST_RATE_CHANGE, old_rate, core->rate); +- + if (core->flags & CLK_RECALC_NEW_RATES) + (void)clk_calc_new_rates(core, core->new_rate); + +@@ -1537,6 +1580,8 @@ static int clk_core_set_rate_nolock(struct clk_core *core, + + core->req_rate = req_rate; + ++ clk_propagate_rate_change(top, POST_RATE_CHANGE); ++ + return ret; + } + +diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h +index b6b17b5..5d49262 100644 +--- a/include/linux/clk-provider.h ++++ b/include/linux/clk-provider.h +@@ -202,6 +202,7 @@ struct clk_ops { + struct clk_rate_request *req); + int (*set_parent)(struct clk_hw *hw, u8 index); + u8 (*get_parent)(struct clk_hw *hw); ++ struct clk_hw *(*get_safe_parent)(struct clk_hw *hw); + int (*set_rate)(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate); + int (*set_rate_and_parent)(struct clk_hw *hw, +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch b/target/linux/ipq806x/patches-4.4/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch new file mode 100644 index 0000000..a1b1f4b --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch @@ -0,0 +1,351 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,05/13] clk: qcom: Add support for High-Frequency PLLs (HFPLLs) +From: Stephen Boyd <sboyd@codeaurora.org> +X-Patchwork-Id: 6063261 +Message-Id: <1426920332-9340-6-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org> +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar <viresh.kumar@linaro.org> +Date: Fri, 20 Mar 2015 23:45:24 -0700 + +HFPLLs are the main frequency source for Krait CPU clocks. Add +support for changing the rate of these PLLs. + +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> + +--- +I'd really like to get rid of __clk_hfpll_init_once() if possible... + + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/clk-hfpll.c | 253 +++++++++++++++++++++++++++++++++++++++++++ + drivers/clk/qcom/clk-hfpll.h | 54 +++++++++ + 3 files changed, 308 insertions(+) + create mode 100644 drivers/clk/qcom/clk-hfpll.c + create mode 100644 drivers/clk/qcom/clk-hfpll.h + +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -8,6 +8,7 @@ + clk-qcom-y += clk-branch.o + clk-qcom-y += clk-regmap-divider.o + clk-qcom-y += clk-regmap-mux.o ++clk-qcom-y += clk-hfpll.o + clk-qcom-y += reset.o + clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o + +--- /dev/null ++++ b/drivers/clk/qcom/clk-hfpll.c +@@ -0,0 +1,253 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++#include <linux/kernel.h> ++#include <linux/export.h> ++#include <linux/regmap.h> ++#include <linux/delay.h> ++#include <linux/err.h> ++#include <linux/clk-provider.h> ++#include <linux/spinlock.h> ++ ++#include "clk-regmap.h" ++#include "clk-hfpll.h" ++ ++#define PLL_OUTCTRL BIT(0) ++#define PLL_BYPASSNL BIT(1) ++#define PLL_RESET_N BIT(2) ++ ++/* Initialize a HFPLL at a given rate and enable it. */ ++static void __clk_hfpll_init_once(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ ++ if (likely(h->init_done)) ++ return; ++ ++ /* Configure PLL parameters for integer mode. */ ++ if (hd->config_val) ++ regmap_write(regmap, hd->config_reg, hd->config_val); ++ regmap_write(regmap, hd->m_reg, 0); ++ regmap_write(regmap, hd->n_reg, 1); ++ ++ if (hd->user_reg) { ++ u32 regval = hd->user_val; ++ unsigned long rate; ++ ++ rate = clk_hw_get_rate(hw->clk); ++ ++ /* Pick the right VCO. */ ++ if (hd->user_vco_mask && rate > hd->low_vco_max_rate) ++ regval |= hd->user_vco_mask; ++ regmap_write(regmap, hd->user_reg, regval); ++ } ++ ++ if (hd->droop_reg) ++ regmap_write(regmap, hd->droop_reg, hd->droop_val); ++ ++ h->init_done = true; ++} ++ ++static void __clk_hfpll_enable(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 val; ++ ++ __clk_hfpll_init_once(hw); ++ ++ /* Disable PLL bypass mode. */ ++ regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL); ++ ++ /* ++ * H/W requires a 5us delay between disabling the bypass and ++ * de-asserting the reset. Delay 10us just to be safe. ++ */ ++ udelay(10); ++ ++ /* De-assert active-low PLL reset. */ ++ regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N); ++ ++ /* Wait for PLL to lock. */ ++ if (hd->status_reg) { ++ do { ++ regmap_read(regmap, hd->status_reg, &val); ++ } while (!(val & BIT(hd->lock_bit))); ++ } else { ++ udelay(60); ++ } ++ ++ /* Enable PLL output. */ ++ regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL); ++} ++ ++/* Enable an already-configured HFPLL. */ ++static int clk_hfpll_enable(struct clk_hw *hw) ++{ ++ unsigned long flags; ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 mode; ++ ++ spin_lock_irqsave(&h->lock, flags); ++ regmap_read(regmap, hd->mode_reg, &mode); ++ if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL))) ++ __clk_hfpll_enable(hw); ++ spin_unlock_irqrestore(&h->lock, flags); ++ ++ return 0; ++} ++ ++static void __clk_hfpll_disable(struct clk_hfpll *h) ++{ ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ ++ /* ++ * Disable the PLL output, disable test mode, enable the bypass mode, ++ * and assert the reset. ++ */ ++ regmap_update_bits(regmap, hd->mode_reg, ++ PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0); ++} ++ ++static void clk_hfpll_disable(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&h->lock, flags); ++ __clk_hfpll_disable(h); ++ spin_unlock_irqrestore(&h->lock, flags); ++} ++ ++static long clk_hfpll_round_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long *parent_rate) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ unsigned long rrate; ++ ++ rate = clamp(rate, hd->min_rate, hd->max_rate); ++ ++ rrate = DIV_ROUND_UP(rate, *parent_rate) * *parent_rate; ++ if (rrate > hd->max_rate) ++ rrate -= *parent_rate; ++ ++ return rrate; ++} ++ ++/* ++ * For optimization reasons, assumes no downstream clocks are actively using ++ * it. ++ */ ++static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long parent_rate) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ unsigned long flags; ++ u32 l_val, val; ++ bool enabled; ++ ++ l_val = rate / parent_rate; ++ ++ spin_lock_irqsave(&h->lock, flags); ++ ++ enabled = __clk_is_enabled(hw->clk); ++ if (enabled) ++ __clk_hfpll_disable(h); ++ ++ /* Pick the right VCO. */ ++ if (hd->user_reg && hd->user_vco_mask) { ++ regmap_read(regmap, hd->user_reg, &val); ++ if (rate <= hd->low_vco_max_rate) ++ val &= ~hd->user_vco_mask; ++ else ++ val |= hd->user_vco_mask; ++ regmap_write(regmap, hd->user_reg, val); ++ } ++ ++ regmap_write(regmap, hd->l_reg, l_val); ++ ++ if (enabled) ++ __clk_hfpll_enable(hw); ++ ++ spin_unlock_irqrestore(&h->lock, flags); ++ ++ return 0; ++} ++ ++static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw, ++ unsigned long parent_rate) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 l_val; ++ ++ regmap_read(regmap, hd->l_reg, &l_val); ++ ++ return l_val * parent_rate; ++} ++ ++static void clk_hfpll_init(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 mode, status; ++ ++ regmap_read(regmap, hd->mode_reg, &mode); ++ if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) { ++ __clk_hfpll_init_once(hw); ++ return; ++ } ++ ++ if (hd->status_reg) { ++ regmap_read(regmap, hd->status_reg, &status); ++ if (!(status & BIT(hd->lock_bit))) { ++ WARN(1, "HFPLL %s is ON, but not locked!\n", ++ __clk_get_name(hw->clk)); ++ clk_hfpll_disable(hw); ++ __clk_hfpll_init_once(hw); ++ } ++ } ++} ++ ++static int hfpll_is_enabled(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 mode; ++ ++ regmap_read(regmap, hd->mode_reg, &mode); ++ mode &= 0x7; ++ return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL); ++} ++ ++const struct clk_ops clk_ops_hfpll = { ++ .enable = clk_hfpll_enable, ++ .disable = clk_hfpll_disable, ++ .is_enabled = hfpll_is_enabled, ++ .round_rate = clk_hfpll_round_rate, ++ .set_rate = clk_hfpll_set_rate, ++ .recalc_rate = clk_hfpll_recalc_rate, ++ .init = clk_hfpll_init, ++}; ++EXPORT_SYMBOL_GPL(clk_ops_hfpll); +--- /dev/null ++++ b/drivers/clk/qcom/clk-hfpll.h +@@ -0,0 +1,54 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++#ifndef __QCOM_CLK_HFPLL_H__ ++#define __QCOM_CLK_HFPLL_H__ ++ ++#include <linux/clk-provider.h> ++#include <linux/spinlock.h> ++#include "clk-regmap.h" ++ ++struct hfpll_data { ++ u32 mode_reg; ++ u32 l_reg; ++ u32 m_reg; ++ u32 n_reg; ++ u32 user_reg; ++ u32 droop_reg; ++ u32 config_reg; ++ u32 status_reg; ++ u8 lock_bit; ++ ++ u32 droop_val; ++ u32 config_val; ++ u32 user_val; ++ u32 user_vco_mask; ++ unsigned long low_vco_max_rate; ++ ++ unsigned long min_rate; ++ unsigned long max_rate; ++}; ++ ++struct clk_hfpll { ++ struct hfpll_data const *d; ++ int init_done; ++ ++ struct clk_regmap clkr; ++ spinlock_t lock; ++}; ++ ++#define to_clk_hfpll(_hw) \ ++ container_of(to_clk_regmap(_hw), struct clk_hfpll, clkr) ++ ++extern const struct clk_ops clk_ops_hfpll; ++ ++#endif diff --git a/target/linux/ipq806x/patches-4.4/138-clk-qcom-Add-HFPLL-driver.patch b/target/linux/ipq806x/patches-4.4/138-clk-qcom-Add-HFPLL-driver.patch new file mode 100644 index 0000000..5a452db --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/138-clk-qcom-Add-HFPLL-driver.patch @@ -0,0 +1,206 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,06/13] clk: qcom: Add HFPLL driver +From: Stephen Boyd <sboyd@codeaurora.org> +X-Patchwork-Id: 6063231 +Message-Id: <1426920332-9340-7-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org> +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org> +Date: Fri, 20 Mar 2015 23:45:25 -0700 + +On some devices (MSM8974 for example), the HFPLLs are +instantiated within the Krait processor subsystem as separate +register regions. Add a driver for these PLLs so that we can +provide HFPLL clocks for use by the system. + +Cc: <devicetree@vger.kernel.org> +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> + +--- +.../devicetree/bindings/clock/qcom,hfpll.txt | 40 ++++++++ + drivers/clk/qcom/Kconfig | 8 ++ + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/hfpll.c | 109 +++++++++++++++++++++ + 4 files changed, 158 insertions(+) + create mode 100644 Documentation/devicetree/bindings/clock/qcom,hfpll.txt + create mode 100644 drivers/clk/qcom/hfpll.c + +--- /dev/null ++++ b/Documentation/devicetree/bindings/clock/qcom,hfpll.txt +@@ -0,0 +1,40 @@ ++High-Frequency PLL (HFPLL) ++ ++PROPERTIES ++ ++- compatible: ++ Usage: required ++ Value type: <string> ++ Definition: must be "qcom,hfpll" ++ ++- reg: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: address and size of HPLL registers. An optional second ++ element specifies the address and size of the alias ++ register region. ++ ++- clock-output-names: ++ Usage: required ++ Value type: <string> ++ Definition: Name of the PLL. Typically hfpllX where X is a CPU number ++ starting at 0. Otherwise hfpll_Y where Y is more specific ++ such as "l2". ++ ++Example: ++ ++1) An HFPLL for the L2 cache. ++ ++ clock-controller@f9016000 { ++ compatible = "qcom,hfpll"; ++ reg = <0xf9016000 0x30>; ++ clock-output-names = "hfpll_l2"; ++ }; ++ ++2) An HFPLL for CPU0. This HFPLL has the alias register region. ++ ++ clock-controller@f908a000 { ++ compatible = "qcom,hfpll"; ++ reg = <0xf908a000 0x30>, <0xf900a000 0x30>; ++ clock-output-names = "hfpll0"; ++ }; +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -106,3 +106,11 @@ + Support for the multimedia clock controller on msm8974 devices. + Say Y if you want to support multimedia devices such as display, + graphics, video encode/decode, camera, etc. ++ ++config QCOM_HFPLL ++ tristate "High-Frequency PLL (HFPLL) Clock Controller" ++ depends on COMMON_CLK_QCOM ++ help ++ Support for the high-frequency PLLs present on Qualcomm devices. ++ Say Y if you want to support CPU frequency scaling on devices ++ such as MSM8974, APQ8084, etc. +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -23,3 +23,4 @@ + obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o + obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o + obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o ++obj-$(CONFIG_QCOM_HFPLL) += hfpll.o +--- /dev/null ++++ b/drivers/clk/qcom/hfpll.c +@@ -0,0 +1,109 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/platform_device.h> ++#include <linux/of.h> ++#include <linux/clk.h> ++#include <linux/clk-provider.h> ++#include <linux/regmap.h> ++ ++#include "clk-regmap.h" ++#include "clk-hfpll.h" ++ ++static const struct hfpll_data hdata = { ++ .mode_reg = 0x00, ++ .l_reg = 0x04, ++ .m_reg = 0x08, ++ .n_reg = 0x0c, ++ .user_reg = 0x10, ++ .config_reg = 0x14, ++ .config_val = 0x430405d, ++ .status_reg = 0x1c, ++ .lock_bit = 16, ++ ++ .user_val = 0x8, ++ .user_vco_mask = 0x100000, ++ .low_vco_max_rate = 1248000000, ++ .min_rate = 537600000UL, ++ .max_rate = 2900000000UL, ++}; ++ ++static const struct of_device_id qcom_hfpll_match_table[] = { ++ { .compatible = "qcom,hfpll" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, qcom_hfpll_match_table); ++ ++static const struct regmap_config hfpll_regmap_config = { ++ .reg_bits = 32, ++ .reg_stride = 4, ++ .val_bits = 32, ++ .max_register = 0x30, ++ .fast_io = true, ++}; ++ ++static int qcom_hfpll_probe(struct platform_device *pdev) ++{ ++ struct clk *clk; ++ struct resource *res; ++ struct device *dev = &pdev->dev; ++ void __iomem *base; ++ struct regmap *regmap; ++ struct clk_hfpll *h; ++ struct clk_init_data init = { ++ .parent_names = (const char *[]){ "xo" }, ++ .num_parents = 1, ++ .ops = &clk_ops_hfpll, ++ }; ++ ++ h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL); ++ if (!h) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ base = devm_ioremap_resource(dev, res); ++ if (IS_ERR(base)) ++ return PTR_ERR(base); ++ ++ regmap = devm_regmap_init_mmio(&pdev->dev, base, &hfpll_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ if (of_property_read_string_index(dev->of_node, "clock-output-names", ++ 0, &init.name)) ++ return -ENODEV; ++ ++ h->d = &hdata; ++ h->clkr.hw.init = &init; ++ spin_lock_init(&h->lock); ++ ++ clk = devm_clk_register_regmap(&pdev->dev, &h->clkr); ++ ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static struct platform_driver qcom_hfpll_driver = { ++ .probe = qcom_hfpll_probe, ++ .driver = { ++ .name = "qcom-hfpll", ++ .of_match_table = qcom_hfpll_match_table, ++ }, ++}; ++module_platform_driver(qcom_hfpll_driver); ++ ++MODULE_DESCRIPTION("QCOM HFPLL Clock Driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:qcom-hfpll"); diff --git a/target/linux/ipq806x/patches-4.4/139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch b/target/linux/ipq806x/patches-4.4/139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch new file mode 100644 index 0000000..5b191b5 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch @@ -0,0 +1,127 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,08/13] clk: qcom: Add IPQ806X's HFPLLs +From: Stephen Boyd <sboyd@codeaurora.org> +X-Patchwork-Id: 6063241 +Message-Id: <1426920332-9340-9-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org> +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar <viresh.kumar@linaro.org> +Date: Fri, 20 Mar 2015 23:45:27 -0700 + +Describe the HFPLLs present on IPQ806X devices. + +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> + +--- +drivers/clk/qcom/gcc-ipq806x.c | 83 ++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 83 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq806x.c ++++ b/drivers/clk/qcom/gcc-ipq806x.c +@@ -30,6 +30,7 @@ + #include "clk-pll.h" + #include "clk-rcg.h" + #include "clk-branch.h" ++#include "clk-hfpll.h" + #include "reset.h" + + static struct clk_pll pll0 = { +@@ -113,6 +114,85 @@ + }, + }; + ++static struct hfpll_data hfpll0_data = { ++ .mode_reg = 0x3200, ++ .l_reg = 0x3208, ++ .m_reg = 0x320c, ++ .n_reg = 0x3210, ++ .config_reg = 0x3204, ++ .status_reg = 0x321c, ++ .config_val = 0x7845c665, ++ .droop_reg = 0x3214, ++ .droop_val = 0x0108c000, ++ .min_rate = 600000000UL, ++ .max_rate = 1800000000UL, ++}; ++ ++static struct clk_hfpll hfpll0 = { ++ .d = &hfpll0_data, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .parent_names = (const char *[]){ "pxo" }, ++ .num_parents = 1, ++ .name = "hfpll0", ++ .ops = &clk_ops_hfpll, ++ .flags = CLK_IGNORE_UNUSED, ++ }, ++ .lock = __SPIN_LOCK_UNLOCKED(hfpll0.lock), ++}; ++ ++static struct hfpll_data hfpll1_data = { ++ .mode_reg = 0x3240, ++ .l_reg = 0x3248, ++ .m_reg = 0x324c, ++ .n_reg = 0x3250, ++ .config_reg = 0x3244, ++ .status_reg = 0x325c, ++ .config_val = 0x7845c665, ++ .droop_reg = 0x3314, ++ .droop_val = 0x0108c000, ++ .min_rate = 600000000UL, ++ .max_rate = 1800000000UL, ++}; ++ ++static struct clk_hfpll hfpll1 = { ++ .d = &hfpll1_data, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .parent_names = (const char *[]){ "pxo" }, ++ .num_parents = 1, ++ .name = "hfpll1", ++ .ops = &clk_ops_hfpll, ++ .flags = CLK_IGNORE_UNUSED, ++ }, ++ .lock = __SPIN_LOCK_UNLOCKED(hfpll1.lock), ++}; ++ ++static struct hfpll_data hfpll_l2_data = { ++ .mode_reg = 0x3300, ++ .l_reg = 0x3308, ++ .m_reg = 0x330c, ++ .n_reg = 0x3310, ++ .config_reg = 0x3304, ++ .status_reg = 0x331c, ++ .config_val = 0x7845c665, ++ .droop_reg = 0x3314, ++ .droop_val = 0x0108c000, ++ .min_rate = 600000000UL, ++ .max_rate = 1800000000UL, ++}; ++ ++static struct clk_hfpll hfpll_l2 = { ++ .d = &hfpll_l2_data, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .parent_names = (const char *[]){ "pxo" }, ++ .num_parents = 1, ++ .name = "hfpll_l2", ++ .ops = &clk_ops_hfpll, ++ .flags = CLK_IGNORE_UNUSED, ++ }, ++ .lock = __SPIN_LOCK_UNLOCKED(hfpll_l2.lock), ++}; ++ ++ + static struct clk_pll pll14 = { + .l_reg = 0x31c4, + .m_reg = 0x31c8, +@@ -2837,6 +2917,9 @@ + [UBI32_CORE2_CLK_SRC] = &ubi32_core2_src_clk.clkr, + [NSSTCM_CLK_SRC] = &nss_tcm_src.clkr, + [NSSTCM_CLK] = &nss_tcm_clk.clkr, ++ [PLL9] = &hfpll0.clkr, ++ [PLL10] = &hfpll1.clkr, ++ [PLL12] = &hfpll_l2.clkr, + }; + + static const struct qcom_reset_map gcc_ipq806x_resets[] = { diff --git a/target/linux/ipq806x/patches-4.4/140-clk-qcom-Add-support-for-Krait-clocks.patch b/target/linux/ipq806x/patches-4.4/140-clk-qcom-Add-support-for-Krait-clocks.patch new file mode 100644 index 0000000..522482d --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/140-clk-qcom-Add-support-for-Krait-clocks.patch @@ -0,0 +1,272 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,09/13] clk: qcom: Add support for Krait clocks +From: Stephen Boyd <sboyd@codeaurora.org> +X-Patchwork-Id: 6063251 +Message-Id: <1426920332-9340-10-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org> +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar <viresh.kumar@linaro.org> +Date: Fri, 20 Mar 2015 23:45:28 -0700 + +The Krait clocks are made up of a series of muxes and a divider +that choose between a fixed rate clock and dedicated HFPLLs for +each CPU. Instead of using mmio accesses to remux parents, the +Krait implementation exposes the remux control via cp15 +registers. Support these clocks. + +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> + +--- +drivers/clk/qcom/Kconfig | 4 ++ + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/clk-krait.c | 166 +++++++++++++++++++++++++++++++++++++++++++ + drivers/clk/qcom/clk-krait.h | 49 +++++++++++++ + 4 files changed, 220 insertions(+) + create mode 100644 drivers/clk/qcom/clk-krait.c + create mode 100644 drivers/clk/qcom/clk-krait.h + +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -114,3 +114,7 @@ + Support for the high-frequency PLLs present on Qualcomm devices. + Say Y if you want to support CPU frequency scaling on devices + such as MSM8974, APQ8084, etc. ++ ++config KRAIT_CLOCKS ++ bool ++ select KRAIT_L2_ACCESSORS +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -8,6 +8,7 @@ + clk-qcom-y += clk-branch.o + clk-qcom-y += clk-regmap-divider.o + clk-qcom-y += clk-regmap-mux.o ++clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o + clk-qcom-y += clk-hfpll.o + clk-qcom-y += reset.o + clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o + +--- /dev/null ++++ b/drivers/clk/qcom/clk-krait.c +@@ -0,0 +1,166 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/io.h> ++#include <linux/delay.h> ++#include <linux/err.h> ++#include <linux/clk-provider.h> ++#include <linux/spinlock.h> ++ ++#include <asm/krait-l2-accessors.h> ++ ++#include "clk-krait.h" ++ ++/* Secondary and primary muxes share the same cp15 register */ ++static DEFINE_SPINLOCK(krait_clock_reg_lock); ++ ++#define LPL_SHIFT 8 ++static void __krait_mux_set_sel(struct krait_mux_clk *mux, int sel) ++{ ++ unsigned long flags; ++ u32 regval; ++ ++ spin_lock_irqsave(&krait_clock_reg_lock, flags); ++ regval = krait_get_l2_indirect_reg(mux->offset); ++ regval &= ~(mux->mask << mux->shift); ++ regval |= (sel & mux->mask) << mux->shift; ++ if (mux->lpl) { ++ regval &= ~(mux->mask << (mux->shift + LPL_SHIFT)); ++ regval |= (sel & mux->mask) << (mux->shift + LPL_SHIFT); ++ } ++ krait_set_l2_indirect_reg(mux->offset, regval); ++ spin_unlock_irqrestore(&krait_clock_reg_lock, flags); ++ ++ /* Wait for switch to complete. */ ++ mb(); ++ udelay(1); ++} ++ ++static int krait_mux_set_parent(struct clk_hw *hw, u8 index) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ u32 sel; ++ ++ sel = clk_mux_reindex(index, mux->parent_map, 0); ++ mux->en_mask = sel; ++ /* Don't touch mux if CPU is off as it won't work */ ++ if (__clk_is_enabled(hw->clk)) ++ __krait_mux_set_sel(mux, sel); ++ return 0; ++} ++ ++static u8 krait_mux_get_parent(struct clk_hw *hw) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ u32 sel; ++ ++ sel = krait_get_l2_indirect_reg(mux->offset); ++ sel >>= mux->shift; ++ sel &= mux->mask; ++ mux->en_mask = sel; ++ ++ return clk_mux_get_parent(hw, sel, mux->parent_map, 0); ++} ++ ++static struct clk_hw *krait_mux_get_safe_parent(struct clk_hw *hw) ++{ ++ int i; ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ int num_parents = clk_hw_get_num_parents(hw->clk); ++ ++ i = mux->safe_sel; ++ for (i = 0; i < num_parents; i++) ++ if (mux->safe_sel == mux->parent_map[i]) ++ break; ++ ++ return __clk_get_hw(clk_hw_get_parent_by_index(hw->clk, i)); ++} ++ ++static int krait_mux_enable(struct clk_hw *hw) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ ++ __krait_mux_set_sel(mux, mux->en_mask); ++ ++ return 0; ++} ++ ++static void krait_mux_disable(struct clk_hw *hw) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ ++ __krait_mux_set_sel(mux, mux->safe_sel); ++} ++ ++const struct clk_ops krait_mux_clk_ops = { ++ .enable = krait_mux_enable, ++ .disable = krait_mux_disable, ++ .set_parent = krait_mux_set_parent, ++ .get_parent = krait_mux_get_parent, ++ .determine_rate = __clk_mux_determine_rate_closest, ++ .get_safe_parent = krait_mux_get_safe_parent, ++}; ++EXPORT_SYMBOL_GPL(krait_mux_clk_ops); ++ ++/* The divider can divide by 2, 4, 6 and 8. But we only really need div-2. */ ++static long krait_div2_round_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long *parent_rate) ++{ ++ *parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw->clk), rate * 2); ++ return DIV_ROUND_UP(*parent_rate, 2); ++} ++ ++static int krait_div2_set_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long parent_rate) ++{ ++ struct krait_div2_clk *d = to_krait_div2_clk(hw); ++ unsigned long flags; ++ u32 val; ++ u32 mask = BIT(d->width) - 1; ++ ++ if (d->lpl) ++ mask = mask << (d->shift + LPL_SHIFT) | mask << d->shift; ++ ++ spin_lock_irqsave(&krait_clock_reg_lock, flags); ++ val = krait_get_l2_indirect_reg(d->offset); ++ val &= ~mask; ++ krait_set_l2_indirect_reg(d->offset, val); ++ spin_unlock_irqrestore(&krait_clock_reg_lock, flags); ++ ++ return 0; ++} ++ ++static unsigned long ++krait_div2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) ++{ ++ struct krait_div2_clk *d = to_krait_div2_clk(hw); ++ u32 mask = BIT(d->width) - 1; ++ u32 div; ++ ++ div = krait_get_l2_indirect_reg(d->offset); ++ div >>= d->shift; ++ div &= mask; ++ div = (div + 1) * 2; ++ ++ return DIV_ROUND_UP(parent_rate, div); ++} ++ ++const struct clk_ops krait_div2_clk_ops = { ++ .round_rate = krait_div2_round_rate, ++ .set_rate = krait_div2_set_rate, ++ .recalc_rate = krait_div2_recalc_rate, ++}; ++EXPORT_SYMBOL_GPL(krait_div2_clk_ops); +--- /dev/null ++++ b/drivers/clk/qcom/clk-krait.h +@@ -0,0 +1,49 @@ ++/* ++ * Copyright (c) 2013, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#ifndef __QCOM_CLK_KRAIT_H ++#define __QCOM_CLK_KRAIT_H ++ ++#include <linux/clk-provider.h> ++ ++struct krait_mux_clk { ++ unsigned int *parent_map; ++ bool has_safe_parent; ++ u8 safe_sel; ++ u32 offset; ++ u32 mask; ++ u32 shift; ++ u32 en_mask; ++ bool lpl; ++ ++ struct clk_hw hw; ++}; ++ ++#define to_krait_mux_clk(_hw) container_of(_hw, struct krait_mux_clk, hw) ++ ++extern const struct clk_ops krait_mux_clk_ops; ++ ++struct krait_div2_clk { ++ u32 offset; ++ u8 width; ++ u32 shift; ++ bool lpl; ++ ++ struct clk_hw hw; ++}; ++ ++#define to_krait_div2_clk(_hw) container_of(_hw, struct krait_div2_clk, hw) ++ ++extern const struct clk_ops krait_div2_clk_ops; ++ ++#endif diff --git a/target/linux/ipq806x/patches-4.4/141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch b/target/linux/ipq806x/patches-4.4/141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch new file mode 100644 index 0000000..b2ddbc8 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch @@ -0,0 +1,207 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,10/13] clk: qcom: Add KPSS ACC/GCC driver +From: Stephen Boyd <sboyd@codeaurora.org> +X-Patchwork-Id: 6063201 +Message-Id: <1426920332-9340-11-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org> +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org> +Date: Fri, 20 Mar 2015 23:45:29 -0700 + +The ACC and GCC regions present in KPSSv1 contain registers to +control clocks and power to each Krait CPU and L2. For CPUfreq +purposes probe these devices and expose a mux clock that chooses +between PXO and PLL8. + +Cc: <devicetree@vger.kernel.org> +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> + +--- +.../devicetree/bindings/arm/msm/qcom,kpss-acc.txt | 7 ++ + .../devicetree/bindings/arm/msm/qcom,kpss-gcc.txt | 28 +++++++ + drivers/clk/qcom/Kconfig | 8 ++ + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/kpss-xcc.c | 95 ++++++++++++++++++++++ + 5 files changed, 139 insertions(+) + create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt + create mode 100644 drivers/clk/qcom/kpss-xcc.c + +--- a/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt ++++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt +@@ -21,10 +21,17 @@ + the register region. An optional second element specifies + the base address and size of the alias register region. + ++- clock-output-names: ++ Usage: optional ++ Value type: <string> ++ Definition: Name of the output clock. Typically acpuX_aux where X is a ++ CPU number starting at 0. ++ + Example: + + clock-controller@2088000 { + compatible = "qcom,kpss-acc-v2"; + reg = <0x02088000 0x1000>, + <0x02008000 0x1000>; ++ clock-output-names = "acpu0_aux"; + }; +--- /dev/null ++++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt +@@ -0,0 +1,28 @@ ++Krait Processor Sub-system (KPSS) Global Clock Controller (GCC) ++ ++PROPERTIES ++ ++- compatible: ++ Usage: required ++ Value type: <string> ++ Definition: should be one of: ++ "qcom,kpss-gcc" ++ ++- reg: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: base address and size of the register region ++ ++- clock-output-names: ++ Usage: required ++ Value type: <string> ++ Definition: Name of the output clock. Typically acpu_l2_aux indicating ++ an L2 cache auxiliary clock. ++ ++Example: ++ ++ l2cc: clock-controller@2011000 { ++ compatible = "qcom,kpss-gcc"; ++ reg = <0x2011000 0x1000>; ++ clock-output-names = "acpu_l2_aux"; ++ }; +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -115,6 +115,14 @@ + Say Y if you want to support CPU frequency scaling on devices + such as MSM8974, APQ8084, etc. + ++config KPSS_XCC ++ tristate "KPSS Clock Controller" ++ depends on COMMON_CLK_QCOM ++ help ++ Support for the Krait ACC and GCC clock controllers. Say Y ++ if you want to support CPU frequency scaling on devices such ++ as MSM8960, APQ8064, etc. ++ + config KRAIT_CLOCKS + bool + select KRAIT_L2_ACCESSORS +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -9,6 +9,7 @@ + clk-qcom-y += clk-regmap-divider.o + clk-qcom-y += clk-regmap-mux.o + clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o ++obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o + clk-qcom-y += clk-hfpll.o + clk-qcom-y += reset.o + clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o +--- /dev/null ++++ b/drivers/clk/qcom/kpss-xcc.c +@@ -0,0 +1,95 @@ ++/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/platform_device.h> ++#include <linux/err.h> ++#include <linux/io.h> ++#include <linux/of.h> ++#include <linux/of_device.h> ++#include <linux/clk.h> ++#include <linux/clk-provider.h> ++ ++static const char *aux_parents[] = { ++ "pll8_vote", ++ "pxo", ++}; ++ ++static unsigned int aux_parent_map[] = { ++ 3, ++ 0, ++}; ++ ++static const struct of_device_id kpss_xcc_match_table[] = { ++ { .compatible = "qcom,kpss-acc-v1", .data = (void *)1UL }, ++ { .compatible = "qcom,kpss-gcc" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, kpss_xcc_match_table); ++ ++static int kpss_xcc_driver_probe(struct platform_device *pdev) ++{ ++ const struct of_device_id *id; ++ struct clk *clk; ++ struct resource *res; ++ void __iomem *base; ++ const char *name; ++ ++ id = of_match_device(kpss_xcc_match_table, &pdev->dev); ++ if (!id) ++ return -ENODEV; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(base)) ++ return PTR_ERR(base); ++ ++ if (id->data) { ++ if (of_property_read_string_index(pdev->dev.of_node, ++ "clock-output-names", 0, &name)) ++ return -ENODEV; ++ base += 0x14; ++ } else { ++ name = "acpu_l2_aux"; ++ base += 0x28; ++ } ++ ++ clk = clk_register_mux_table(&pdev->dev, name, aux_parents, ++ ARRAY_SIZE(aux_parents), 0, base, 0, 0x3, ++ 0, aux_parent_map, NULL); ++ ++ platform_set_drvdata(pdev, clk); ++ ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static int kpss_xcc_driver_remove(struct platform_device *pdev) ++{ ++ clk_unregister_mux(platform_get_drvdata(pdev)); ++ return 0; ++} ++ ++static struct platform_driver kpss_xcc_driver = { ++ .probe = kpss_xcc_driver_probe, ++ .remove = kpss_xcc_driver_remove, ++ .driver = { ++ .name = "kpss-xcc", ++ .of_match_table = kpss_xcc_match_table, ++ }, ++}; ++module_platform_driver(kpss_xcc_driver); ++ ++MODULE_DESCRIPTION("Krait Processor Sub System (KPSS) Clock Driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:kpss-xcc"); diff --git a/target/linux/ipq806x/patches-4.4/142-clk-qcom-Add-Krait-clock-controller-driver.patch b/target/linux/ipq806x/patches-4.4/142-clk-qcom-Add-Krait-clock-controller-driver.patch new file mode 100644 index 0000000..f0e9467 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/142-clk-qcom-Add-Krait-clock-controller-driver.patch @@ -0,0 +1,435 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,11/13] clk: qcom: Add Krait clock controller driver +From: Stephen Boyd <sboyd@codeaurora.org> +X-Patchwork-Id: 6063121 +Message-Id: <1426920332-9340-12-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org> +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org> +Date: Fri, 20 Mar 2015 23:45:30 -0700 + +The Krait CPU clocks are made up of a primary mux and secondary +mux for each CPU and the L2, controlled via cp15 accessors. For +Kraits within KPSSv1 each secondary mux accepts a different aux +source, but on KPSSv2 each secondary mux accepts the same aux +source. + +Cc: <devicetree@vger.kernel.org> +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> + +--- +.../devicetree/bindings/clock/qcom,krait-cc.txt | 22 ++ + drivers/clk/qcom/Kconfig | 8 + + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/krait-cc.c | 352 +++++++++++++++++++++ + 4 files changed, 383 insertions(+) + create mode 100644 Documentation/devicetree/bindings/clock/qcom,krait-cc.txt + create mode 100644 drivers/clk/qcom/krait-cc.c + +--- /dev/null ++++ b/Documentation/devicetree/bindings/clock/qcom,krait-cc.txt +@@ -0,0 +1,22 @@ ++Krait Clock Controller ++ ++PROPERTIES ++ ++- compatible: ++ Usage: required ++ Value type: <string> ++ Definition: must be one of: ++ "qcom,krait-cc-v1" ++ "qcom,krait-cc-v2" ++ ++- #clock-cells: ++ Usage: required ++ Value type: <u32> ++ Definition: must be 1 ++ ++Example: ++ ++ kraitcc: clock-controller { ++ compatible = "qcom,krait-cc-v1"; ++ #clock-cells = <1>; ++ }; +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -123,6 +123,14 @@ + if you want to support CPU frequency scaling on devices such + as MSM8960, APQ8064, etc. + ++config KRAITCC ++ tristate "Krait Clock Controller" ++ depends on COMMON_CLK_QCOM && ARM ++ select KRAIT_CLOCKS ++ help ++ Support for the Krait CPU clocks on Qualcomm devices. ++ Say Y if you want to support CPU frequency scaling. ++ + config KRAIT_CLOCKS + bool + select KRAIT_L2_ACCESSORS +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -26,3 +26,4 @@ + obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o + obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o + obj-$(CONFIG_QCOM_HFPLL) += hfpll.o ++obj-$(CONFIG_KRAITCC) += krait-cc.o +--- /dev/null ++++ b/drivers/clk/qcom/krait-cc.c +@@ -0,0 +1,352 @@ ++/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/platform_device.h> ++#include <linux/err.h> ++#include <linux/io.h> ++#include <linux/of.h> ++#include <linux/of_device.h> ++#include <linux/clk.h> ++#include <linux/clk-provider.h> ++#include <linux/slab.h> ++ ++#include "clk-krait.h" ++ ++static unsigned int sec_mux_map[] = { ++ 2, ++ 0, ++}; ++ ++static unsigned int pri_mux_map[] = { ++ 1, ++ 2, ++ 0, ++}; ++ ++static int ++krait_add_div(struct device *dev, int id, const char *s, unsigned offset) ++{ ++ struct krait_div2_clk *div; ++ struct clk_init_data init = { ++ .num_parents = 1, ++ .ops = &krait_div2_clk_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }; ++ const char *p_names[1]; ++ struct clk *clk; ++ ++ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL); ++ if (!div) ++ return -ENOMEM; ++ ++ div->width = 2; ++ div->shift = 6; ++ div->lpl = id >= 0; ++ div->offset = offset; ++ div->hw.init = &init; ++ ++ init.name = kasprintf(GFP_KERNEL, "hfpll%s_div", s); ++ if (!init.name) ++ return -ENOMEM; ++ ++ init.parent_names = p_names; ++ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s); ++ if (!p_names[0]) { ++ kfree(init.name); ++ return -ENOMEM; ++ } ++ ++ clk = devm_clk_register(dev, &div->hw); ++ kfree(p_names[0]); ++ kfree(init.name); ++ ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static int ++krait_add_sec_mux(struct device *dev, int id, const char *s, unsigned offset, ++ bool unique_aux) ++{ ++ struct krait_mux_clk *mux; ++ static const char *sec_mux_list[] = { ++ "acpu_aux", ++ "qsb", ++ }; ++ struct clk_init_data init = { ++ .parent_names = sec_mux_list, ++ .num_parents = ARRAY_SIZE(sec_mux_list), ++ .ops = &krait_mux_clk_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }; ++ struct clk *clk; ++ ++ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); ++ if (!mux) ++ return -ENOMEM; ++ ++ mux->offset = offset; ++ mux->lpl = id >= 0; ++ mux->has_safe_parent = true; ++ mux->safe_sel = 2; ++ mux->mask = 0x3; ++ mux->shift = 2; ++ mux->parent_map = sec_mux_map; ++ mux->hw.init = &init; ++ ++ init.name = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s); ++ if (!init.name) ++ return -ENOMEM; ++ ++ if (unique_aux) { ++ sec_mux_list[0] = kasprintf(GFP_KERNEL, "acpu%s_aux", s); ++ if (!sec_mux_list[0]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_aux; ++ } ++ } ++ ++ clk = devm_clk_register(dev, &mux->hw); ++ ++ if (unique_aux) ++ kfree(sec_mux_list[0]); ++err_aux: ++ kfree(init.name); ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static struct clk * ++krait_add_pri_mux(struct device *dev, int id, const char *s, unsigned offset) ++{ ++ struct krait_mux_clk *mux; ++ const char *p_names[3]; ++ struct clk_init_data init = { ++ .parent_names = p_names, ++ .num_parents = ARRAY_SIZE(p_names), ++ .ops = &krait_mux_clk_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }; ++ struct clk *clk; ++ ++ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); ++ if (!mux) ++ return ERR_PTR(-ENOMEM); ++ ++ mux->has_safe_parent = true; ++ mux->safe_sel = 0; ++ mux->mask = 0x3; ++ mux->shift = 0; ++ mux->offset = offset; ++ mux->lpl = id >= 0; ++ mux->parent_map = pri_mux_map; ++ mux->hw.init = &init; ++ ++ init.name = kasprintf(GFP_KERNEL, "krait%s_pri_mux", s); ++ if (!init.name) ++ return ERR_PTR(-ENOMEM); ++ ++ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s); ++ if (!p_names[0]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_p0; ++ } ++ ++ p_names[1] = kasprintf(GFP_KERNEL, "hfpll%s_div", s); ++ if (!p_names[1]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_p1; ++ } ++ ++ p_names[2] = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s); ++ if (!p_names[2]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_p2; ++ } ++ ++ clk = devm_clk_register(dev, &mux->hw); ++ ++ kfree(p_names[2]); ++err_p2: ++ kfree(p_names[1]); ++err_p1: ++ kfree(p_names[0]); ++err_p0: ++ kfree(init.name); ++ return clk; ++} ++ ++/* id < 0 for L2, otherwise id == physical CPU number */ ++static struct clk *krait_add_clks(struct device *dev, int id, bool unique_aux) ++{ ++ int ret; ++ unsigned offset; ++ void *p = NULL; ++ const char *s; ++ struct clk *clk; ++ ++ if (id >= 0) { ++ offset = 0x4501 + (0x1000 * id); ++ s = p = kasprintf(GFP_KERNEL, "%d", id); ++ if (!s) ++ return ERR_PTR(-ENOMEM); ++ } else { ++ offset = 0x500; ++ s = "_l2"; ++ } ++ ++ ret = krait_add_div(dev, id, s, offset); ++ if (ret) { ++ clk = ERR_PTR(ret); ++ goto err; ++ } ++ ++ ret = krait_add_sec_mux(dev, id, s, offset, unique_aux); ++ if (ret) { ++ clk = ERR_PTR(ret); ++ goto err; ++ } ++ ++ clk = krait_add_pri_mux(dev, id, s, offset); ++err: ++ kfree(p); ++ return clk; ++} ++ ++static struct clk *krait_of_get(struct of_phandle_args *clkspec, void *data) ++{ ++ unsigned int idx = clkspec->args[0]; ++ struct clk **clks = data; ++ ++ if (idx >= 5) { ++ pr_err("%s: invalid clock index %d\n", __func__, idx); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ return clks[idx] ? : ERR_PTR(-ENODEV); ++} ++ ++static const struct of_device_id krait_cc_match_table[] = { ++ { .compatible = "qcom,krait-cc-v1", (void *)1UL }, ++ { .compatible = "qcom,krait-cc-v2" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, krait_cc_match_table); ++ ++static int krait_cc_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct of_device_id *id; ++ unsigned long cur_rate, aux_rate; ++ int cpu; ++ struct clk *clk; ++ struct clk **clks; ++ struct clk *l2_pri_mux_clk; ++ ++ id = of_match_device(krait_cc_match_table, dev); ++ if (!id) ++ return -ENODEV; ++ ++ /* Rate is 1 because 0 causes problems for __clk_mux_determine_rate */ ++ clk = clk_register_fixed_rate(dev, "qsb", NULL, CLK_IS_ROOT, 1); ++ if (IS_ERR(clk)) ++ return PTR_ERR(clk); ++ ++ if (!id->data) { ++ clk = clk_register_fixed_factor(dev, "acpu_aux", ++ "gpll0_vote", 0, 1, 2); ++ if (IS_ERR(clk)) ++ return PTR_ERR(clk); ++ } ++ ++ /* Krait configurations have at most 4 CPUs and one L2 */ ++ clks = devm_kcalloc(dev, 5, sizeof(*clks), GFP_KERNEL); ++ if (!clks) ++ return -ENOMEM; ++ ++ for_each_possible_cpu(cpu) { ++ clk = krait_add_clks(dev, cpu, id->data); ++ if (IS_ERR(clk)) ++ return PTR_ERR(clk); ++ clks[cpu] = clk; ++ } ++ ++ l2_pri_mux_clk = krait_add_clks(dev, -1, id->data); ++ if (IS_ERR(l2_pri_mux_clk)) ++ return PTR_ERR(l2_pri_mux_clk); ++ clks[4] = l2_pri_mux_clk; ++ ++ /* ++ * We don't want the CPU or L2 clocks to be turned off at late init ++ * if CPUFREQ or HOTPLUG configs are disabled. So, bump up the ++ * refcount of these clocks. Any cpufreq/hotplug manager can assume ++ * that the clocks have already been prepared and enabled by the time ++ * they take over. ++ */ ++ for_each_online_cpu(cpu) { ++ clk_prepare_enable(l2_pri_mux_clk); ++ WARN(clk_prepare_enable(clks[cpu]), ++ "Unable to turn on CPU%d clock", cpu); ++ } ++ ++ /* ++ * Force reinit of HFPLLs and muxes to overwrite any potential ++ * incorrect configuration of HFPLLs and muxes by the bootloader. ++ * While at it, also make sure the cores are running at known rates ++ * and print the current rate. ++ * ++ * The clocks are set to aux clock rate first to make sure the ++ * secondary mux is not sourcing off of QSB. The rate is then set to ++ * two different rates to force a HFPLL reinit under all ++ * circumstances. ++ */ ++ cur_rate = clk_get_rate(l2_pri_mux_clk); ++ aux_rate = 384000000; ++ if (cur_rate == 1) { ++ pr_info("L2 @ QSB rate. Forcing new rate.\n"); ++ cur_rate = aux_rate; ++ } ++ clk_set_rate(l2_pri_mux_clk, aux_rate); ++ clk_set_rate(l2_pri_mux_clk, 2); ++ clk_set_rate(l2_pri_mux_clk, cur_rate); ++ pr_info("L2 @ %lu KHz\n", clk_get_rate(l2_pri_mux_clk) / 1000); ++ for_each_possible_cpu(cpu) { ++ clk = clks[cpu]; ++ cur_rate = clk_get_rate(clk); ++ if (cur_rate == 1) { ++ pr_info("CPU%d @ QSB rate. Forcing new rate.\n", cpu); ++ cur_rate = aux_rate; ++ } ++ clk_set_rate(clk, aux_rate); ++ clk_set_rate(clk, 2); ++ clk_set_rate(clk, cur_rate); ++ pr_info("CPU%d @ %lu KHz\n", cpu, clk_get_rate(clk) / 1000); ++ } ++ ++ of_clk_add_provider(dev->of_node, krait_of_get, clks); ++ ++ return 0; ++} ++ ++static struct platform_driver krait_cc_driver = { ++ .probe = krait_cc_probe, ++ .driver = { ++ .name = "krait-cc", ++ .of_match_table = krait_cc_match_table, ++ }, ++}; ++module_platform_driver(krait_cc_driver); ++ ++MODULE_DESCRIPTION("Krait CPU Clock Driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:krait-cc"); diff --git a/target/linux/ipq806x/patches-4.4/143-cpufreq-Add-module-to-register-cpufreq-on-Krait-CPUs.patch b/target/linux/ipq806x/patches-4.4/143-cpufreq-Add-module-to-register-cpufreq-on-Krait-CPUs.patch new file mode 100644 index 0000000..d4c43f4 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/143-cpufreq-Add-module-to-register-cpufreq-on-Krait-CPUs.patch @@ -0,0 +1,304 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,12/13] cpufreq: Add module to register cpufreq on Krait CPUs +From: Stephen Boyd <sboyd@codeaurora.org> +X-Patchwork-Id: 6063191 +Message-Id: <1426920332-9340-13-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org> +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org> +Date: Fri, 20 Mar 2015 23:45:31 -0700 + +Register a cpufreq-generic device whenever we detect that a +"qcom,krait" compatible CPU is present in DT. + +Cc: <devicetree@vger.kernel.org> +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> + +--- +.../devicetree/bindings/arm/msm/qcom,pvs.txt | 38 ++++ + drivers/cpufreq/Kconfig.arm | 9 + + drivers/cpufreq/Makefile | 1 + + drivers/cpufreq/qcom-cpufreq.c | 204 +++++++++++++++++++++ + 4 files changed, 252 insertions(+) + create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt + create mode 100644 drivers/cpufreq/qcom-cpufreq.c + +--- /dev/null ++++ b/Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt +@@ -0,0 +1,38 @@ ++Qualcomm Process Voltage Scaling Tables ++ ++The node name is required to be "qcom,pvs". There shall only be one ++such node present in the root of the tree. ++ ++PROPERTIES ++ ++- qcom,pvs-format-a or qcom,pvs-format-b: ++ Usage: required ++ Value type: <empty> ++ Definition: Indicates the format of qcom,speedX-pvsY-bin-vZ properties. ++ If qcom,pvs-format-a is used the table is two columns ++ (frequency and voltage in that order). If qcom,pvs-format-b is used the table is three columns (frequency, voltage, ++ and current in that order). ++ ++- qcom,speedX-pvsY-bin-vZ: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: The PVS table corresponding to the speed bin X, pvs bin Y, ++ and version Z. ++Example: ++ ++ qcom,pvs { ++ qcom,pvs-format-a; ++ qcom,speed0-pvs0-bin-v0 = ++ < 384000000 950000 >, ++ < 486000000 975000 >, ++ < 594000000 1000000 >, ++ < 702000000 1025000 >, ++ < 810000000 1075000 >, ++ < 918000000 1100000 >, ++ < 1026000000 1125000 >, ++ < 1134000000 1175000 >, ++ < 1242000000 1200000 >, ++ < 1350000000 1225000 >, ++ < 1458000000 1237500 >, ++ < 1512000000 1250000 >; ++ }; +--- a/drivers/cpufreq/Kconfig.arm ++++ b/drivers/cpufreq/Kconfig.arm +@@ -95,6 +95,15 @@ + depends on ARCH_OMAP2PLUS + default ARCH_OMAP2PLUS + ++config ARM_QCOM_CPUFREQ ++ tristate "Qualcomm based" ++ depends on ARCH_QCOM ++ select PM_OPP ++ help ++ This adds the CPUFreq driver for Qualcomm SoC based boards. ++ ++ If in doubt, say N. ++ + config ARM_S3C_CPUFREQ + bool + help +--- a/drivers/cpufreq/Makefile ++++ b/drivers/cpufreq/Makefile +@@ -61,6 +61,7 @@ + obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o + obj-$(CONFIG_ARM_PXA2xx_CPUFREQ) += pxa2xx-cpufreq.o + obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o ++obj-$(CONFIG_ARM_QCOM_CPUFREQ) += qcom-cpufreq.o + obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o + obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o + obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o +--- /dev/null ++++ b/drivers/cpufreq/qcom-cpufreq.c +@@ -0,0 +1,204 @@ ++/* Copyright (c) 2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/cpu.h> ++#include <linux/err.h> ++#include <linux/init.h> ++#include <linux/io.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <linux/pm_opp.h> ++#include <linux/slab.h> ++#include <linux/cpufreq-dt.h> ++ ++static void __init get_krait_bin_format_a(int *speed, int *pvs, int *pvs_ver) ++{ ++ void __iomem *base; ++ u32 pte_efuse; ++ ++ *speed = *pvs = *pvs_ver = 0; ++ ++ base = ioremap(0x007000c0, 4); ++ if (!base) { ++ pr_warn("Unable to read efuse data. Defaulting to 0!\n"); ++ return; ++ } ++ ++ pte_efuse = readl_relaxed(base); ++ iounmap(base); ++ ++ *speed = pte_efuse & 0xf; ++ if (*speed == 0xf) ++ *speed = (pte_efuse >> 4) & 0xf; ++ ++ if (*speed == 0xf) { ++ *speed = 0; ++ pr_warn("Speed bin: Defaulting to %d\n", *speed); ++ } else { ++ pr_info("Speed bin: %d\n", *speed); ++ } ++ ++ *pvs = (pte_efuse >> 10) & 0x7; ++ if (*pvs == 0x7) ++ *pvs = (pte_efuse >> 13) & 0x7; ++ ++ if (*pvs == 0x7) { ++ *pvs = 0; ++ pr_warn("PVS bin: Defaulting to %d\n", *pvs); ++ } else { ++ pr_info("PVS bin: %d\n", *pvs); ++ } ++} ++ ++static void __init get_krait_bin_format_b(int *speed, int *pvs, int *pvs_ver) ++{ ++ u32 pte_efuse, redundant_sel; ++ void __iomem *base; ++ ++ *speed = 0; ++ *pvs = 0; ++ *pvs_ver = 0; ++ ++ base = ioremap(0xfc4b80b0, 8); ++ if (!base) { ++ pr_warn("Unable to read efuse data. Defaulting to 0!\n"); ++ return; ++ } ++ ++ pte_efuse = readl_relaxed(base); ++ redundant_sel = (pte_efuse >> 24) & 0x7; ++ *speed = pte_efuse & 0x7; ++ /* 4 bits of PVS are in efuse register bits 31, 8-6. */ ++ *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7); ++ *pvs_ver = (pte_efuse >> 4) & 0x3; ++ ++ switch (redundant_sel) { ++ case 1: ++ *speed = (pte_efuse >> 27) & 0xf; ++ break; ++ case 2: ++ *pvs = (pte_efuse >> 27) & 0xf; ++ break; ++ } ++ ++ /* Check SPEED_BIN_BLOW_STATUS */ ++ if (pte_efuse & BIT(3)) { ++ pr_info("Speed bin: %d\n", *speed); ++ } else { ++ pr_warn("Speed bin not set. Defaulting to 0!\n"); ++ *speed = 0; ++ } ++ ++ /* Check PVS_BLOW_STATUS */ ++ pte_efuse = readl_relaxed(base + 0x4) & BIT(21); ++ if (pte_efuse) { ++ pr_info("PVS bin: %d\n", *pvs); ++ } else { ++ pr_warn("PVS bin not set. Defaulting to 0!\n"); ++ *pvs = 0; ++ } ++ ++ pr_info("PVS version: %d\n", *pvs_ver); ++ iounmap(base); ++} ++ ++static int __init qcom_cpufreq_populate_opps(void) ++{ ++ int len, rows, cols, i, k, speed, pvs, pvs_ver; ++ char table_name[] = "qcom,speedXX-pvsXX-bin-vXX"; ++ struct device_node *np; ++ struct device *dev; ++ int cpu = 0; ++ ++ np = of_find_node_by_name(NULL, "qcom,pvs"); ++ if (!np) ++ return -ENODEV; ++ ++ if (of_property_read_bool(np, "qcom,pvs-format-a")) { ++ get_krait_bin_format_a(&speed, &pvs, &pvs_ver); ++ cols = 2; ++ } else if (of_property_read_bool(np, "qcom,pvs-format-b")) { ++ get_krait_bin_format_b(&speed, &pvs, &pvs_ver); ++ cols = 3; ++ } else { ++ return -ENODEV; ++ } ++ ++ snprintf(table_name, sizeof(table_name), ++ "qcom,speed%d-pvs%d-bin-v%d", speed, pvs, pvs_ver); ++ ++ if (!of_find_property(np, table_name, &len)) ++ return -EINVAL; ++ ++ len /= sizeof(u32); ++ if (len % cols || len == 0) ++ return -EINVAL; ++ ++ rows = len / cols; ++ ++ for (i = 0, k = 0; i < rows; i++) { ++ u32 freq, volt; ++ ++ of_property_read_u32_index(np, table_name, k++, &freq); ++ of_property_read_u32_index(np, table_name, k++, &volt); ++ while (k % cols) ++ k++; /* Skip uA entries if present */ ++ for (cpu = 0; cpu < num_possible_cpus(); cpu++) { ++ dev = get_cpu_device(cpu); ++ if (!dev) ++ return -ENODEV; ++ if (dev_pm_opp_add(dev, freq, volt)) ++ pr_warn("failed to add OPP %u\n", freq); ++ } ++ } ++ ++ return 0; ++} ++ ++static int __init qcom_cpufreq_driver_init(void) ++{ ++ struct cpufreq_dt_platform_data pdata = { .independent_clocks = true }; ++ struct platform_device_info devinfo = { ++ .name = "cpufreq-dt", ++ .data = &pdata, ++ .size_data = sizeof(pdata), ++ }; ++ struct device *cpu_dev; ++ struct device_node *np; ++ int ret; ++ ++ cpu_dev = get_cpu_device(0); ++ if (!cpu_dev) ++ return -ENODEV; ++ ++ np = of_node_get(cpu_dev->of_node); ++ if (!np) ++ return -ENOENT; ++ ++ if (!of_device_is_compatible(np, "qcom,krait")) { ++ of_node_put(np); ++ return -ENODEV; ++ } ++ of_node_put(np); ++ ++ ret = qcom_cpufreq_populate_opps(); ++ if (ret) ++ return ret; ++ ++ return PTR_ERR_OR_ZERO(platform_device_register_full(&devinfo)); ++} ++module_init(qcom_cpufreq_driver_init); ++ ++MODULE_DESCRIPTION("Qualcomm CPUfreq driver"); ++MODULE_LICENSE("GPL v2"); diff --git a/target/linux/ipq806x/patches-4.4/144-ARM-dts-qcom-Add-necessary-DT-data-for-Krait-cpufreq.patch b/target/linux/ipq806x/patches-4.4/144-ARM-dts-qcom-Add-necessary-DT-data-for-Krait-cpufreq.patch new file mode 100644 index 0000000..aaf1401 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/144-ARM-dts-qcom-Add-necessary-DT-data-for-Krait-cpufreq.patch @@ -0,0 +1,100 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -26,6 +26,11 @@ + next-level-cache = <&L2>; + qcom,acc = <&acc0>; + qcom,saw = <&saw0>; ++ clocks = <&kraitcc 0>; ++ clock-names = "cpu"; ++ clock-latency = <100000>; ++ core-supply = <&smb208_s2a>; ++ voltage-tolerance = <5>; + }; + + cpu@1 { +@@ -36,11 +41,24 @@ + next-level-cache = <&L2>; + qcom,acc = <&acc1>; + qcom,saw = <&saw1>; ++ clocks = <&kraitcc 1>; ++ clock-names = "cpu"; ++ clock-latency = <100000>; ++ core-supply = <&smb208_s2b>; + }; + + L2: l2-cache { + compatible = "cache"; + cache-level = <2>; ++ clocks = <&kraitcc 4>; ++ clock-names = "cache"; ++ cache-points-kHz = < ++ /* kHz uV CPU kHz */ ++ 1200000 1150000 1200000 ++ 1000000 1100000 600000 ++ 384000 1100000 384000 ++ >; ++ vdd_dig-supply = <&smb208_s1a>; + }; + }; + +@@ -73,6 +91,46 @@ + }; + }; + ++ kraitcc: clock-controller { ++ compatible = "qcom,krait-cc-v1"; ++ #clock-cells = <1>; ++ }; ++ ++ qcom,pvs { ++ qcom,pvs-format-a; ++ qcom,speed0-pvs0-bin-v0 = ++ < 1400000000 1250000 >, ++ < 1200000000 1200000 >, ++ < 1000000000 1150000 >, ++ < 800000000 1100000 >, ++ < 600000000 1050000 >, ++ < 384000000 1000000 >; ++ ++ qcom,speed0-pvs1-bin-v0 = ++ < 1400000000 1175000 >, ++ < 1200000000 1125000 >, ++ < 1000000000 1075000 >, ++ < 800000000 1025000 >, ++ < 600000000 975000 >, ++ < 384000000 925000 >; ++ ++ qcom,speed0-pvs2-bin-v0 = ++ < 1400000000 1125000 >, ++ < 1200000000 1075000 >, ++ < 1000000000 1025000 >, ++ < 800000000 995000 >, ++ < 600000000 925000 >, ++ < 384000000 875000 >; ++ ++ qcom,speed0-pvs3-bin-v0 = ++ < 1400000000 1050000 >, ++ < 1200000000 1000000 >, ++ < 1000000000 950000 >, ++ < 800000000 900000 >, ++ < 600000000 850000 >, ++ < 384000000 800000 >; ++ }; ++ + soc: soc { + #address-cells = <1>; + #size-cells = <1>; +@@ -215,11 +273,13 @@ + acc0: clock-controller@2088000 { + compatible = "qcom,kpss-acc-v1"; + reg = <0x02088000 0x1000>, <0x02008000 0x1000>; ++ clock-output-names = "acpu0_aux"; + }; + + acc1: clock-controller@2098000 { + compatible = "qcom,kpss-acc-v1"; + reg = <0x02098000 0x1000>, <0x02008000 0x1000>; ++ clock-output-names = "acpu1_aux"; + }; + + l2cc: clock-controller@2011000 { diff --git a/target/linux/ipq806x/patches-4.4/145-cpufreq-Add-a-cpufreq-krait-based-on-cpufre.patch b/target/linux/ipq806x/patches-4.4/145-cpufreq-Add-a-cpufreq-krait-based-on-cpufre.patch new file mode 100644 index 0000000..f33c9e0 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/145-cpufreq-Add-a-cpufreq-krait-based-on-cpufre.patch @@ -0,0 +1,461 @@ +From dd77db4143290689d3a5e1ec61627233d0711b66 Mon Sep 17 00:00:00 2001 +From: Stephen Boyd <sboyd@codeaurora.org> +Date: Fri, 30 May 2014 16:36:11 -0700 +Subject: [PATCH] FROMLIST: cpufreq: Add a cpufreq-krait based on cpufreq-cpu0 + +Krait processors have individual clocks for each CPU that can +scale independently from one another. cpufreq-cpu0 is fairly +close to this, but assumes that there is only one clock for all +CPUs. Add a driver to support the Krait configuration. + +TODO: Merge into cpufreq-cpu0? Or make generic? + +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> + +--- + drivers/cpufreq/Kconfig | 13 +++ + drivers/cpufreq/Makefile | 1 + + drivers/cpufreq/cpufreq-krait.c | 190 ++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 204 insertions(+) + create mode 100644 drivers/cpufreq/cpufreq-krait.c + +--- a/drivers/cpufreq/Kconfig ++++ b/drivers/cpufreq/Kconfig +@@ -198,6 +198,19 @@ + + If in doubt, say N. + ++config GENERIC_CPUFREQ_KRAIT ++ tristate "Krait cpufreq driver" ++ depends on HAVE_CLK && OF ++ # if CPU_THERMAL is on and THERMAL=m, CPU0 cannot be =y: ++ depends on !CPU_THERMAL || THERMAL ++ select PM_OPP ++ help ++ This adds a generic cpufreq driver for CPU0 frequency management. ++ It supports both uniprocessor (UP) and symmetric multiprocessor (SMP) ++ systems which share clock and voltage across all CPUs. ++ ++ If in doubt, say N. ++ + if X86 + source "drivers/cpufreq/Kconfig.x86" + endif +--- a/drivers/cpufreq/Makefile ++++ b/drivers/cpufreq/Makefile +@@ -13,6 +13,7 @@ + obj-$(CONFIG_CPU_FREQ_GOV_COMMON) += cpufreq_governor.o + + obj-$(CONFIG_CPUFREQ_DT) += cpufreq-dt.o ++obj-$(CONFIG_GENERIC_CPUFREQ_KRAIT) += cpufreq-krait.o + + ################################################################################## + # x86 drivers. +--- /dev/null ++++ b/drivers/cpufreq/cpufreq-krait.c +@@ -0,0 +1,390 @@ ++/* ++ * Copyright (C) 2012 Freescale Semiconductor, Inc. ++ * Copyright (c) 2014, The Linux Foundation. All rights reserved. ++ * ++ * The OPP code in function krait_set_target() is reused from ++ * drivers/cpufreq/omap-cpufreq.c ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#include <linux/clk.h> ++#include <linux/cpu.h> ++#include <linux/cpu_cooling.h> ++#include <linux/cpufreq.h> ++#include <linux/cpumask.h> ++#include <linux/err.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/pm_opp.h> ++#include <linux/platform_device.h> ++#include <linux/regulator/consumer.h> ++#include <linux/slab.h> ++#include <linux/thermal.h> ++ ++static unsigned int transition_latency; ++static unsigned int voltage_tolerance; /* in percentage */ ++ ++static struct device *cpu_dev; ++static DEFINE_PER_CPU(struct clk *, krait_cpu_clks); ++static DEFINE_PER_CPU(struct regulator *, krait_supply_core); ++static struct cpufreq_frequency_table *freq_table; ++static struct thermal_cooling_device *cdev; ++ ++struct cache_points { ++ unsigned long cache_freq; ++ unsigned int cache_volt; ++ unsigned long cpu_freq; ++}; ++ ++static struct regulator *krait_l2_reg; ++static struct clk *krait_l2_clk; ++static struct cache_points *krait_l2_points; ++static int nr_krait_l2_points; ++ ++static int krait_parse_cache_points(struct device *dev, ++ struct device_node *of_node) ++{ ++ const struct property *prop; ++ const __be32 *val; ++ int nr, i; ++ ++ prop = of_find_property(of_node, "cache-points-kHz", NULL); ++ if (!prop) ++ return -ENODEV; ++ if (!prop->value) ++ return -ENODATA; ++ ++ /* ++ * Each OPP is a set of tuples consisting of frequency and ++ * cpu-frequency like <freq-kHz volt-uV freq-kHz>. ++ */ ++ nr = prop->length / sizeof(u32); ++ if (nr % 3) { ++ dev_err(dev, "%s: Invalid cache points\n", __func__); ++ return -EINVAL; ++ } ++ nr /= 3; ++ ++ krait_l2_points = devm_kcalloc(dev, nr, sizeof(*krait_l2_points), ++ GFP_KERNEL); ++ if (!krait_l2_points) ++ return -ENOMEM; ++ nr_krait_l2_points = nr; ++ ++ for (i = 0, val = prop->value; i < nr; i++) { ++ unsigned long cache_freq = be32_to_cpup(val++) * 1000; ++ unsigned int cache_volt = be32_to_cpup(val++); ++ unsigned long cpu_freq = be32_to_cpup(val++) * 1000; ++ ++ krait_l2_points[i].cache_freq = cache_freq; ++ krait_l2_points[i].cache_volt = cache_volt; ++ krait_l2_points[i].cpu_freq = cpu_freq; ++ } ++ ++ return 0; ++} ++ ++static int krait_set_target(struct cpufreq_policy *policy, unsigned int index) ++{ ++ struct dev_pm_opp *opp; ++ unsigned long volt = 0, volt_old = 0, tol = 0; ++ unsigned long freq, max_cpu_freq = 0; ++ unsigned int old_freq, new_freq; ++ long freq_Hz, freq_exact; ++ int ret, i; ++ struct clk *cpu_clk; ++ struct regulator *core; ++ unsigned int cpu; ++ ++ cpu_clk = per_cpu(krait_cpu_clks, policy->cpu); ++ ++ freq_Hz = clk_round_rate(cpu_clk, freq_table[index].frequency * 1000); ++ if (freq_Hz <= 0) ++ freq_Hz = freq_table[index].frequency * 1000; ++ ++ freq_exact = freq_Hz; ++ new_freq = freq_Hz / 1000; ++ old_freq = clk_get_rate(cpu_clk) / 1000; ++ ++ core = per_cpu(krait_supply_core, policy->cpu); ++ ++ rcu_read_lock(); ++ opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz); ++ if (IS_ERR(opp)) { ++ rcu_read_unlock(); ++ pr_err("failed to find OPP for %ld\n", freq_Hz); ++ return PTR_ERR(opp); ++ } ++ volt = dev_pm_opp_get_voltage(opp); ++ rcu_read_unlock(); ++ tol = volt * voltage_tolerance / 100; ++ volt_old = regulator_get_voltage(core); ++ ++ pr_debug("%u MHz, %ld mV --> %u MHz, %ld mV\n", ++ old_freq / 1000, volt_old ? volt_old / 1000 : -1, ++ new_freq / 1000, volt ? volt / 1000 : -1); ++ ++ /* scaling up? scale voltage before frequency */ ++ if (new_freq > old_freq) { ++ ret = regulator_set_voltage_tol(core, volt, tol); ++ if (ret) { ++ pr_err("failed to scale voltage up: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ ret = clk_set_rate(cpu_clk, freq_exact); ++ if (ret) { ++ pr_err("failed to set clock rate: %d\n", ret); ++ return ret; ++ } ++ ++ /* scaling down? scale voltage after frequency */ ++ if (new_freq < old_freq) { ++ ret = regulator_set_voltage_tol(core, volt, tol); ++ if (ret) { ++ pr_err("failed to scale voltage down: %d\n", ret); ++ clk_set_rate(cpu_clk, old_freq * 1000); ++ } ++ } ++ ++ for_each_possible_cpu(cpu) { ++ freq = clk_get_rate(per_cpu(krait_cpu_clks, cpu)); ++ max_cpu_freq = max(max_cpu_freq, freq); ++ } ++ ++ for (i = 0; i < nr_krait_l2_points; i++) { ++ if (max_cpu_freq >= krait_l2_points[i].cpu_freq) { ++ if (krait_l2_reg) { ++ ret = regulator_set_voltage_tol(krait_l2_reg, ++ krait_l2_points[i].cache_volt, ++ tol); ++ if (ret) { ++ pr_err("failed to scale l2 voltage: %d\n", ++ ret); ++ } ++ } ++ ret = clk_set_rate(krait_l2_clk, ++ krait_l2_points[i].cache_freq); ++ if (ret) ++ pr_err("failed to scale l2 clk: %d\n", ret); ++ break; ++ } ++ ++ } ++ ++ return ret; ++} ++ ++static int krait_cpufreq_init(struct cpufreq_policy *policy) ++{ ++ int ret; ++ ++ policy->clk = per_cpu(krait_cpu_clks, policy->cpu); ++ ++ ret = cpufreq_table_validate_and_show(policy, freq_table); ++ if (ret) { ++ pr_err("%s: invalid frequency table: %d\n", __func__, ret); ++ return ret; ++ } ++ ++ policy->cpuinfo.transition_latency = transition_latency; ++ ++ return 0; ++} ++ ++static struct cpufreq_driver krait_cpufreq_driver = { ++ .flags = CPUFREQ_STICKY, ++ .verify = cpufreq_generic_frequency_table_verify, ++ .target_index = krait_set_target, ++ .get = cpufreq_generic_get, ++ .init = krait_cpufreq_init, ++ .name = "generic_krait", ++ .attr = cpufreq_generic_attr, ++}; ++ ++static int krait_cpufreq_probe(struct platform_device *pdev) ++{ ++ struct device_node *np, *cache; ++ int ret, i; ++ unsigned int cpu; ++ struct device *dev; ++ struct clk *clk; ++ struct regulator *core; ++ unsigned long freq_Hz, freq, max_cpu_freq = 0; ++ struct dev_pm_opp *opp; ++ unsigned long volt, tol; ++ ++ cpu_dev = get_cpu_device(0); ++ if (!cpu_dev) { ++ pr_err("failed to get krait device\n"); ++ return -ENODEV; ++ } ++ ++ np = of_node_get(cpu_dev->of_node); ++ if (!np) { ++ pr_err("failed to find krait node\n"); ++ return -ENOENT; ++ } ++ ++ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table); ++ if (ret) { ++ pr_err("failed to init cpufreq table: %d\n", ret); ++ goto out_put_node; ++ } ++ ++ of_property_read_u32(np, "voltage-tolerance", &voltage_tolerance); ++ ++ if (of_property_read_u32(np, "clock-latency", &transition_latency)) ++ transition_latency = CPUFREQ_ETERNAL; ++ ++ cache = of_find_next_cache_node(np); ++ if (cache) { ++ struct device_node *vdd; ++ ++ vdd = of_parse_phandle(cache, "vdd_dig-supply", 0); ++ if (vdd) { ++ krait_l2_reg = regulator_get(NULL, vdd->name); ++ if (IS_ERR(krait_l2_reg)) { ++ pr_warn("failed to get l2 vdd_dig supply\n"); ++ krait_l2_reg = NULL; ++ } ++ of_node_put(vdd); ++ } ++ ++ krait_l2_clk = of_clk_get(cache, 0); ++ if (!IS_ERR(krait_l2_clk)) { ++ ret = krait_parse_cache_points(&pdev->dev, cache); ++ if (ret) ++ clk_put(krait_l2_clk); ++ } ++ if (IS_ERR(krait_l2_clk) || ret) ++ krait_l2_clk = NULL; ++ } ++ ++ for_each_possible_cpu(cpu) { ++ dev = get_cpu_device(cpu); ++ if (!dev) { ++ pr_err("failed to get krait device\n"); ++ ret = -ENOENT; ++ goto out_free_table; ++ } ++ per_cpu(krait_cpu_clks, cpu) = clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(clk)) { ++ ret = PTR_ERR(clk); ++ goto out_free_table; ++ } ++ core = devm_regulator_get(dev, "core"); ++ if (IS_ERR(core)) { ++ pr_debug("failed to get core regulator\n"); ++ ret = PTR_ERR(core); ++ goto out_free_table; ++ } ++ per_cpu(krait_supply_core, cpu) = core; ++ ++ freq = freq_Hz = clk_get_rate(clk); ++ ++ rcu_read_lock(); ++ opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz); ++ if (IS_ERR(opp)) { ++ rcu_read_unlock(); ++ pr_err("failed to find OPP for %ld\n", freq_Hz); ++ ret = PTR_ERR(opp); ++ goto out_free_table; ++ } ++ volt = dev_pm_opp_get_voltage(opp); ++ rcu_read_unlock(); ++ ++ tol = volt * voltage_tolerance / 100; ++ ret = regulator_set_voltage_tol(core, volt, tol); ++ if (ret) { ++ pr_err("failed to scale voltage up: %d\n", ret); ++ goto out_free_table; ++ } ++ ret = regulator_enable(core); ++ if (ret) { ++ pr_err("failed to enable regulator: %d\n", ret); ++ goto out_free_table; ++ } ++ max_cpu_freq = max(max_cpu_freq, freq); ++ } ++ ++ for (i = 0; i < nr_krait_l2_points; i++) { ++ if (max_cpu_freq >= krait_l2_points[i].cpu_freq) { ++ if (krait_l2_reg) { ++ ret = regulator_set_voltage_tol(krait_l2_reg, ++ krait_l2_points[i].cache_volt, ++ tol); ++ if (ret) ++ pr_err("failed to scale l2 voltage: %d\n", ++ ret); ++ ret = regulator_enable(krait_l2_reg); ++ if (ret) ++ pr_err("failed to enable l2 voltage: %d\n", ++ ret); ++ } ++ break; ++ } ++ ++ } ++ ++ ret = cpufreq_register_driver(&krait_cpufreq_driver); ++ if (ret) { ++ pr_err("failed register driver: %d\n", ret); ++ goto out_free_table; ++ } ++ of_node_put(np); ++ ++ /* ++ * For now, just loading the cooling device; ++ * thermal DT code takes care of matching them. ++ */ ++ for_each_possible_cpu(cpu) { ++ dev = get_cpu_device(cpu); ++ np = of_node_get(dev->of_node); ++ if (of_find_property(np, "#cooling-cells", NULL)) { ++ cdev = of_cpufreq_cooling_register(np, cpumask_of(cpu)); ++ if (IS_ERR(cdev)) ++ pr_err("running cpufreq without cooling device: %ld\n", ++ PTR_ERR(cdev)); ++ } ++ of_node_put(np); ++ } ++ ++ return 0; ++ ++out_free_table: ++ regulator_put(krait_l2_reg); ++ clk_put(krait_l2_clk); ++ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); ++out_put_node: ++ of_node_put(np); ++ return ret; ++} ++ ++static int krait_cpufreq_remove(struct platform_device *pdev) ++{ ++ cpufreq_cooling_unregister(cdev); ++ cpufreq_unregister_driver(&krait_cpufreq_driver); ++ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); ++ clk_put(krait_l2_clk); ++ regulator_put(krait_l2_reg); ++ ++ return 0; ++} ++ ++static struct platform_driver krait_cpufreq_platdrv = { ++ .driver = { ++ .name = "cpufreq-krait", ++ .owner = THIS_MODULE, ++ }, ++ .probe = krait_cpufreq_probe, ++ .remove = krait_cpufreq_remove, ++}; ++module_platform_driver(krait_cpufreq_platdrv); ++ ++MODULE_DESCRIPTION("Krait CPUfreq driver"); ++MODULE_LICENSE("GPL v2"); +--- a/drivers/cpufreq/qcom-cpufreq.c ++++ b/drivers/cpufreq/qcom-cpufreq.c +@@ -168,11 +168,8 @@ + + static int __init qcom_cpufreq_driver_init(void) + { +- struct cpufreq_dt_platform_data pdata = { .independent_clocks = true }; + struct platform_device_info devinfo = { +- .name = "cpufreq-dt", +- .data = &pdata, +- .size_data = sizeof(pdata), ++ .name = "cpufreq-krait", + }; + struct device *cpu_dev; + struct device_node *np; diff --git a/target/linux/ipq806x/patches-4.4/155-dt-bindings-qcom_adm-Fix-channel-specifiers.patch b/target/linux/ipq806x/patches-4.4/155-dt-bindings-qcom_adm-Fix-channel-specifiers.patch new file mode 100644 index 0000000..4f5c0ef --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/155-dt-bindings-qcom_adm-Fix-channel-specifiers.patch @@ -0,0 +1,76 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v6,1/2] dt/bindings: qcom_adm: Fix channel specifiers +From: Andy Gross <agross@codeaurora.org> +X-Patchwork-Id: 6027361 +Message-Id: <1426571172-9711-2-git-send-email-agross@codeaurora.org> +To: Vinod Koul <vinod.koul@intel.com> +Cc: devicetree@vger.kernel.org, dmaengine@vger.kernel.org, + linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, Kumar Gala <galak@codeaurora.org>, + Bjorn Andersson <bjorn.andersson@sonymobile.com>, + Andy Gross <agross@codeaurora.org> +Date: Tue, 17 Mar 2015 00:46:11 -0500 + +This patch removes the crci information from the dma channel property. At least +one client device requires using more than one CRCI value for a channel. This +does not match the current binding and the crci information needs to be removed. + +Instead, the client device will provide this information via other means. + +Signed-off-by: Andy Gross <agross@codeaurora.org> + +--- +Documentation/devicetree/bindings/dma/qcom_adm.txt | 16 ++++++---------- + 1 file changed, 6 insertions(+), 10 deletions(-) + +--- a/Documentation/devicetree/bindings/dma/qcom_adm.txt ++++ b/Documentation/devicetree/bindings/dma/qcom_adm.txt +@@ -4,8 +4,7 @@ Required properties: + - compatible: must contain "qcom,adm" for IPQ/APQ8064 and MSM8960 + - reg: Address range for DMA registers + - interrupts: Should contain one interrupt shared by all channels +-- #dma-cells: must be <2>. First cell denotes the channel number. Second cell +- denotes CRCI (client rate control interface) flow control assignment. ++- #dma-cells: must be <1>. First cell denotes the channel number. + - clocks: Should contain the core clock and interface clock. + - clock-names: Must contain "core" for the core clock and "iface" for the + interface clock. +@@ -22,7 +21,7 @@ Example: + compatible = "qcom,adm"; + reg = <0x18300000 0x100000>; + interrupts = <0 170 0>; +- #dma-cells = <2>; ++ #dma-cells = <1>; + + clocks = <&gcc ADM0_CLK>, <&gcc ADM0_PBUS_CLK>; + clock-names = "core", "iface"; +@@ -35,15 +34,12 @@ Example: + qcom,ee = <0>; + }; + +-DMA clients must use the format descripted in the dma.txt file, using a three ++DMA clients must use the format descripted in the dma.txt file, using a two + cell specifier for each channel. + +-Each dmas request consists of 3 cells: ++Each dmas request consists of two cells: + 1. phandle pointing to the DMA controller + 2. channel number +- 3. CRCI assignment, if applicable. If no CRCI flow control is required, use 0. +- The CRCI is used for flow control. It identifies the peripheral device that +- is the source/destination for the transferred data. + + Example: + +@@ -56,7 +52,7 @@ Example: + + cs-gpios = <&qcom_pinmux 20 0>; + +- dmas = <&adm_dma 6 9>, +- <&adm_dma 5 10>; ++ dmas = <&adm_dma 6>, ++ <&adm_dma 5>; + dma-names = "rx", "tx"; + }; diff --git a/target/linux/ipq806x/patches-4.4/156-dmaengine-Add-ADM-driver.patch b/target/linux/ipq806x/patches-4.4/156-dmaengine-Add-ADM-driver.patch new file mode 100644 index 0000000..805f28f --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/156-dmaengine-Add-ADM-driver.patch @@ -0,0 +1,964 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v6,2/2] dmaengine: Add ADM driver +From: Andy Gross <agross@codeaurora.org> +X-Patchwork-Id: 6027351 +Message-Id: <1426571172-9711-3-git-send-email-agross@codeaurora.org> +To: Vinod Koul <vinod.koul@intel.com> +Cc: devicetree@vger.kernel.org, dmaengine@vger.kernel.org, + linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, Kumar Gala <galak@codeaurora.org>, + Bjorn Andersson <bjorn.andersson@sonymobile.com>, + Andy Gross <agross@codeaurora.org> +Date: Tue, 17 Mar 2015 00:46:12 -0500 + +Add the DMA engine driver for the QCOM Application Data Mover (ADM) DMA +controller found in the MSM8x60 and IPQ/APQ8064 platforms. + +The ADM supports both memory to memory transactions and memory +to/from peripheral device transactions. The controller also provides flow +control capabilities for transactions to/from peripheral devices. + +The initial release of this driver supports slave transfers to/from peripherals +and also incorporates CRCI (client rate control interface) flow control. + +Signed-off-by: Andy Gross <agross@codeaurora.org> +Reviewed-by: sricharan <sricharan@codeaurora.org> + +--- +drivers/dma/Kconfig | 10 + + drivers/dma/Makefile | 1 + + drivers/dma/qcom_adm.c | 900 ++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 911 insertions(+) + create mode 100644 drivers/dma/qcom_adm.c + +--- a/drivers/dma/Kconfig ++++ b/drivers/dma/Kconfig +@@ -558,4 +558,14 @@ + config DMA_ENGINE_RAID + bool + ++config QCOM_ADM ++ tristate "Qualcomm ADM support" ++ depends on ARCH_QCOM || (COMPILE_TEST && OF && ARM) ++ select DMA_ENGINE ++ select DMA_VIRTUAL_CHANNELS ++ ---help--- ++ Enable support for the Qualcomm ADM DMA controller. This controller ++ provides DMA capabilities for both general purpose and on-chip ++ peripheral devices. ++ + endif +--- /dev/null ++++ b/drivers/dma/qcom_adm.c +@@ -0,0 +1,900 @@ ++/* ++ * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/io.h> ++#include <linux/init.h> ++#include <linux/slab.h> ++#include <linux/module.h> ++#include <linux/interrupt.h> ++#include <linux/dma-mapping.h> ++#include <linux/scatterlist.h> ++#include <linux/device.h> ++#include <linux/platform_device.h> ++#include <linux/of.h> ++#include <linux/of_address.h> ++#include <linux/of_irq.h> ++#include <linux/of_dma.h> ++#include <linux/reset.h> ++#include <linux/clk.h> ++#include <linux/dmaengine.h> ++ ++#include "dmaengine.h" ++#include "virt-dma.h" ++ ++/* ADM registers - calculated from channel number and security domain */ ++#define ADM_CHAN_MULTI 0x4 ++#define ADM_CI_MULTI 0x4 ++#define ADM_CRCI_MULTI 0x4 ++#define ADM_EE_MULTI 0x800 ++#define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * chan) ++#define ADM_EE_OFFS(ee) (ADM_EE_MULTI * ee) ++#define ADM_CHAN_EE_OFFS(chan, ee) (ADM_CHAN_OFFS(chan) + ADM_EE_OFFS(ee)) ++#define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * chan) ++#define ADM_CI_OFFS(ci) (ADM_CHAN_OFF(ci)) ++#define ADM_CH_CMD_PTR(chan, ee) (ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_RSLT(chan, ee) (0x40 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_FLUSH_STATE0(chan, ee) (0x80 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_STATUS_SD(chan, ee) (0x200 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_CONF(chan) (0x240 + ADM_CHAN_OFFS(chan)) ++#define ADM_CH_RSLT_CONF(chan, ee) (0x300 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_SEC_DOMAIN_IRQ_STATUS(ee) (0x380 + ADM_EE_OFFS(ee)) ++#define ADM_CI_CONF(ci) (0x390 + ci * ADM_CI_MULTI) ++#define ADM_GP_CTL 0x3d8 ++#define ADM_CRCI_CTL(crci, ee) (0x400 + crci * ADM_CRCI_MULTI + \ ++ ADM_EE_OFFS(ee)) ++ ++/* channel status */ ++#define ADM_CH_STATUS_VALID BIT(1) ++ ++/* channel result */ ++#define ADM_CH_RSLT_VALID BIT(31) ++#define ADM_CH_RSLT_ERR BIT(3) ++#define ADM_CH_RSLT_FLUSH BIT(2) ++#define ADM_CH_RSLT_TPD BIT(1) ++ ++/* channel conf */ ++#define ADM_CH_CONF_SHADOW_EN BIT(12) ++#define ADM_CH_CONF_MPU_DISABLE BIT(11) ++#define ADM_CH_CONF_PERM_MPU_CONF BIT(9) ++#define ADM_CH_CONF_FORCE_RSLT_EN BIT(7) ++#define ADM_CH_CONF_SEC_DOMAIN(ee) (((ee & 0x3) << 4) | ((ee & 0x4) << 11)) ++ ++/* channel result conf */ ++#define ADM_CH_RSLT_CONF_FLUSH_EN BIT(1) ++#define ADM_CH_RSLT_CONF_IRQ_EN BIT(0) ++ ++/* CRCI CTL */ ++#define ADM_CRCI_CTL_MUX_SEL BIT(18) ++#define ADM_CRCI_CTL_RST BIT(17) ++ ++/* CI configuration */ ++#define ADM_CI_RANGE_END(x) (x << 24) ++#define ADM_CI_RANGE_START(x) (x << 16) ++#define ADM_CI_BURST_4_WORDS BIT(2) ++#define ADM_CI_BURST_8_WORDS BIT(3) ++ ++/* GP CTL */ ++#define ADM_GP_CTL_LP_EN BIT(12) ++#define ADM_GP_CTL_LP_CNT(x) (x << 8) ++ ++/* Command pointer list entry */ ++#define ADM_CPLE_LP BIT(31) ++#define ADM_CPLE_CMD_PTR_LIST BIT(29) ++ ++/* Command list entry */ ++#define ADM_CMD_LC BIT(31) ++#define ADM_CMD_DST_CRCI(n) (((n) & 0xf) << 7) ++#define ADM_CMD_SRC_CRCI(n) (((n) & 0xf) << 3) ++ ++#define ADM_CMD_TYPE_SINGLE 0x0 ++#define ADM_CMD_TYPE_BOX 0x3 ++ ++#define ADM_CRCI_MUX_SEL BIT(4) ++#define ADM_DESC_ALIGN 8 ++#define ADM_MAX_XFER (SZ_64K-1) ++#define ADM_MAX_ROWS (SZ_64K-1) ++#define ADM_MAX_CHANNELS 16 ++ ++struct adm_desc_hw_box { ++ u32 cmd; ++ u32 src_addr; ++ u32 dst_addr; ++ u32 row_len; ++ u32 num_rows; ++ u32 row_offset; ++}; ++ ++struct adm_desc_hw_single { ++ u32 cmd; ++ u32 src_addr; ++ u32 dst_addr; ++ u32 len; ++}; ++ ++struct adm_async_desc { ++ struct virt_dma_desc vd; ++ struct adm_device *adev; ++ ++ size_t length; ++ enum dma_transfer_direction dir; ++ dma_addr_t dma_addr; ++ size_t dma_len; ++ ++ void *cpl; ++ dma_addr_t cp_addr; ++ u32 crci; ++ u32 mux; ++ u32 blk_size; ++}; ++ ++struct adm_chan { ++ struct virt_dma_chan vc; ++ struct adm_device *adev; ++ ++ /* parsed from DT */ ++ u32 id; /* channel id */ ++ ++ struct adm_async_desc *curr_txd; ++ struct dma_slave_config slave; ++ struct list_head node; ++ ++ int error; ++ int initialized; ++}; ++ ++static inline struct adm_chan *to_adm_chan(struct dma_chan *common) ++{ ++ return container_of(common, struct adm_chan, vc.chan); ++} ++ ++struct adm_device { ++ void __iomem *regs; ++ struct device *dev; ++ struct dma_device common; ++ struct device_dma_parameters dma_parms; ++ struct adm_chan *channels; ++ ++ u32 ee; ++ ++ struct clk *core_clk; ++ struct clk *iface_clk; ++ ++ struct reset_control *clk_reset; ++ struct reset_control *c0_reset; ++ struct reset_control *c1_reset; ++ struct reset_control *c2_reset; ++ int irq; ++}; ++ ++/** ++ * adm_free_chan - Frees dma resources associated with the specific channel ++ * ++ * Free all allocated descriptors associated with this channel ++ * ++ */ ++static void adm_free_chan(struct dma_chan *chan) ++{ ++ /* free all queued descriptors */ ++ vchan_free_chan_resources(to_virt_chan(chan)); ++} ++ ++/** ++ * adm_get_blksize - Get block size from burst value ++ * ++ */ ++static int adm_get_blksize(unsigned int burst) ++{ ++ int ret; ++ ++ switch (burst) { ++ case 16: ++ case 32: ++ case 64: ++ case 128: ++ ret = ffs(burst>>4) - 1; ++ break; ++ case 192: ++ ret = 4; ++ break; ++ case 256: ++ ret = 5; ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++/** ++ * adm_process_fc_descriptors - Process descriptors for flow controlled xfers ++ * ++ * @achan: ADM channel ++ * @desc: Descriptor memory pointer ++ * @sg: Scatterlist entry ++ * @crci: CRCI value ++ * @burst: Burst size of transaction ++ * @direction: DMA transfer direction ++ */ ++static void *adm_process_fc_descriptors(struct adm_chan *achan, ++ void *desc, struct scatterlist *sg, u32 crci, u32 burst, ++ enum dma_transfer_direction direction) ++{ ++ struct adm_desc_hw_box *box_desc = NULL; ++ struct adm_desc_hw_single *single_desc; ++ u32 remainder = sg_dma_len(sg); ++ u32 rows, row_offset, crci_cmd; ++ u32 mem_addr = sg_dma_address(sg); ++ u32 *incr_addr = &mem_addr; ++ u32 *src, *dst; ++ ++ if (direction == DMA_DEV_TO_MEM) { ++ crci_cmd = ADM_CMD_SRC_CRCI(crci); ++ row_offset = burst; ++ src = &achan->slave.src_addr; ++ dst = &mem_addr; ++ } else { ++ crci_cmd = ADM_CMD_DST_CRCI(crci); ++ row_offset = burst << 16; ++ src = &mem_addr; ++ dst = &achan->slave.dst_addr; ++ } ++ ++ while (remainder >= burst) { ++ box_desc = desc; ++ box_desc->cmd = ADM_CMD_TYPE_BOX | crci_cmd; ++ box_desc->row_offset = row_offset; ++ box_desc->src_addr = *src; ++ box_desc->dst_addr = *dst; ++ ++ rows = remainder / burst; ++ rows = min_t(u32, rows, ADM_MAX_ROWS); ++ box_desc->num_rows = rows << 16 | rows; ++ box_desc->row_len = burst << 16 | burst; ++ ++ *incr_addr += burst * rows; ++ remainder -= burst * rows; ++ desc += sizeof(*box_desc); ++ } ++ ++ /* if leftover bytes, do one single descriptor */ ++ if (remainder) { ++ single_desc = desc; ++ single_desc->cmd = ADM_CMD_TYPE_SINGLE | crci_cmd; ++ single_desc->len = remainder; ++ single_desc->src_addr = *src; ++ single_desc->dst_addr = *dst; ++ desc += sizeof(*single_desc); ++ ++ if (sg_is_last(sg)) ++ single_desc->cmd |= ADM_CMD_LC; ++ } else { ++ if (box_desc && sg_is_last(sg)) ++ box_desc->cmd |= ADM_CMD_LC; ++ } ++ ++ return desc; ++} ++ ++/** ++ * adm_process_non_fc_descriptors - Process descriptors for non-fc xfers ++ * ++ * @achan: ADM channel ++ * @desc: Descriptor memory pointer ++ * @sg: Scatterlist entry ++ * @direction: DMA transfer direction ++ */ ++static void *adm_process_non_fc_descriptors(struct adm_chan *achan, ++ void *desc, struct scatterlist *sg, ++ enum dma_transfer_direction direction) ++{ ++ struct adm_desc_hw_single *single_desc; ++ u32 remainder = sg_dma_len(sg); ++ u32 mem_addr = sg_dma_address(sg); ++ u32 *incr_addr = &mem_addr; ++ u32 *src, *dst; ++ ++ if (direction == DMA_DEV_TO_MEM) { ++ src = &achan->slave.src_addr; ++ dst = &mem_addr; ++ } else { ++ src = &mem_addr; ++ dst = &achan->slave.dst_addr; ++ } ++ ++ do { ++ single_desc = desc; ++ single_desc->cmd = ADM_CMD_TYPE_SINGLE; ++ single_desc->src_addr = *src; ++ single_desc->dst_addr = *dst; ++ single_desc->len = (remainder > ADM_MAX_XFER) ? ++ ADM_MAX_XFER : remainder; ++ ++ remainder -= single_desc->len; ++ *incr_addr += single_desc->len; ++ desc += sizeof(*single_desc); ++ } while (remainder); ++ ++ /* set last command if this is the end of the whole transaction */ ++ if (sg_is_last(sg)) ++ single_desc->cmd |= ADM_CMD_LC; ++ ++ return desc; ++} ++ ++/** ++ * adm_prep_slave_sg - Prep slave sg transaction ++ * ++ * @chan: dma channel ++ * @sgl: scatter gather list ++ * @sg_len: length of sg ++ * @direction: DMA transfer direction ++ * @flags: DMA flags ++ * @context: transfer context (unused) ++ */ ++static struct dma_async_tx_descriptor *adm_prep_slave_sg(struct dma_chan *chan, ++ struct scatterlist *sgl, unsigned int sg_len, ++ enum dma_transfer_direction direction, unsigned long flags, ++ void *context) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ struct adm_device *adev = achan->adev; ++ struct adm_async_desc *async_desc; ++ struct scatterlist *sg; ++ u32 i, burst; ++ u32 single_count = 0, box_count = 0, crci = 0; ++ void *desc; ++ u32 *cple; ++ int blk_size = 0; ++ ++ if (!is_slave_direction(direction)) { ++ dev_err(adev->dev, "invalid dma direction\n"); ++ return NULL; ++ } ++ ++ /* ++ * get burst value from slave configuration ++ */ ++ burst = (direction == DMA_MEM_TO_DEV) ? ++ achan->slave.dst_maxburst : ++ achan->slave.src_maxburst; ++ ++ /* if using flow control, validate burst and crci values */ ++ if (achan->slave.device_fc) { ++ ++ blk_size = adm_get_blksize(burst); ++ if (blk_size < 0) { ++ dev_err(adev->dev, "invalid burst value: %d\n", ++ burst); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ crci = achan->slave.slave_id & 0xf; ++ if (!crci || achan->slave.slave_id > 0x1f) { ++ dev_err(adev->dev, "invalid crci value\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ } ++ ++ /* iterate through sgs and compute allocation size of structures */ ++ for_each_sg(sgl, sg, sg_len, i) { ++ if (achan->slave.device_fc) { ++ box_count += DIV_ROUND_UP(sg_dma_len(sg) / burst, ++ ADM_MAX_ROWS); ++ if (sg_dma_len(sg) % burst) ++ single_count++; ++ } else { ++ single_count += DIV_ROUND_UP(sg_dma_len(sg), ++ ADM_MAX_XFER); ++ } ++ } ++ ++ async_desc = kzalloc(sizeof(*async_desc), GFP_NOWAIT); ++ if (!async_desc) ++ return ERR_PTR(-ENOMEM); ++ ++ if (crci) ++ async_desc->mux = achan->slave.slave_id & ADM_CRCI_MUX_SEL ? ++ ADM_CRCI_CTL_MUX_SEL : 0; ++ async_desc->crci = crci; ++ async_desc->blk_size = blk_size; ++ async_desc->dma_len = single_count * sizeof(struct adm_desc_hw_single) + ++ box_count * sizeof(struct adm_desc_hw_box) + ++ sizeof(*cple) + 2 * ADM_DESC_ALIGN; ++ ++ async_desc->cpl = dma_alloc_writecombine(adev->dev, async_desc->dma_len, ++ &async_desc->dma_addr, GFP_NOWAIT); ++ ++ if (!async_desc->cpl) { ++ kfree(async_desc); ++ return ERR_PTR(-ENOMEM); ++ } ++ ++ async_desc->adev = adev; ++ ++ /* both command list entry and descriptors must be 8 byte aligned */ ++ cple = PTR_ALIGN(async_desc->cpl, ADM_DESC_ALIGN); ++ desc = PTR_ALIGN(cple + 1, ADM_DESC_ALIGN); ++ ++ /* init cmd list */ ++ *cple = ADM_CPLE_LP; ++ *cple |= (desc - async_desc->cpl + async_desc->dma_addr) >> 3; ++ ++ for_each_sg(sgl, sg, sg_len, i) { ++ async_desc->length += sg_dma_len(sg); ++ ++ if (achan->slave.device_fc) ++ desc = adm_process_fc_descriptors(achan, desc, sg, crci, ++ burst, direction); ++ else ++ desc = adm_process_non_fc_descriptors(achan, desc, sg, ++ direction); ++ } ++ ++ return vchan_tx_prep(&achan->vc, &async_desc->vd, flags); ++} ++ ++/** ++ * adm_terminate_all - terminate all transactions on a channel ++ * @achan: adm dma channel ++ * ++ * Dequeues and frees all transactions, aborts current transaction ++ * No callbacks are done ++ * ++ */ ++static int adm_terminate_all(struct dma_chan *chan) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ struct adm_device *adev = achan->adev; ++ unsigned long flags; ++ LIST_HEAD(head); ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ vchan_get_all_descriptors(&achan->vc, &head); ++ ++ /* send flush command to terminate current transaction */ ++ writel_relaxed(0x0, ++ adev->regs + ADM_CH_FLUSH_STATE0(achan->id, adev->ee)); ++ ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++ ++ vchan_dma_desc_free_list(&achan->vc, &head); ++ ++ return 0; ++} ++ ++static int adm_slave_config(struct dma_chan *chan, struct dma_slave_config *cfg) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ unsigned long flag; ++ ++ spin_lock_irqsave(&achan->vc.lock, flag); ++ memcpy(&achan->slave, cfg, sizeof(struct dma_slave_config)); ++ spin_unlock_irqrestore(&achan->vc.lock, flag); ++ ++ return 0; ++} ++ ++/** ++ * adm_start_dma - start next transaction ++ * @achan - ADM dma channel ++ */ ++static void adm_start_dma(struct adm_chan *achan) ++{ ++ struct virt_dma_desc *vd = vchan_next_desc(&achan->vc); ++ struct adm_device *adev = achan->adev; ++ struct adm_async_desc *async_desc; ++ ++ lockdep_assert_held(&achan->vc.lock); ++ ++ if (!vd) ++ return; ++ ++ list_del(&vd->node); ++ ++ /* write next command list out to the CMD FIFO */ ++ async_desc = container_of(vd, struct adm_async_desc, vd); ++ achan->curr_txd = async_desc; ++ ++ /* reset channel error */ ++ achan->error = 0; ++ ++ if (!achan->initialized) { ++ /* enable interrupts */ ++ writel(ADM_CH_CONF_SHADOW_EN | ++ ADM_CH_CONF_PERM_MPU_CONF | ++ ADM_CH_CONF_MPU_DISABLE | ++ ADM_CH_CONF_SEC_DOMAIN(adev->ee), ++ adev->regs + ADM_CH_CONF(achan->id)); ++ ++ writel(ADM_CH_RSLT_CONF_IRQ_EN | ADM_CH_RSLT_CONF_FLUSH_EN, ++ adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee)); ++ ++ achan->initialized = 1; ++ } ++ ++ /* set the crci block size if this transaction requires CRCI */ ++ if (async_desc->crci) { ++ writel(async_desc->mux | async_desc->blk_size, ++ adev->regs + ADM_CRCI_CTL(async_desc->crci, adev->ee)); ++ } ++ ++ /* make sure IRQ enable doesn't get reordered */ ++ wmb(); ++ ++ /* write next command list out to the CMD FIFO */ ++ writel(ALIGN(async_desc->dma_addr, ADM_DESC_ALIGN) >> 3, ++ adev->regs + ADM_CH_CMD_PTR(achan->id, adev->ee)); ++} ++ ++/** ++ * adm_dma_irq - irq handler for ADM controller ++ * @irq: IRQ of interrupt ++ * @data: callback data ++ * ++ * IRQ handler for the bam controller ++ */ ++static irqreturn_t adm_dma_irq(int irq, void *data) ++{ ++ struct adm_device *adev = data; ++ u32 srcs, i; ++ struct adm_async_desc *async_desc; ++ unsigned long flags; ++ ++ srcs = readl_relaxed(adev->regs + ++ ADM_SEC_DOMAIN_IRQ_STATUS(adev->ee)); ++ ++ for (i = 0; i < ADM_MAX_CHANNELS; i++) { ++ struct adm_chan *achan = &adev->channels[i]; ++ u32 status, result; ++ ++ if (srcs & BIT(i)) { ++ status = readl_relaxed(adev->regs + ++ ADM_CH_STATUS_SD(i, adev->ee)); ++ ++ /* if no result present, skip */ ++ if (!(status & ADM_CH_STATUS_VALID)) ++ continue; ++ ++ result = readl_relaxed(adev->regs + ++ ADM_CH_RSLT(i, adev->ee)); ++ ++ /* no valid results, skip */ ++ if (!(result & ADM_CH_RSLT_VALID)) ++ continue; ++ ++ /* flag error if transaction was flushed or failed */ ++ if (result & (ADM_CH_RSLT_ERR | ADM_CH_RSLT_FLUSH)) ++ achan->error = 1; ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ async_desc = achan->curr_txd; ++ ++ achan->curr_txd = NULL; ++ ++ if (async_desc) { ++ vchan_cookie_complete(&async_desc->vd); ++ ++ /* kick off next DMA */ ++ adm_start_dma(achan); ++ } ++ ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++ } ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++/** ++ * adm_tx_status - returns status of transaction ++ * @chan: dma channel ++ * @cookie: transaction cookie ++ * @txstate: DMA transaction state ++ * ++ * Return status of dma transaction ++ */ ++static enum dma_status adm_tx_status(struct dma_chan *chan, dma_cookie_t cookie, ++ struct dma_tx_state *txstate) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ struct virt_dma_desc *vd; ++ enum dma_status ret; ++ unsigned long flags; ++ size_t residue = 0; ++ ++ ret = dma_cookie_status(chan, cookie, txstate); ++ if (ret == DMA_COMPLETE || !txstate) ++ return ret; ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ ++ vd = vchan_find_desc(&achan->vc, cookie); ++ if (vd) ++ residue = container_of(vd, struct adm_async_desc, vd)->length; ++ ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++ ++ /* ++ * residue is either the full length if it is in the issued list, or 0 ++ * if it is in progress. We have no reliable way of determining ++ * anything inbetween ++ */ ++ dma_set_residue(txstate, residue); ++ ++ if (achan->error) ++ return DMA_ERROR; ++ ++ return ret; ++} ++ ++/** ++ * adm_issue_pending - starts pending transactions ++ * @chan: dma channel ++ * ++ * Issues all pending transactions and starts DMA ++ */ ++static void adm_issue_pending(struct dma_chan *chan) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ ++ if (vchan_issue_pending(&achan->vc) && !achan->curr_txd) ++ adm_start_dma(achan); ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++} ++ ++/** ++ * adm_dma_free_desc - free descriptor memory ++ * @vd: virtual descriptor ++ * ++ */ ++static void adm_dma_free_desc(struct virt_dma_desc *vd) ++{ ++ struct adm_async_desc *async_desc = container_of(vd, ++ struct adm_async_desc, vd); ++ ++ dma_free_writecombine(async_desc->adev->dev, async_desc->dma_len, ++ async_desc->cpl, async_desc->dma_addr); ++ kfree(async_desc); ++} ++ ++static void adm_channel_init(struct adm_device *adev, struct adm_chan *achan, ++ u32 index) ++{ ++ achan->id = index; ++ achan->adev = adev; ++ ++ vchan_init(&achan->vc, &adev->common); ++ achan->vc.desc_free = adm_dma_free_desc; ++} ++ ++static int adm_dma_probe(struct platform_device *pdev) ++{ ++ struct adm_device *adev; ++ struct resource *iores; ++ int ret; ++ u32 i; ++ ++ adev = devm_kzalloc(&pdev->dev, sizeof(*adev), GFP_KERNEL); ++ if (!adev) ++ return -ENOMEM; ++ ++ adev->dev = &pdev->dev; ++ ++ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ adev->regs = devm_ioremap_resource(&pdev->dev, iores); ++ if (IS_ERR(adev->regs)) ++ return PTR_ERR(adev->regs); ++ ++ adev->irq = platform_get_irq(pdev, 0); ++ if (adev->irq < 0) ++ return adev->irq; ++ ++ ret = of_property_read_u32(pdev->dev.of_node, "qcom,ee", &adev->ee); ++ if (ret) { ++ dev_err(adev->dev, "Execution environment unspecified\n"); ++ return ret; ++ } ++ ++ adev->core_clk = devm_clk_get(adev->dev, "core"); ++ if (IS_ERR(adev->core_clk)) ++ return PTR_ERR(adev->core_clk); ++ ++ ret = clk_prepare_enable(adev->core_clk); ++ if (ret) { ++ dev_err(adev->dev, "failed to prepare/enable core clock\n"); ++ return ret; ++ } ++ ++ adev->iface_clk = devm_clk_get(adev->dev, "iface"); ++ if (IS_ERR(adev->iface_clk)) { ++ ret = PTR_ERR(adev->iface_clk); ++ goto err_disable_core_clk; ++ } ++ ++ ret = clk_prepare_enable(adev->iface_clk); ++ if (ret) { ++ dev_err(adev->dev, "failed to prepare/enable iface clock\n"); ++ goto err_disable_core_clk; ++ } ++ ++ adev->clk_reset = devm_reset_control_get(&pdev->dev, "clk"); ++ if (IS_ERR(adev->clk_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 reset\n"); ++ ret = PTR_ERR(adev->clk_reset); ++ goto err_disable_clks; ++ } ++ ++ adev->c0_reset = devm_reset_control_get(&pdev->dev, "c0"); ++ if (IS_ERR(adev->c0_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 C0 reset\n"); ++ ret = PTR_ERR(adev->c0_reset); ++ goto err_disable_clks; ++ } ++ ++ adev->c1_reset = devm_reset_control_get(&pdev->dev, "c1"); ++ if (IS_ERR(adev->c1_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 C1 reset\n"); ++ ret = PTR_ERR(adev->c1_reset); ++ goto err_disable_clks; ++ } ++ ++ adev->c2_reset = devm_reset_control_get(&pdev->dev, "c2"); ++ if (IS_ERR(adev->c2_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 C2 reset\n"); ++ ret = PTR_ERR(adev->c2_reset); ++ goto err_disable_clks; ++ } ++ ++ reset_control_assert(adev->clk_reset); ++ reset_control_assert(adev->c0_reset); ++ reset_control_assert(adev->c1_reset); ++ reset_control_assert(adev->c2_reset); ++ ++ reset_control_deassert(adev->clk_reset); ++ reset_control_deassert(adev->c0_reset); ++ reset_control_deassert(adev->c1_reset); ++ reset_control_deassert(adev->c2_reset); ++ ++ adev->channels = devm_kcalloc(adev->dev, ADM_MAX_CHANNELS, ++ sizeof(*adev->channels), GFP_KERNEL); ++ ++ if (!adev->channels) { ++ ret = -ENOMEM; ++ goto err_disable_clks; ++ } ++ ++ /* allocate and initialize channels */ ++ INIT_LIST_HEAD(&adev->common.channels); ++ ++ for (i = 0; i < ADM_MAX_CHANNELS; i++) ++ adm_channel_init(adev, &adev->channels[i], i); ++ ++ /* reset CRCIs */ ++ for (i = 0; i < 16; i++) ++ writel(ADM_CRCI_CTL_RST, adev->regs + ++ ADM_CRCI_CTL(i, adev->ee)); ++ ++ /* configure client interfaces */ ++ writel(ADM_CI_RANGE_START(0x40) | ADM_CI_RANGE_END(0xb0) | ++ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(0)); ++ writel(ADM_CI_RANGE_START(0x2a) | ADM_CI_RANGE_END(0x2c) | ++ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(1)); ++ writel(ADM_CI_RANGE_START(0x12) | ADM_CI_RANGE_END(0x28) | ++ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(2)); ++ writel(ADM_GP_CTL_LP_EN | ADM_GP_CTL_LP_CNT(0xf), ++ adev->regs + ADM_GP_CTL); ++ ++ ret = devm_request_irq(adev->dev, adev->irq, adm_dma_irq, ++ 0, "adm_dma", adev); ++ if (ret) ++ goto err_disable_clks; ++ ++ platform_set_drvdata(pdev, adev); ++ ++ adev->common.dev = adev->dev; ++ adev->common.dev->dma_parms = &adev->dma_parms; ++ ++ /* set capabilities */ ++ dma_cap_zero(adev->common.cap_mask); ++ dma_cap_set(DMA_SLAVE, adev->common.cap_mask); ++ dma_cap_set(DMA_PRIVATE, adev->common.cap_mask); ++ ++ /* initialize dmaengine apis */ ++ adev->common.directions = BIT(DMA_DEV_TO_MEM | DMA_MEM_TO_DEV); ++ adev->common.residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR; ++ adev->common.src_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES; ++ adev->common.dst_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES; ++ adev->common.device_free_chan_resources = adm_free_chan; ++ adev->common.device_prep_slave_sg = adm_prep_slave_sg; ++ adev->common.device_issue_pending = adm_issue_pending; ++ adev->common.device_tx_status = adm_tx_status; ++ adev->common.device_terminate_all = adm_terminate_all; ++ adev->common.device_config = adm_slave_config; ++ ++ ret = dma_async_device_register(&adev->common); ++ if (ret) { ++ dev_err(adev->dev, "failed to register dma async device\n"); ++ goto err_disable_clks; ++ } ++ ++ ret = of_dma_controller_register(pdev->dev.of_node, ++ of_dma_xlate_by_chan_id, ++ &adev->common); ++ if (ret) ++ goto err_unregister_dma; ++ ++ return 0; ++ ++err_unregister_dma: ++ dma_async_device_unregister(&adev->common); ++err_disable_clks: ++ clk_disable_unprepare(adev->iface_clk); ++err_disable_core_clk: ++ clk_disable_unprepare(adev->core_clk); ++ ++ return ret; ++} ++ ++static int adm_dma_remove(struct platform_device *pdev) ++{ ++ struct adm_device *adev = platform_get_drvdata(pdev); ++ struct adm_chan *achan; ++ u32 i; ++ ++ of_dma_controller_free(pdev->dev.of_node); ++ dma_async_device_unregister(&adev->common); ++ ++ for (i = 0; i < ADM_MAX_CHANNELS; i++) { ++ achan = &adev->channels[i]; ++ ++ /* mask IRQs for this channel/EE pair */ ++ writel(0, adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee)); ++ ++ adm_terminate_all(&adev->channels[i].vc.chan); ++ } ++ ++ devm_free_irq(adev->dev, adev->irq, adev); ++ ++ clk_disable_unprepare(adev->core_clk); ++ clk_disable_unprepare(adev->iface_clk); ++ ++ return 0; ++} ++ ++static const struct of_device_id adm_of_match[] = { ++ { .compatible = "qcom,adm", }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, adm_of_match); ++ ++static struct platform_driver adm_dma_driver = { ++ .probe = adm_dma_probe, ++ .remove = adm_dma_remove, ++ .driver = { ++ .name = "adm-dma-engine", ++ .of_match_table = adm_of_match, ++ }, ++}; ++ ++module_platform_driver(adm_dma_driver); ++ ++MODULE_AUTHOR("Andy Gross <agross@codeaurora.org>"); ++MODULE_DESCRIPTION("QCOM ADM DMA engine driver"); ++MODULE_LICENSE("GPL v2"); +--- a/drivers/dma/Makefile ++++ b/drivers/dma/Makefile +@@ -65,5 +65,6 @@ + obj-$(CONFIG_TI_EDMA) += edma.o + obj-$(CONFIG_XGENE_DMA) += xgene-dma.o + obj-$(CONFIG_ZX_DMA) += zx296702_dma.o ++obj-$(CONFIG_QCOM_ADM) += qcom_adm.o + + obj-y += xilinx/ diff --git a/target/linux/ipq806x/patches-4.4/157-ARM-DT-ipq8064-Add-ADM-device-node.patch b/target/linux/ipq806x/patches-4.4/157-ARM-DT-ipq8064-Add-ADM-device-node.patch new file mode 100644 index 0000000..b8abd0a --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/157-ARM-DT-ipq8064-Add-ADM-device-node.patch @@ -0,0 +1,42 @@ +From 1fb18acab2d71e7e4efd9c10492edb1baf84dcc0 Mon Sep 17 00:00:00 2001 +From: Andy Gross <agross@codeaurora.org> +Date: Wed, 20 May 2015 15:41:07 +0530 +Subject: [PATCH] ARM: DT: ipq8064: Add ADM device node + +This patch adds support for the ADM DMA on the IPQ8064 SOC + +Signed-off-by: Andy Gross <agross@codeaurora.org> +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 4 ++++ + arch/arm/boot/dts/qcom-ipq8064.dtsi | 21 +++++++++++++++++++++ + 2 files changed, 25 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -727,6 +727,26 @@ + + status = "disabled"; + }; ++ ++ adm_dma: dma@18300000 { ++ compatible = "qcom,adm"; ++ reg = <0x18300000 0x100000>; ++ interrupts = <0 170 0>; ++ #dma-cells = <1>; ++ ++ clocks = <&gcc ADM0_CLK>, <&gcc ADM0_PBUS_CLK>; ++ clock-names = "core", "iface"; ++ ++ resets = <&gcc ADM0_RESET>, ++ <&gcc ADM0_PBUS_RESET>, ++ <&gcc ADM0_C0_RESET>, ++ <&gcc ADM0_C1_RESET>, ++ <&gcc ADM0_C2_RESET>; ++ reset-names = "clk", "pbus", "c0", "c1", "c2"; ++ qcom,ee = <0>; ++ ++ status = "disabled"; ++ }; + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-4.4/161-mtd-nand-Create-a-BBT-flag-to-access-bad-block-markers-in-raw-mode.patch b/target/linux/ipq806x/patches-4.4/161-mtd-nand-Create-a-BBT-flag-to-access-bad-block-markers-in-raw-mode.patch new file mode 100644 index 0000000..21fe405 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/161-mtd-nand-Create-a-BBT-flag-to-access-bad-block-markers-in-raw-mode.patch @@ -0,0 +1,83 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3, + 1/5] mtd: nand: Create a BBT flag to access bad block markers in raw + mode +From: Archit Taneja <architt@codeaurora.org> +X-Patchwork-Id: 6927081 +Message-Id: <1438578498-32254-2-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja <architt@codeaurora.org> +Date: Mon, 3 Aug 2015 10:38:14 +0530 + +Some controllers can access the factory bad block marker from OOB only +when they read it in raw mode. When ECC is enabled, these controllers +discard reading/writing bad block markers, preventing access to them +altogether. + +The bbt driver assumes MTD_OPS_PLACE_OOB when scanning for bad blocks. +This results in the nand driver's ecc->read_oob() op to be called, which +works with ECC enabled. + +Create a new BBT option flag that tells nand_bbt to force the mode to +MTD_OPS_RAW. This would result in the correct op being called for the +underlying nand controller driver. + +Reviewed-by: Andy Gross <agross@codeaurora.org> +Signed-off-by: Archit Taneja <architt@codeaurora.org> + +--- +drivers/mtd/nand/nand_base.c | 6 +++++- + drivers/mtd/nand/nand_bbt.c | 6 +++++- + include/linux/mtd/bbm.h | 7 +++++++ + 3 files changed, 17 insertions(+), 2 deletions(-) + +--- a/drivers/mtd/nand/nand_base.c ++++ b/drivers/mtd/nand/nand_base.c +@@ -394,7 +394,11 @@ + } else { + ops.len = ops.ooblen = 1; + } +- ops.mode = MTD_OPS_PLACE_OOB; ++ ++ if (unlikely(chip->bbt_options & NAND_BBT_ACCESS_BBM_RAW)) ++ ops.mode = MTD_OPS_RAW; ++ else ++ ops.mode = MTD_OPS_PLACE_OOB; + + /* Write to first/last page(s) if necessary */ + if (chip->bbt_options & NAND_BBT_SCANLASTPAGE) +--- a/drivers/mtd/nand/nand_bbt.c ++++ b/drivers/mtd/nand/nand_bbt.c +@@ -420,7 +420,11 @@ + ops.oobbuf = buf; + ops.ooboffs = 0; + ops.datbuf = NULL; +- ops.mode = MTD_OPS_PLACE_OOB; ++ ++ if (unlikely(bd->options & NAND_BBT_ACCESS_BBM_RAW)) ++ ops.mode = MTD_OPS_RAW; ++ else ++ ops.mode = MTD_OPS_PLACE_OOB; + + for (j = 0; j < numpages; j++) { + /* +--- a/include/linux/mtd/bbm.h ++++ b/include/linux/mtd/bbm.h +@@ -116,6 +116,12 @@ + #define NAND_BBT_NO_OOB_BBM 0x00080000 + + /* ++ * Force MTD_OPS_RAW mode when trying to access bad block markes from OOB. To ++ * be used by controllers which can access BBM only when ECC is disabled, i.e, ++ * when in RAW access mode ++ */ ++#define NAND_BBT_ACCESS_BBM_RAW 0x00100000 ++/* + * Flag set by nand_create_default_bbt_descr(), marking that the nand_bbt_descr + * was allocated dynamicaly and must be freed in nand_release(). Has no meaning + * in nand_chip.bbt_options. diff --git a/target/linux/ipq806x/patches-4.4/162-mtd-nand-Qualcomm-NAND-controller-driver.patch b/target/linux/ipq806x/patches-4.4/162-mtd-nand-Qualcomm-NAND-controller-driver.patch new file mode 100644 index 0000000..19e5f91 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/162-mtd-nand-Qualcomm-NAND-controller-driver.patch @@ -0,0 +1,2024 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,2/5] mtd: nand: Qualcomm NAND controller driver +From: Archit Taneja <architt@codeaurora.org> +X-Patchwork-Id: 6927101 +Message-Id: <1438578498-32254-3-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja <architt@codeaurora.org> +Date: Mon, 3 Aug 2015 10:38:15 +0530 + +The Qualcomm NAND controller is found in SoCs like IPQ806x, MSM7xx, +MDM9x15 series. + +It exists as a sub block inside the IPs EBI2 (External Bus Interface 2) +and QPIC (Qualcomm Parallel Interface Controller). These IPs provide a +broader interface for external slow peripheral devices such as LCD and +NAND/NOR flash memory or SRAM like interfaces. + +We add support for the NAND controller found within EBI2. For the SoCs +of our interest, we only use the NAND controller within EBI2. Therefore, +it's safe for us to assume that the NAND controller is a standalone block +within the SoC. + +The controller supports 512B, 2kB, 4kB and 8kB page 8-bit and 16-bit NAND +flash devices. It contains a HW ECC block that supports BCH ECC (4, 8 and +16 bit correction/step) and RS ECC(4 bit correction/step) that covers main +and spare data. The controller contains an internal 512 byte page buffer +to which we read/write via DMA. The EBI2 type NAND controller uses ADM DMA +for register read/write and data transfers. The controller performs page +reads and writes at a codeword/step level of 512 bytes. It can support up +to 2 external chips of different configurations. + +The driver prepares register read and write configuration descriptors for +each codeword, followed by data descriptors to read or write data from the +controller's internal buffer. It uses a single ADM DMA channel that we get +via dmaengine API. The controller requires 2 ADM CRCIs for command and +data flow control. These are passed via DT. + +The ecc layout used by the controller is syndrome like, but we can't use +the standard syndrome ecc ops because of several reasons. First, the amount +of data bytes covered by ecc isn't same in each step. Second, writing to +free oob space requires us writing to the entire step in which the oob +lies. This forces us to create our own ecc ops. + +One more difference is how the controller accesses the bad block marker. +The controller ignores reading the marker when ECC is enabled. ECC needs +to be explicity disabled to read or write to the bad block marker. For +this reason, we use the newly created flag NAND_BBT_ACCESS_BBM_RAW to +read the factory provided bad block markers. + +v3: +- Refactor dma functions for maximum reuse +- Use dma_slave_confing on stack +- optimize and clean upempty_page_fixup using memchr_inv +- ensure portability with dma register reads using le32_* funcs +- use NAND_USE_BOUNCE_BUFFER instead of doing it ourselves +- fix handling of return values of dmaengine funcs +- constify wherever possible +- Remove dependency on ADM DMA in Kconfig +- Misc fixes and clean ups + +v2: +- Use new BBT flag that allows us to read BBM in raw mode +- reduce memcpy-s in the driver +- some refactor and clean ups because of above changes + +Reviewed-by: Andy Gross <agross@codeaurora.org> +Signed-off-by: Archit Taneja <architt@codeaurora.org> + +--- +drivers/mtd/nand/Kconfig | 7 + + drivers/mtd/nand/Makefile | 1 + + drivers/mtd/nand/qcom_nandc.c | 1913 +++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 1921 insertions(+) + create mode 100644 drivers/mtd/nand/qcom_nandc.c + +--- a/drivers/mtd/nand/Kconfig ++++ b/drivers/mtd/nand/Kconfig +@@ -546,4 +546,11 @@ + help + Enables support for NAND controller on Hisilicon SoC Hip04. + ++config MTD_NAND_QCOM ++ tristate "Support for NAND on QCOM SoCs" ++ depends on ARCH_QCOM ++ help ++ Enables support for NAND flash chips on SoCs containing the EBI2 NAND ++ controller. This controller is found on IPQ806x SoC. ++ + endif # MTD_NAND +--- /dev/null ++++ b/drivers/mtd/nand/qcom_nandc.c +@@ -0,0 +1,1918 @@ ++/* ++ * Copyright (c) 2015, The Linux Foundation. All rights reserved. ++ * ++ * This software is licensed under the terms of the GNU General Public ++ * License version 2, as published by the Free Software Foundation, and ++ * may be copied, distributed, and modified under those terms. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/clk.h> ++#include <linux/slab.h> ++#include <linux/bitops.h> ++#include <linux/dma-mapping.h> ++#include <linux/dmaengine.h> ++#include <linux/module.h> ++#include <linux/mtd/nand.h> ++#include <linux/mtd/partitions.h> ++#include <linux/of.h> ++#include <linux/of_device.h> ++#include <linux/of_mtd.h> ++#include <linux/delay.h> ++ ++/* NANDc reg offsets */ ++#define NAND_FLASH_CMD 0x00 ++#define NAND_ADDR0 0x04 ++#define NAND_ADDR1 0x08 ++#define NAND_FLASH_CHIP_SELECT 0x0c ++#define NAND_EXEC_CMD 0x10 ++#define NAND_FLASH_STATUS 0x14 ++#define NAND_BUFFER_STATUS 0x18 ++#define NAND_DEV0_CFG0 0x20 ++#define NAND_DEV0_CFG1 0x24 ++#define NAND_DEV0_ECC_CFG 0x28 ++#define NAND_DEV1_ECC_CFG 0x2c ++#define NAND_DEV1_CFG0 0x30 ++#define NAND_DEV1_CFG1 0x34 ++#define NAND_READ_ID 0x40 ++#define NAND_READ_STATUS 0x44 ++#define NAND_DEV_CMD0 0xa0 ++#define NAND_DEV_CMD1 0xa4 ++#define NAND_DEV_CMD2 0xa8 ++#define NAND_DEV_CMD_VLD 0xac ++#define SFLASHC_BURST_CFG 0xe0 ++#define NAND_ERASED_CW_DETECT_CFG 0xe8 ++#define NAND_ERASED_CW_DETECT_STATUS 0xec ++#define NAND_EBI2_ECC_BUF_CFG 0xf0 ++#define FLASH_BUF_ACC 0x100 ++ ++#define NAND_CTRL 0xf00 ++#define NAND_VERSION 0xf08 ++#define NAND_READ_LOCATION_0 0xf20 ++#define NAND_READ_LOCATION_1 0xf24 ++ ++/* dummy register offsets, used by write_reg_dma */ ++#define NAND_DEV_CMD1_RESTORE 0xdead ++#define NAND_DEV_CMD_VLD_RESTORE 0xbeef ++ ++/* NAND_FLASH_CMD bits */ ++#define PAGE_ACC BIT(4) ++#define LAST_PAGE BIT(5) ++ ++/* NAND_FLASH_CHIP_SELECT bits */ ++#define NAND_DEV_SEL 0 ++#define DM_EN BIT(2) ++ ++/* NAND_FLASH_STATUS bits */ ++#define FS_OP_ERR BIT(4) ++#define FS_READY_BSY_N BIT(5) ++#define FS_MPU_ERR BIT(8) ++#define FS_DEVICE_STS_ERR BIT(16) ++#define FS_DEVICE_WP BIT(23) ++ ++/* NAND_BUFFER_STATUS bits */ ++#define BS_UNCORRECTABLE_BIT BIT(8) ++#define BS_CORRECTABLE_ERR_MSK 0x1f ++ ++/* NAND_DEVn_CFG0 bits */ ++#define DISABLE_STATUS_AFTER_WRITE 4 ++#define CW_PER_PAGE 6 ++#define UD_SIZE_BYTES 9 ++#define ECC_PARITY_SIZE_BYTES_RS 19 ++#define SPARE_SIZE_BYTES 23 ++#define NUM_ADDR_CYCLES 27 ++#define STATUS_BFR_READ 30 ++#define SET_RD_MODE_AFTER_STATUS 31 ++ ++/* NAND_DEVn_CFG0 bits */ ++#define DEV0_CFG1_ECC_DISABLE 0 ++#define WIDE_FLASH 1 ++#define NAND_RECOVERY_CYCLES 2 ++#define CS_ACTIVE_BSY 5 ++#define BAD_BLOCK_BYTE_NUM 6 ++#define BAD_BLOCK_IN_SPARE_AREA 16 ++#define WR_RD_BSY_GAP 17 ++#define ENABLE_BCH_ECC 27 ++ ++/* NAND_DEV0_ECC_CFG bits */ ++#define ECC_CFG_ECC_DISABLE 0 ++#define ECC_SW_RESET 1 ++#define ECC_MODE 4 ++#define ECC_PARITY_SIZE_BYTES_BCH 8 ++#define ECC_NUM_DATA_BYTES 16 ++#define ECC_FORCE_CLK_OPEN 30 ++ ++/* NAND_DEV_CMD1 bits */ ++#define READ_ADDR 0 ++ ++/* NAND_DEV_CMD_VLD bits */ ++#define READ_START_VLD 0 ++ ++/* NAND_EBI2_ECC_BUF_CFG bits */ ++#define NUM_STEPS 0 ++ ++/* NAND_ERASED_CW_DETECT_CFG bits */ ++#define ERASED_CW_ECC_MASK 1 ++#define AUTO_DETECT_RES 0 ++#define MASK_ECC (1 << ERASED_CW_ECC_MASK) ++#define RESET_ERASED_DET (1 << AUTO_DETECT_RES) ++#define ACTIVE_ERASED_DET (0 << AUTO_DETECT_RES) ++#define CLR_ERASED_PAGE_DET (RESET_ERASED_DET | MASK_ECC) ++#define SET_ERASED_PAGE_DET (ACTIVE_ERASED_DET | MASK_ECC) ++ ++/* NAND_ERASED_CW_DETECT_STATUS bits */ ++#define PAGE_ALL_ERASED BIT(7) ++#define CODEWORD_ALL_ERASED BIT(6) ++#define PAGE_ERASED BIT(5) ++#define CODEWORD_ERASED BIT(4) ++#define ERASED_PAGE (PAGE_ALL_ERASED | PAGE_ERASED) ++#define ERASED_CW (CODEWORD_ALL_ERASED | CODEWORD_ERASED) ++ ++/* Version Mask */ ++#define NAND_VERSION_MAJOR_MASK 0xf0000000 ++#define NAND_VERSION_MAJOR_SHIFT 28 ++#define NAND_VERSION_MINOR_MASK 0x0fff0000 ++#define NAND_VERSION_MINOR_SHIFT 16 ++ ++/* NAND OP_CMDs */ ++#define PAGE_READ 0x2 ++#define PAGE_READ_WITH_ECC 0x3 ++#define PAGE_READ_WITH_ECC_SPARE 0x4 ++#define PROGRAM_PAGE 0x6 ++#define PAGE_PROGRAM_WITH_ECC 0x7 ++#define PROGRAM_PAGE_SPARE 0x9 ++#define BLOCK_ERASE 0xa ++#define FETCH_ID 0xb ++#define RESET_DEVICE 0xd ++ ++/* ++ * the NAND controller performs reads/writes with ECC in 516 byte chunks. ++ * the driver calls the chunks 'step' or 'codeword' interchangeably ++ */ ++#define NANDC_STEP_SIZE 512 ++ ++/* ++ * the largest page size we support is 8K, this will have 16 steps/codewords ++ * of 512 bytes each ++ */ ++#define MAX_NUM_STEPS (SZ_8K / NANDC_STEP_SIZE) ++ ++/* we read at most 3 registers per codeword scan */ ++#define MAX_REG_RD (3 * MAX_NUM_STEPS) ++ ++/* ECC modes */ ++#define ECC_NONE BIT(0) ++#define ECC_RS_4BIT BIT(1) ++#define ECC_BCH_4BIT BIT(2) ++#define ECC_BCH_8BIT BIT(3) ++ ++struct desc_info { ++ struct list_head list; ++ ++ enum dma_transfer_direction dir; ++ struct scatterlist sgl; ++ struct dma_async_tx_descriptor *dma_desc; ++}; ++ ++/* ++ * holds the current register values that we want to write. acts as a contiguous ++ * chunk of memory which we use to write the controller registers through DMA. ++ */ ++struct nandc_regs { ++ u32 cmd; ++ u32 addr0; ++ u32 addr1; ++ u32 chip_sel; ++ u32 exec; ++ ++ u32 cfg0; ++ u32 cfg1; ++ u32 ecc_bch_cfg; ++ ++ u32 clrflashstatus; ++ u32 clrreadstatus; ++ ++ u32 cmd1; ++ u32 vld; ++ ++ u32 orig_cmd1; ++ u32 orig_vld; ++ ++ u32 ecc_buf_cfg; ++}; ++ ++/* ++ * @cmd_crci: ADM DMA CRCI for command flow control ++ * @data_crci: ADM DMA CRCI for data flow control ++ * @list: DMA descriptor list (list of desc_infos) ++ * @dma_done: completion param to denote end of last ++ * descriptor in the list ++ * @data_buffer: our local DMA buffer for page read/writes, ++ * used when we can't use the buffer provided ++ * by upper layers directly ++ * @buf_size/count/start: markers for chip->read_buf/write_buf functions ++ * @reg_read_buf: buffer for reading register data via DMA ++ * @reg_read_pos: marker for data read in reg_read_buf ++ * @cfg0, cfg1, cfg0_raw..: NANDc register configurations needed for ++ * ecc/non-ecc mode for the current nand flash ++ * device ++ * @regs: a contiguous chunk of memory for DMA register ++ * writes ++ * @ecc_strength: 4 bit or 8 bit ecc, received via DT ++ * @bus_width: 8 bit or 16 bit NAND bus width, received via DT ++ * @ecc_modes: supported ECC modes by the current controller, ++ * initialized via DT match data ++ * @cw_size: the number of bytes in a single step/codeword ++ * of a page, consisting of all data, ecc, spare ++ * and reserved bytes ++ * @cw_data: the number of bytes within a codeword protected ++ * by ECC ++ * @bch_enabled: flag to tell whether BCH or RS ECC mode is used ++ * @status: value to be returned if NAND_CMD_STATUS command ++ * is executed ++ */ ++struct qcom_nandc_data { ++ struct platform_device *pdev; ++ struct device *dev; ++ ++ void __iomem *base; ++ struct resource *res; ++ ++ struct clk *core_clk; ++ struct clk *aon_clk; ++ ++ /* DMA stuff */ ++ struct dma_chan *chan; ++ struct dma_slave_config slave_conf; ++ unsigned int cmd_crci; ++ unsigned int data_crci; ++ struct list_head list; ++ struct completion dma_done; ++ ++ /* MTD stuff */ ++ struct nand_chip chip; ++ struct mtd_info mtd; ++ ++ /* local data buffer and markers */ ++ u8 *data_buffer; ++ int buf_size; ++ int buf_count; ++ int buf_start; ++ ++ /* local buffer to read back registers */ ++ u32 *reg_read_buf; ++ int reg_read_pos; ++ ++ /* required configs */ ++ u32 cfg0, cfg1; ++ u32 cfg0_raw, cfg1_raw; ++ u32 ecc_buf_cfg; ++ u32 ecc_bch_cfg; ++ u32 clrflashstatus; ++ u32 clrreadstatus; ++ u32 sflashc_burst_cfg; ++ u32 cmd1, vld; ++ ++ /* register state */ ++ struct nandc_regs *regs; ++ ++ /* things we get from DT */ ++ int ecc_strength; ++ int bus_width; ++ ++ u32 ecc_modes; ++ ++ /* misc params */ ++ int cw_size; ++ int cw_data; ++ bool use_ecc; ++ bool bch_enabled; ++ u8 status; ++ int last_command; ++}; ++ ++static inline u32 nandc_read(struct qcom_nandc_data *this, int offset) ++{ ++ return ioread32(this->base + offset); ++} ++ ++static inline void nandc_write(struct qcom_nandc_data *this, int offset, ++ u32 val) ++{ ++ iowrite32(val, this->base + offset); ++} ++ ++/* helper to configure address register values */ ++static void set_address(struct qcom_nandc_data *this, u16 column, int page) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nandc_regs *regs = this->regs; ++ ++ if (chip->options & NAND_BUSWIDTH_16) ++ column >>= 1; ++ ++ regs->addr0 = page << 16 | column; ++ regs->addr1 = page >> 16 & 0xff; ++} ++ ++/* ++ * update_rw_regs: set up read/write register values, these will be ++ * written to the NAND controller registers via DMA ++ * ++ * @num_cw: number of steps for the read/write operation ++ * @read: read or write operation ++ */ ++static void update_rw_regs(struct qcom_nandc_data *this, int num_cw, bool read) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ if (read) { ++ if (this->use_ecc) ++ regs->cmd = PAGE_READ_WITH_ECC | PAGE_ACC | LAST_PAGE; ++ else ++ regs->cmd = PAGE_READ | PAGE_ACC | LAST_PAGE; ++ } else { ++ regs->cmd = PROGRAM_PAGE | PAGE_ACC | LAST_PAGE; ++ } ++ ++ if (this->use_ecc) { ++ regs->cfg0 = (this->cfg0 & ~(7U << CW_PER_PAGE)) | ++ (num_cw - 1) << CW_PER_PAGE; ++ ++ regs->cfg1 = this->cfg1; ++ regs->ecc_bch_cfg = this->ecc_bch_cfg; ++ } else { ++ regs->cfg0 = (this->cfg0_raw & ~(7U << CW_PER_PAGE)) | ++ (num_cw - 1) << CW_PER_PAGE; ++ ++ regs->cfg1 = this->cfg1_raw; ++ regs->ecc_bch_cfg = 1 << ECC_CFG_ECC_DISABLE; ++ } ++ ++ regs->ecc_buf_cfg = this->ecc_buf_cfg; ++ regs->clrflashstatus = this->clrflashstatus; ++ regs->clrreadstatus = this->clrreadstatus; ++ regs->exec = 1; ++} ++ ++static int prep_dma_desc(struct qcom_nandc_data *this, bool read, int reg_off, ++ const void *vaddr, int size, bool flow_control) ++{ ++ struct desc_info *desc; ++ struct dma_async_tx_descriptor *dma_desc; ++ struct scatterlist *sgl; ++ struct dma_slave_config slave_conf; ++ int r; ++ ++ desc = kzalloc(sizeof(*desc), GFP_KERNEL); ++ if (!desc) ++ return -ENOMEM; ++ ++ list_add_tail(&desc->list, &this->list); ++ ++ sgl = &desc->sgl; ++ ++ sg_init_one(sgl, vaddr, size); ++ ++ desc->dir = read ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; ++ ++ r = dma_map_sg(this->dev, sgl, 1, desc->dir); ++ if (r == 0) { ++ r = -ENOMEM; ++ goto err; ++ } ++ ++ memset(&slave_conf, 0x00, sizeof(slave_conf)); ++ ++ slave_conf.device_fc = flow_control; ++ if (read) { ++ slave_conf.src_maxburst = 16; ++ slave_conf.src_addr = this->res->start + reg_off; ++ slave_conf.slave_id = this->data_crci; ++ } else { ++ slave_conf.dst_maxburst = 16; ++ slave_conf.dst_addr = this->res->start + reg_off; ++ slave_conf.slave_id = this->cmd_crci; ++ } ++ ++ r = dmaengine_slave_config(this->chan, &slave_conf); ++ if (r) { ++ dev_err(this->dev, "failed to configure dma channel\n"); ++ goto err; ++ } ++ ++ dma_desc = dmaengine_prep_slave_sg(this->chan, sgl, 1, desc->dir, 0); ++ if (!dma_desc) { ++ dev_err(this->dev, "failed to prepare desc\n"); ++ r = -EINVAL; ++ goto err; ++ } ++ ++ desc->dma_desc = dma_desc; ++ ++ return 0; ++err: ++ kfree(desc); ++ ++ return r; ++} ++ ++/* ++ * read_reg_dma: prepares a descriptor to read a given number of ++ * contiguous registers to the reg_read_buf pointer ++ * ++ * @first: offset of the first register in the contiguous block ++ * @num_regs: number of registers to read ++ */ ++static int read_reg_dma(struct qcom_nandc_data *this, int first, int num_regs) ++{ ++ bool flow_control = false; ++ void *vaddr; ++ int size; ++ ++ if (first == NAND_READ_ID || first == NAND_FLASH_STATUS) ++ flow_control = true; ++ ++ size = num_regs * sizeof(u32); ++ vaddr = this->reg_read_buf + this->reg_read_pos; ++ this->reg_read_pos += num_regs; ++ ++ return prep_dma_desc(this, true, first, vaddr, size, flow_control); ++} ++ ++/* ++ * write_reg_dma: prepares a descriptor to write a given number of ++ * contiguous registers ++ * ++ * @first: offset of the first register in the contiguous block ++ * @num_regs: number of registers to write ++ */ ++static int write_reg_dma(struct qcom_nandc_data *this, int first, int num_regs) ++{ ++ bool flow_control = false; ++ struct nandc_regs *regs = this->regs; ++ void *vaddr; ++ int size; ++ ++ switch (first) { ++ case NAND_FLASH_CMD: ++ vaddr = ®s->cmd; ++ flow_control = true; ++ break; ++ case NAND_EXEC_CMD: ++ vaddr = ®s->exec; ++ break; ++ case NAND_FLASH_STATUS: ++ vaddr = ®s->clrflashstatus; ++ break; ++ case NAND_DEV0_CFG0: ++ vaddr = ®s->cfg0; ++ break; ++ case NAND_READ_STATUS: ++ vaddr = ®s->clrreadstatus; ++ break; ++ case NAND_DEV_CMD1: ++ vaddr = ®s->cmd1; ++ break; ++ case NAND_DEV_CMD1_RESTORE: ++ first = NAND_DEV_CMD1; ++ vaddr = ®s->orig_cmd1; ++ break; ++ case NAND_DEV_CMD_VLD: ++ vaddr = ®s->vld; ++ break; ++ case NAND_DEV_CMD_VLD_RESTORE: ++ first = NAND_DEV_CMD_VLD; ++ vaddr = ®s->orig_vld; ++ break; ++ case NAND_EBI2_ECC_BUF_CFG: ++ vaddr = ®s->ecc_buf_cfg; ++ break; ++ default: ++ dev_err(this->dev, "invalid starting register\n"); ++ return -EINVAL; ++ } ++ ++ size = num_regs * sizeof(u32); ++ ++ return prep_dma_desc(this, false, first, vaddr, size, flow_control); ++} ++ ++/* ++ * read_data_dma: prepares a DMA descriptor to transfer data from the ++ * controller's internal buffer to the buffer 'vaddr' ++ * ++ * @reg_off: offset within the controller's data buffer ++ * @vaddr: virtual address of the buffer we want to write to ++ * @size: DMA transaction size in bytes ++ */ ++static int read_data_dma(struct qcom_nandc_data *this, int reg_off, ++ const u8 *vaddr, int size) ++{ ++ return prep_dma_desc(this, true, reg_off, vaddr, size, false); ++} ++ ++/* ++ * write_data_dma: prepares a DMA descriptor to transfer data from ++ * 'vaddr' to the controller's internal buffer ++ * ++ * @reg_off: offset within the controller's data buffer ++ * @vaddr: virtual address of the buffer we want to read from ++ * @size: DMA transaction size in bytes ++ */ ++static int write_data_dma(struct qcom_nandc_data *this, int reg_off, ++ const u8 *vaddr, int size) ++{ ++ return prep_dma_desc(this, false, reg_off, vaddr, size, false); ++} ++ ++/* ++ * helper to prepare dma descriptors to configure registers needed for reading a ++ * codeword/step in a page ++ */ ++static void config_cw_read(struct qcom_nandc_data *this) ++{ ++ write_reg_dma(this, NAND_FLASH_CMD, 3); ++ write_reg_dma(this, NAND_DEV0_CFG0, 3); ++ write_reg_dma(this, NAND_EBI2_ECC_BUF_CFG, 1); ++ ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 2); ++ read_reg_dma(this, NAND_ERASED_CW_DETECT_STATUS, 1); ++} ++ ++/* ++ * helpers to prepare dma descriptors used to configure registers needed for ++ * writing a codeword/step in a page ++ */ ++static void config_cw_write_pre(struct qcom_nandc_data *this) ++{ ++ write_reg_dma(this, NAND_FLASH_CMD, 3); ++ write_reg_dma(this, NAND_DEV0_CFG0, 3); ++ write_reg_dma(this, NAND_EBI2_ECC_BUF_CFG, 1); ++} ++ ++static void config_cw_write_post(struct qcom_nandc_data *this) ++{ ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 1); ++ ++ write_reg_dma(this, NAND_FLASH_STATUS, 1); ++ write_reg_dma(this, NAND_READ_STATUS, 1); ++} ++ ++/* ++ * the following functions are used within chip->cmdfunc() to perform different ++ * NAND_CMD_* commands ++ */ ++ ++/* sets up descriptors for NAND_CMD_PARAM */ ++static int nandc_param(struct qcom_nandc_data *this) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ /* ++ * NAND_CMD_PARAM is called before we know much about the FLASH chip ++ * in use. we configure the controller to perform a raw read of 512 ++ * bytes to read onfi params ++ */ ++ regs->cmd = PAGE_READ | PAGE_ACC | LAST_PAGE; ++ regs->addr0 = 0; ++ regs->addr1 = 0; ++ regs->cfg0 = 0 << CW_PER_PAGE ++ | 512 << UD_SIZE_BYTES ++ | 5 << NUM_ADDR_CYCLES ++ | 0 << SPARE_SIZE_BYTES; ++ ++ regs->cfg1 = 7 << NAND_RECOVERY_CYCLES ++ | 0 << CS_ACTIVE_BSY ++ | 17 << BAD_BLOCK_BYTE_NUM ++ | 1 << BAD_BLOCK_IN_SPARE_AREA ++ | 2 << WR_RD_BSY_GAP ++ | 0 << WIDE_FLASH ++ | 1 << DEV0_CFG1_ECC_DISABLE; ++ ++ regs->ecc_bch_cfg = 1 << ECC_CFG_ECC_DISABLE; ++ ++ /* configure CMD1 and VLD for ONFI param probing */ ++ regs->vld = (this->vld & ~(1 << READ_START_VLD)) ++ | 0 << READ_START_VLD; ++ ++ regs->cmd1 = (this->cmd1 & ~(0xFF << READ_ADDR)) ++ | NAND_CMD_PARAM << READ_ADDR; ++ ++ regs->exec = 1; ++ ++ regs->orig_cmd1 = this->cmd1; ++ regs->orig_vld = this->vld; ++ ++ write_reg_dma(this, NAND_DEV_CMD_VLD, 1); ++ write_reg_dma(this, NAND_DEV_CMD1, 1); ++ ++ this->buf_count = 512; ++ memset(this->data_buffer, 0xff, this->buf_count); ++ ++ config_cw_read(this); ++ ++ read_data_dma(this, FLASH_BUF_ACC, this->data_buffer, this->buf_count); ++ ++ /* restore CMD1 and VLD regs */ ++ write_reg_dma(this, NAND_DEV_CMD1_RESTORE, 1); ++ write_reg_dma(this, NAND_DEV_CMD_VLD_RESTORE, 1); ++ ++ return 0; ++} ++ ++/* sets up descriptors for NAND_CMD_ERASE1 */ ++static int erase_block(struct qcom_nandc_data *this, int page_addr) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ regs->cmd = BLOCK_ERASE | PAGE_ACC | LAST_PAGE; ++ regs->addr0 = page_addr; ++ regs->addr1 = 0; ++ regs->cfg0 = this->cfg0_raw & ~(7 << CW_PER_PAGE); ++ regs->cfg1 = this->cfg1_raw; ++ regs->exec = 1; ++ regs->clrflashstatus = this->clrflashstatus; ++ regs->clrreadstatus = this->clrreadstatus; ++ ++ write_reg_dma(this, NAND_FLASH_CMD, 3); ++ write_reg_dma(this, NAND_DEV0_CFG0, 2); ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 1); ++ ++ write_reg_dma(this, NAND_FLASH_STATUS, 1); ++ write_reg_dma(this, NAND_READ_STATUS, 1); ++ ++ return 0; ++} ++ ++/* sets up descriptors for NAND_CMD_READID */ ++static int read_id(struct qcom_nandc_data *this, int column) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ if (column == -1) ++ return 0; ++ ++ regs->cmd = FETCH_ID; ++ regs->addr0 = column; ++ regs->addr1 = 0; ++ regs->chip_sel = DM_EN; ++ regs->exec = 1; ++ ++ write_reg_dma(this, NAND_FLASH_CMD, 4); ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_READ_ID, 1); ++ ++ return 0; ++} ++ ++/* sets up descriptors for NAND_CMD_RESET */ ++static int reset(struct qcom_nandc_data *this) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ regs->cmd = RESET_DEVICE; ++ regs->exec = 1; ++ ++ write_reg_dma(this, NAND_FLASH_CMD, 1); ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 1); ++ ++ return 0; ++} ++ ++/* helpers to submit/free our list of dma descriptors */ ++static void dma_callback(void *param) ++{ ++ struct qcom_nandc_data *this = param; ++ struct completion *c = &this->dma_done; ++ ++ complete(c); ++} ++ ++static int submit_descs(struct qcom_nandc_data *this) ++{ ++ struct completion *c = &this->dma_done; ++ struct desc_info *desc; ++ int r; ++ ++ init_completion(c); ++ ++ list_for_each_entry(desc, &this->list, list) { ++ /* ++ * we add a callback to the last descriptor in our list to ++ * notify completion of command ++ */ ++ if (list_is_last(&desc->list, &this->list)) { ++ desc->dma_desc->callback = dma_callback; ++ desc->dma_desc->callback_param = this; ++ } ++ ++ dmaengine_submit(desc->dma_desc); ++ } ++ ++ dma_async_issue_pending(this->chan); ++ ++ r = wait_for_completion_timeout(c, msecs_to_jiffies(500)); ++ if (!r) ++ return -ETIMEDOUT; ++ ++ return 0; ++} ++ ++static void free_descs(struct qcom_nandc_data *this) ++{ ++ struct desc_info *desc, *n; ++ ++ list_for_each_entry_safe(desc, n, &this->list, list) { ++ list_del(&desc->list); ++ dma_unmap_sg(this->dev, &desc->sgl, 1, desc->dir); ++ kfree(desc); ++ } ++} ++ ++/* reset the register read buffer for next NAND operation */ ++static void clear_read_regs(struct qcom_nandc_data *this) ++{ ++ this->reg_read_pos = 0; ++ memset(this->reg_read_buf, 0, MAX_REG_RD * sizeof(*this->reg_read_buf)); ++} ++ ++static void pre_command(struct qcom_nandc_data *this, int command) ++{ ++ this->buf_count = 0; ++ this->buf_start = 0; ++ this->use_ecc = false; ++ this->last_command = command; ++ ++ clear_read_regs(this); ++} ++ ++/* ++ * this is called after NAND_CMD_PAGEPROG and NAND_CMD_ERASE1 to set our ++ * privately maintained status byte, this status byte can be read after ++ * NAND_CMD_STATUS is called ++ */ ++static void parse_erase_write_errors(struct qcom_nandc_data *this, int command) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int num_cw; ++ int i; ++ ++ num_cw = command == NAND_CMD_PAGEPROG ? ecc->steps : 1; ++ ++ for (i = 0; i < num_cw; i++) { ++ __le32 flash_status = le32_to_cpu(this->reg_read_buf[i]); ++ ++ if (flash_status & FS_MPU_ERR) ++ this->status &= ~NAND_STATUS_WP; ++ ++ if (flash_status & FS_OP_ERR || (i == (num_cw - 1) && ++ (flash_status & FS_DEVICE_STS_ERR))) ++ this->status |= NAND_STATUS_FAIL; ++ } ++} ++ ++static void post_command(struct qcom_nandc_data *this, int command) ++{ ++ switch (command) { ++ case NAND_CMD_READID: ++ memcpy(this->data_buffer, this->reg_read_buf, this->buf_count); ++ break; ++ case NAND_CMD_PAGEPROG: ++ case NAND_CMD_ERASE1: ++ parse_erase_write_errors(this, command); ++ break; ++ default: ++ break; ++ } ++} ++ ++/* ++ * Implements chip->cmdfunc. It's only used for a limited set of commands. ++ * The rest of the commands wouldn't be called by upper layers. For example, ++ * NAND_CMD_READOOB would never be called because we have our own versions ++ * of read_oob ops for nand_ecc_ctrl. ++ */ ++static void qcom_nandc_command(struct mtd_info *mtd, unsigned int command, ++ int column, int page_addr) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ struct qcom_nandc_data *this = chip->priv; ++ bool wait = false; ++ int r = 0; ++ ++ pre_command(this, command); ++ ++ switch (command) { ++ case NAND_CMD_RESET: ++ r = reset(this); ++ wait = true; ++ break; ++ ++ case NAND_CMD_READID: ++ this->buf_count = 4; ++ r = read_id(this, column); ++ wait = true; ++ break; ++ ++ case NAND_CMD_PARAM: ++ r = nandc_param(this); ++ wait = true; ++ break; ++ ++ case NAND_CMD_ERASE1: ++ r = erase_block(this, page_addr); ++ wait = true; ++ break; ++ ++ case NAND_CMD_READ0: ++ /* we read the entire page for now */ ++ WARN_ON(column != 0); ++ ++ this->use_ecc = true; ++ set_address(this, 0, page_addr); ++ update_rw_regs(this, ecc->steps, true); ++ break; ++ ++ case NAND_CMD_SEQIN: ++ WARN_ON(column != 0); ++ set_address(this, 0, page_addr); ++ break; ++ ++ case NAND_CMD_PAGEPROG: ++ case NAND_CMD_STATUS: ++ case NAND_CMD_NONE: ++ default: ++ break; ++ } ++ ++ if (r) { ++ dev_err(this->dev, "failure executing command %d\n", ++ command); ++ free_descs(this); ++ return; ++ } ++ ++ if (wait) { ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, ++ "failure submitting descs for command %d\n", ++ command); ++ } ++ ++ free_descs(this); ++ ++ post_command(this, command); ++} ++ ++/* ++ * when using RS ECC, the NAND controller flags an error when reading an ++ * erased page. however, there are special characters at certain offsets when ++ * we read the erased page. we check here if the page is really empty. if so, ++ * we replace the magic characters with 0xffs ++ */ ++static bool empty_page_fixup(struct qcom_nandc_data *this, u8 *data_buf) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage = ecc->steps; ++ u8 orig1[MAX_NUM_STEPS], orig2[MAX_NUM_STEPS]; ++ int i, j; ++ ++ /* if BCH is enabled, HW will take care of detecting erased pages */ ++ if (this->bch_enabled || !this->use_ecc) ++ return false; ++ ++ for (i = 0; i < cwperpage; i++) { ++ u8 *empty1, *empty2; ++ __le32 flash_status = le32_to_cpu(this->reg_read_buf[3 * i]); ++ ++ /* ++ * an erased page flags an error in NAND_FLASH_STATUS, check if ++ * the page is erased by looking for 0x54s at offsets 3 and 175 ++ * from the beginning of each codeword ++ */ ++ if (!(flash_status & FS_OP_ERR)) ++ break; ++ ++ empty1 = &data_buf[3 + i * this->cw_data]; ++ empty2 = &data_buf[175 + i * this->cw_data]; ++ ++ /* ++ * if the error wasn't because of an erased page, bail out and ++ * and let someone else do the error checking ++ */ ++ if ((*empty1 == 0x54 && *empty2 == 0xff) || ++ (*empty1 == 0xff && *empty2 == 0x54)) { ++ orig1[i] = *empty1; ++ orig2[i] = *empty2; ++ ++ *empty1 = 0xff; ++ *empty2 = 0xff; ++ } else { ++ break; ++ } ++ } ++ ++ if (i < cwperpage || memchr_inv(data_buf, 0xff, mtd->writesize)) ++ goto not_empty; ++ ++ /* ++ * tell the caller that the page was empty and is fixed up, so that ++ * parse_read_errors() doesn't think it's an error ++ */ ++ return true; ++ ++not_empty: ++ /* restore original values if not empty*/ ++ for (j = 0; j < i; j++) { ++ data_buf[3 + j * this->cw_data] = orig1[j]; ++ data_buf[175 + j * this->cw_data] = orig2[j]; ++ } ++ ++ return false; ++} ++ ++struct read_stats { ++ __le32 flash; ++ __le32 buffer; ++ __le32 erased_cw; ++}; ++ ++/* ++ * reads back status registers set by the controller to notify page read ++ * errors. this is equivalent to what 'ecc->correct()' would do. ++ */ ++static int parse_read_errors(struct qcom_nandc_data *this, bool erased_page) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage = ecc->steps; ++ unsigned int max_bitflips = 0; ++ int i; ++ ++ for (i = 0; i < cwperpage; i++) { ++ int stat; ++ struct read_stats *buf; ++ ++ buf = (struct read_stats *) (this->reg_read_buf + 3 * i); ++ ++ buf->flash = le32_to_cpu(buf->flash); ++ buf->buffer = le32_to_cpu(buf->buffer); ++ buf->erased_cw = le32_to_cpu(buf->erased_cw); ++ ++ if (buf->flash & (FS_OP_ERR | FS_MPU_ERR)) { ++ ++ /* ignore erased codeword errors */ ++ if (this->bch_enabled) { ++ if ((buf->erased_cw & ERASED_CW) == ERASED_CW) ++ continue; ++ } else if (erased_page) { ++ continue; ++ } ++ ++ if (buf->buffer & BS_UNCORRECTABLE_BIT) { ++ mtd->ecc_stats.failed++; ++ continue; ++ } ++ } ++ ++ stat = buf->buffer & BS_CORRECTABLE_ERR_MSK; ++ mtd->ecc_stats.corrected += stat; ++ ++ max_bitflips = max_t(unsigned int, max_bitflips, stat); ++ } ++ ++ return max_bitflips; ++} ++ ++/* ++ * helper to perform the actual page read operation, used by ecc->read_page() ++ * and ecc->read_oob() ++ */ ++static int read_page_low(struct qcom_nandc_data *this, u8 *data_buf, ++ u8 *oob_buf) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int i, r; ++ ++ /* queue cmd descs for each codeword */ ++ for (i = 0; i < ecc->steps; i++) { ++ int data_size, oob_size; ++ ++ if (i == (ecc->steps - 1)) { ++ data_size = ecc->size - ((ecc->steps - 1) << 2); ++ oob_size = (ecc->steps << 2) + ecc->bytes; ++ } else { ++ data_size = this->cw_data; ++ oob_size = ecc->bytes; ++ } ++ ++ config_cw_read(this); ++ ++ if (data_buf) ++ read_data_dma(this, FLASH_BUF_ACC, data_buf, data_size); ++ ++ if (oob_buf) ++ read_data_dma(this, FLASH_BUF_ACC + data_size, oob_buf, ++ oob_size); ++ ++ if (data_buf) ++ data_buf += data_size; ++ if (oob_buf) ++ oob_buf += oob_size; ++ } ++ ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, "failure to read page/oob\n"); ++ ++ free_descs(this); ++ ++ return r; ++} ++ ++/* ++ * a helper that copies the last step/codeword of a page (containing free oob) ++ * into our local buffer ++ */ ++static int copy_last_cw(struct qcom_nandc_data *this, bool use_ecc, int page) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int size; ++ int r; ++ ++ clear_read_regs(this); ++ ++ size = use_ecc ? this->cw_data : this->cw_size; ++ ++ /* prepare a clean read buffer */ ++ memset(this->data_buffer, 0xff, size); ++ ++ this->use_ecc = use_ecc; ++ set_address(this, this->cw_size * (ecc->steps - 1), page); ++ update_rw_regs(this, 1, true); ++ ++ config_cw_read(this); ++ ++ read_data_dma(this, FLASH_BUF_ACC, this->data_buffer, size); ++ ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, "failed to copy last codeword\n"); ++ ++ free_descs(this); ++ ++ return r; ++} ++ ++/* implements ecc->read_page() */ ++static int qcom_nandc_read_page(struct mtd_info *mtd, struct nand_chip *chip, ++ uint8_t *buf, int oob_required, int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ u8 *data_buf, *oob_buf = NULL; ++ bool erased_page; ++ int r; ++ ++ data_buf = buf; ++ oob_buf = oob_required ? chip->oob_poi : NULL; ++ ++ r = read_page_low(this, data_buf, oob_buf); ++ if (r) { ++ dev_err(this->dev, "failure to read page\n"); ++ return r; ++ } ++ ++ erased_page = empty_page_fixup(this, data_buf); ++ ++ return parse_read_errors(this, erased_page); ++} ++ ++/* implements ecc->read_oob() */ ++static int qcom_nandc_read_oob(struct mtd_info *mtd, struct nand_chip *chip, ++ int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int r; ++ ++ clear_read_regs(this); ++ ++ this->use_ecc = true; ++ set_address(this, 0, page); ++ update_rw_regs(this, ecc->steps, true); ++ ++ r = read_page_low(this, NULL, chip->oob_poi); ++ if (r) ++ dev_err(this->dev, "failure to read oob\n"); ++ ++ return r; ++} ++ ++/* implements ecc->read_oob_raw(), used to read the bad block marker flag */ ++static int qcom_nandc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip, ++ int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ uint8_t *oob = chip->oob_poi; ++ int start, length; ++ int r; ++ ++ /* ++ * configure registers for a raw page read, the address is set to the ++ * beginning of the last codeword, we don't care about reading ecc ++ * portion of oob, just the free stuff ++ */ ++ r = copy_last_cw(this, false, page); ++ if (r) ++ return r; ++ ++ /* ++ * reading raw oob has 2 parts, first the bad block byte, then the ++ * actual free oob region. perform a memcpy in two steps ++ */ ++ start = mtd->writesize - (this->cw_size * (ecc->steps - 1)); ++ length = chip->options & NAND_BUSWIDTH_16 ? 2 : 1; ++ ++ memcpy(oob, this->data_buffer + start, length); ++ ++ oob += length; ++ ++ start = this->cw_data - (ecc->steps << 2) + 1; ++ length = ecc->steps << 2; ++ ++ memcpy(oob, this->data_buffer + start, length); ++ ++ return 0; ++} ++ ++/* implements ecc->write_page() */ ++static int qcom_nandc_write_page(struct mtd_info *mtd, struct nand_chip *chip, ++ const uint8_t *buf, int oob_required) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ u8 *data_buf, *oob_buf; ++ int i, r = 0; ++ ++ clear_read_regs(this); ++ ++ data_buf = (u8 *) buf; ++ oob_buf = chip->oob_poi; ++ ++ this->use_ecc = true; ++ update_rw_regs(this, ecc->steps, false); ++ ++ for (i = 0; i < ecc->steps; i++) { ++ int data_size, oob_size; ++ ++ if (i == (ecc->steps - 1)) { ++ data_size = ecc->size - ((ecc->steps - 1) << 2); ++ oob_size = (ecc->steps << 2) + ecc->bytes; ++ } else { ++ data_size = this->cw_data; ++ oob_size = ecc->bytes; ++ } ++ ++ config_cw_write_pre(this); ++ write_data_dma(this, FLASH_BUF_ACC, data_buf, data_size); ++ ++ /* ++ * we don't really need to write anything to oob for the ++ * first n - 1 codewords since these oob regions just ++ * contain ecc that's written by the controller itself ++ */ ++ if (i == (ecc->steps - 1)) ++ write_data_dma(this, FLASH_BUF_ACC + data_size, ++ oob_buf, oob_size); ++ config_cw_write_post(this); ++ ++ data_buf += data_size; ++ oob_buf += oob_size; ++ } ++ ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, "failure to write page\n"); ++ ++ free_descs(this); ++ ++ return r; ++} ++ ++/* ++ * implements ecc->write_oob() ++ * ++ * the NAND controller cannot write only data or only oob within a codeword, ++ * since ecc is calculated for the combined codeword. we first copy the ++ * entire contents for the last codeword(data + oob), replace the old oob ++ * with the new one in chip->oob_poi, and then write the entire codeword. ++ * this read-copy-write operation results in a slight perormance loss. ++ */ ++static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, ++ int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ uint8_t *oob = chip->oob_poi; ++ int free_boff; ++ int data_size, oob_size; ++ int r, status = 0; ++ ++ r = copy_last_cw(this, true, page); ++ if (r) ++ return r; ++ ++ clear_read_regs(this); ++ ++ /* calculate the data and oob size for the last codeword/step */ ++ data_size = ecc->size - ((ecc->steps - 1) << 2); ++ oob_size = (ecc->steps << 2) + ecc->bytes; ++ ++ /* ++ * the location of spare data in the oob buffer, we could also use ++ * ecc->layout.oobfree here ++ */ ++ free_boff = ecc->bytes * (ecc->steps - 1); ++ ++ /* override new oob content to last codeword */ ++ memcpy(this->data_buffer + data_size, oob + free_boff, oob_size); ++ ++ this->use_ecc = true; ++ set_address(this, this->cw_size * (ecc->steps - 1), page); ++ update_rw_regs(this, 1, false); ++ ++ config_cw_write_pre(this); ++ write_data_dma(this, FLASH_BUF_ACC, this->data_buffer, ++ data_size + oob_size); ++ config_cw_write_post(this); ++ ++ r = submit_descs(this); ++ ++ free_descs(this); ++ ++ if (r) { ++ dev_err(this->dev, "failure to write oob\n"); ++ return -EIO; ++ } ++ ++ chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); ++ ++ status = chip->waitfunc(mtd, chip); ++ ++ return status & NAND_STATUS_FAIL ? -EIO : 0; ++} ++ ++/* implements ecc->write_oob_raw(), used to write bad block marker flag */ ++static int qcom_nandc_write_oob_raw(struct mtd_info *mtd, ++ struct nand_chip *chip, int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ uint8_t *oob = chip->oob_poi; ++ int start, length; ++ int r, status = 0; ++ ++ r = copy_last_cw(this, false, page); ++ if (r) ++ return r; ++ ++ clear_read_regs(this); ++ ++ /* ++ * writing raw oob has 2 parts, first the bad block region, then the ++ * actual free region ++ */ ++ start = mtd->writesize - (this->cw_size * (ecc->steps - 1)); ++ length = chip->options & NAND_BUSWIDTH_16 ? 2 : 1; ++ ++ memcpy(this->data_buffer + start, oob, length); ++ ++ oob += length; ++ ++ start = this->cw_data - (ecc->steps << 2) + 1; ++ length = ecc->steps << 2; ++ ++ memcpy(this->data_buffer + start, oob, length); ++ ++ /* prepare write */ ++ this->use_ecc = false; ++ set_address(this, this->cw_size * (ecc->steps - 1), page); ++ update_rw_regs(this, 1, false); ++ ++ config_cw_write_pre(this); ++ write_data_dma(this, FLASH_BUF_ACC, this->data_buffer, this->cw_size); ++ config_cw_write_post(this); ++ ++ r = submit_descs(this); ++ ++ free_descs(this); ++ ++ if (r) { ++ dev_err(this->dev, "failure to write updated oob\n"); ++ return -EIO; ++ } ++ ++ chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); ++ ++ status = chip->waitfunc(mtd, chip); ++ ++ return status & NAND_STATUS_FAIL ? -EIO : 0; ++} ++ ++/* ++ * the three functions below implement chip->read_byte(), chip->read_buf() ++ * and chip->write_buf() respectively. these aren't used for ++ * reading/writing page data, they are used for smaller data like reading ++ * id, status etc ++ */ ++static uint8_t qcom_nandc_read_byte(struct mtd_info *mtd) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ uint8_t *buf = this->data_buffer; ++ uint8_t ret = 0x0; ++ ++ if (this->last_command == NAND_CMD_STATUS) { ++ ret = this->status; ++ ++ this->status = NAND_STATUS_READY | NAND_STATUS_WP; ++ ++ return ret; ++ } ++ ++ if (this->buf_start < this->buf_count) ++ ret = buf[this->buf_start++]; ++ ++ return ret; ++} ++ ++static void qcom_nandc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ int real_len = min_t(size_t, len, this->buf_count - this->buf_start); ++ ++ memcpy(buf, this->data_buffer + this->buf_start, real_len); ++ this->buf_start += real_len; ++} ++ ++static void qcom_nandc_write_buf(struct mtd_info *mtd, const uint8_t *buf, ++ int len) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ int real_len = min_t(size_t, len, this->buf_count - this->buf_start); ++ ++ memcpy(this->data_buffer + this->buf_start, buf, real_len); ++ ++ this->buf_start += real_len; ++} ++ ++/* we support only one external chip for now */ ++static void qcom_nandc_select_chip(struct mtd_info *mtd, int chipnr) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ ++ if (chipnr <= 0) ++ return; ++ ++ dev_warn(this->dev, "invalid chip select\n"); ++} ++ ++/* ++ * NAND controller page layout info ++ * ++ * |-----------------------| |---------------------------------| ++ * | xx.......xx| | *********xx.......xx| ++ * | DATA xx..ECC..xx| | DATA **SPARE**xx..ECC..xx| ++ * | (516) xx.......xx| | (516-n*4) **(n*4)**xx.......xx| ++ * | xx.......xx| | *********xx.......xx| ++ * |-----------------------| |---------------------------------| ++ * codeword 1,2..n-1 codeword n ++ * <---(528/532 Bytes)----> <-------(528/532 Bytes)----------> ++ * ++ * n = number of codewords in the page ++ * . = ECC bytes ++ * * = spare bytes ++ * x = unused/reserved bytes ++ * ++ * 2K page: n = 4, spare = 16 bytes ++ * 4K page: n = 8, spare = 32 bytes ++ * 8K page: n = 16, spare = 64 bytes ++ * ++ * the qcom nand controller operates at a sub page/codeword level. each ++ * codeword is 528 and 532 bytes for 4 bit and 8 bit ECC modes respectively. ++ * the number of ECC bytes vary based on the ECC strength and the bus width. ++ * ++ * the first n - 1 codewords contains 516 bytes of user data, the remaining ++ * 12/16 bytes consist of ECC and reserved data. The nth codeword contains ++ * both user data and spare(oobavail) bytes that sum up to 516 bytes. ++ * ++ * the layout described above is used by the controller when the ECC block is ++ * enabled. When we read a page with ECC enabled, the unused/reserved bytes are ++ * skipped and not copied to our internal buffer. therefore, the nand_ecclayout ++ * layouts defined below doesn't consider the positions occupied by the reserved ++ * bytes ++ * ++ * when the ECC block is disabled, one unused byte (or two for 16 bit bus width) ++ * in the last codeword is the position of bad block marker. the bad block ++ * marker cannot be accessed when ECC is enabled. ++ * ++ */ ++ ++/* ++ * Layouts for different page sizes and ecc modes. We skip the eccpos field ++ * since it isn't needed for this driver ++ */ ++ ++/* 2K page, 4 bit ECC */ ++static struct nand_ecclayout layout_oob_64 = { ++ .eccbytes = 40, ++ .oobfree = { ++ { 30, 16 }, ++ }, ++}; ++ ++/* 4K page, 4 bit ECC, 8/16 bit bus width */ ++static struct nand_ecclayout layout_oob_128 = { ++ .eccbytes = 80, ++ .oobfree = { ++ { 70, 32 }, ++ }, ++}; ++ ++/* 4K page, 8 bit ECC, 8 bit bus width */ ++static struct nand_ecclayout layout_oob_224_x8 = { ++ .eccbytes = 104, ++ .oobfree = { ++ { 91, 32 }, ++ }, ++}; ++ ++/* 4K page, 8 bit ECC, 16 bit bus width */ ++static struct nand_ecclayout layout_oob_224_x16 = { ++ .eccbytes = 112, ++ .oobfree = { ++ { 98, 32 }, ++ }, ++}; ++ ++/* 8K page, 4 bit ECC, 8/16 bit bus width */ ++static struct nand_ecclayout layout_oob_256 = { ++ .eccbytes = 160, ++ .oobfree = { ++ { 151, 64 }, ++ }, ++}; ++ ++/* ++ * this is called before scan_ident, we do some minimal configurations so ++ * that reading ID and ONFI params work ++ */ ++static void qcom_nandc_pre_init(struct qcom_nandc_data *this) ++{ ++ /* kill onenand */ ++ nandc_write(this, SFLASHC_BURST_CFG, 0); ++ ++ /* enable ADM DMA */ ++ nandc_write(this, NAND_FLASH_CHIP_SELECT, DM_EN); ++ ++ /* save the original values of these registers */ ++ this->cmd1 = nandc_read(this, NAND_DEV_CMD1); ++ this->vld = nandc_read(this, NAND_DEV_CMD_VLD); ++ ++ /* initial status value */ ++ this->status = NAND_STATUS_READY | NAND_STATUS_WP; ++} ++ ++static int qcom_nandc_ecc_init(struct qcom_nandc_data *this) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage; ++ bool wide_bus; ++ ++ /* the nand controller fetches codewords/chunks of 512 bytes */ ++ cwperpage = mtd->writesize >> 9; ++ ++ ecc->strength = this->ecc_strength; ++ ++ wide_bus = chip->options & NAND_BUSWIDTH_16 ? true : false; ++ ++ if (ecc->strength >= 8) { ++ /* 8 bit ECC defaults to BCH ECC on all platforms */ ++ ecc->bytes = wide_bus ? 14 : 13; ++ } else { ++ /* ++ * if the controller supports BCH for 4 bit ECC, the controller ++ * uses lesser bytes for ECC. If RS is used, the ECC bytes is ++ * always 10 bytes ++ */ ++ if (this->ecc_modes & ECC_BCH_4BIT) ++ ecc->bytes = wide_bus ? 8 : 7; ++ else ++ ecc->bytes = 10; ++ } ++ ++ /* each step consists of 512 bytes of data */ ++ ecc->size = NANDC_STEP_SIZE; ++ ++ ecc->read_page = qcom_nandc_read_page; ++ ecc->read_oob = qcom_nandc_read_oob; ++ ecc->write_page = qcom_nandc_write_page; ++ ecc->write_oob = qcom_nandc_write_oob; ++ ++ /* ++ * the bad block marker is readable only when we read the page with ECC ++ * disabled. all the ops above run with ECC enabled. We need raw read ++ * and write function for oob in order to access bad block marker. ++ */ ++ ecc->read_oob_raw = qcom_nandc_read_oob_raw; ++ ecc->write_oob_raw = qcom_nandc_write_oob_raw; ++ ++ switch (mtd->oobsize) { ++ case 64: ++ ecc->layout = &layout_oob_64; ++ break; ++ case 128: ++ ecc->layout = &layout_oob_128; ++ break; ++ case 224: ++ if (wide_bus) ++ ecc->layout = &layout_oob_224_x16; ++ else ++ ecc->layout = &layout_oob_224_x8; ++ break; ++ case 256: ++ ecc->layout = &layout_oob_256; ++ break; ++ default: ++ dev_err(this->dev, "unsupported NAND device, oobsize %d\n", ++ mtd->oobsize); ++ return -ENODEV; ++ } ++ ++ ecc->mode = NAND_ECC_HW; ++ ++ /* enable ecc by default */ ++ this->use_ecc = true; ++ ++ return 0; ++} ++ ++static void qcom_nandc_hw_post_init(struct qcom_nandc_data *this) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage = mtd->writesize / ecc->size; ++ int spare_bytes, bad_block_byte; ++ bool wide_bus; ++ int ecc_mode = 0; ++ ++ wide_bus = chip->options & NAND_BUSWIDTH_16 ? true : false; ++ ++ if (ecc->strength >= 8) { ++ this->cw_size = 532; ++ ++ spare_bytes = wide_bus ? 0 : 2; ++ ++ this->bch_enabled = true; ++ ecc_mode = 1; ++ } else { ++ this->cw_size = 528; ++ ++ if (this->ecc_modes & ECC_BCH_4BIT) { ++ spare_bytes = wide_bus ? 2 : 4; ++ ++ this->bch_enabled = true; ++ ecc_mode = 0; ++ } else { ++ spare_bytes = wide_bus ? 0 : 1; ++ } ++ } ++ ++ /* ++ * DATA_UD_BYTES varies based on whether the read/write command protects ++ * spare data with ECC too. We protect spare data by default, so we set ++ * it to main + spare data, which are 512 and 4 bytes respectively. ++ */ ++ this->cw_data = 516; ++ ++ bad_block_byte = mtd->writesize - this->cw_size * (cwperpage - 1) + 1; ++ ++ this->cfg0 = (cwperpage - 1) << CW_PER_PAGE ++ | this->cw_data << UD_SIZE_BYTES ++ | 0 << DISABLE_STATUS_AFTER_WRITE ++ | 5 << NUM_ADDR_CYCLES ++ | ecc->bytes << ECC_PARITY_SIZE_BYTES_RS ++ | 0 << STATUS_BFR_READ ++ | 1 << SET_RD_MODE_AFTER_STATUS ++ | spare_bytes << SPARE_SIZE_BYTES; ++ ++ this->cfg1 = 7 << NAND_RECOVERY_CYCLES ++ | 0 << CS_ACTIVE_BSY ++ | bad_block_byte << BAD_BLOCK_BYTE_NUM ++ | 0 << BAD_BLOCK_IN_SPARE_AREA ++ | 2 << WR_RD_BSY_GAP ++ | wide_bus << WIDE_FLASH ++ | this->bch_enabled << ENABLE_BCH_ECC; ++ ++ this->cfg0_raw = (cwperpage - 1) << CW_PER_PAGE ++ | this->cw_size << UD_SIZE_BYTES ++ | 5 << NUM_ADDR_CYCLES ++ | 0 << SPARE_SIZE_BYTES; ++ ++ this->cfg1_raw = 7 << NAND_RECOVERY_CYCLES ++ | 0 << CS_ACTIVE_BSY ++ | 17 << BAD_BLOCK_BYTE_NUM ++ | 1 << BAD_BLOCK_IN_SPARE_AREA ++ | 2 << WR_RD_BSY_GAP ++ | wide_bus << WIDE_FLASH ++ | 1 << DEV0_CFG1_ECC_DISABLE; ++ ++ this->ecc_bch_cfg = this->bch_enabled << ECC_CFG_ECC_DISABLE ++ | 0 << ECC_SW_RESET ++ | this->cw_data << ECC_NUM_DATA_BYTES ++ | 1 << ECC_FORCE_CLK_OPEN ++ | ecc_mode << ECC_MODE ++ | ecc->bytes << ECC_PARITY_SIZE_BYTES_BCH; ++ ++ this->ecc_buf_cfg = 0x203 << NUM_STEPS; ++ ++ this->clrflashstatus = FS_READY_BSY_N; ++ this->clrreadstatus = 0xc0; ++ ++ dev_dbg(this->dev, ++ "cfg0 %x cfg1 %x ecc_buf_cfg %x ecc_bch cfg %x cw_size %d cw_data %d strength %d parity_bytes %d steps %d\n", ++ this->cfg0, this->cfg1, this->ecc_buf_cfg, ++ this->ecc_bch_cfg, this->cw_size, this->cw_data, ++ ecc->strength, ecc->bytes, cwperpage); ++} ++ ++static int qcom_nandc_alloc(struct qcom_nandc_data *this) ++{ ++ int r; ++ ++ r = dma_set_coherent_mask(this->dev, DMA_BIT_MASK(32)); ++ if (r) { ++ dev_err(this->dev, "failed to set DMA mask\n"); ++ return r; ++ } ++ ++ /* ++ * we use the internal buffer for reading ONFI params, reading small ++ * data like ID and status, and preforming read-copy-write operations ++ * when writing to a codeword partially. 532 is the maximum possible ++ * size of a codeword for our nand controller ++ */ ++ this->buf_size = 532; ++ ++ this->data_buffer = devm_kzalloc(this->dev, this->buf_size, GFP_KERNEL); ++ if (!this->data_buffer) ++ return -ENOMEM; ++ ++ this->regs = devm_kzalloc(this->dev, sizeof(*this->regs), GFP_KERNEL); ++ if (!this->regs) ++ return -ENOMEM; ++ ++ this->reg_read_buf = devm_kzalloc(this->dev, ++ MAX_REG_RD * sizeof(*this->reg_read_buf), ++ GFP_KERNEL); ++ if (!this->reg_read_buf) ++ return -ENOMEM; ++ ++ INIT_LIST_HEAD(&this->list); ++ ++ this->chan = dma_request_slave_channel(this->dev, "rxtx"); ++ if (!this->chan) { ++ dev_err(this->dev, "failed to request slave channel\n"); ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++static void qcom_nandc_unalloc(struct qcom_nandc_data *this) ++{ ++ dma_release_channel(this->chan); ++} ++ ++static int qcom_nandc_init(struct qcom_nandc_data *this) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct device_node *np = this->dev->of_node; ++ struct mtd_part_parser_data ppdata = { .of_node = np }; ++ int r; ++ ++ mtd->priv = chip; ++ mtd->name = "qcom-nandc"; ++ mtd->owner = THIS_MODULE; ++ ++ chip->priv = this; ++ ++ chip->cmdfunc = qcom_nandc_command; ++ chip->select_chip = qcom_nandc_select_chip; ++ chip->read_byte = qcom_nandc_read_byte; ++ chip->read_buf = qcom_nandc_read_buf; ++ chip->write_buf = qcom_nandc_write_buf; ++ ++ chip->options |= NAND_NO_SUBPAGE_WRITE | NAND_USE_BOUNCE_BUFFER; ++ if (this->bus_width == 16) ++ chip->options |= NAND_BUSWIDTH_16; ++ ++ chip->bbt_options = NAND_BBT_ACCESS_BBM_RAW; ++ if (of_get_nand_on_flash_bbt(np)) ++ chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; ++ ++ qcom_nandc_pre_init(this); ++ ++ r = nand_scan_ident(mtd, 1, NULL); ++ if (r) ++ return r; ++ ++ r = qcom_nandc_ecc_init(this); ++ if (r) ++ return r; ++ ++ qcom_nandc_hw_post_init(this); ++ ++ r = nand_scan_tail(mtd); ++ if (r) ++ return r; ++ ++ return mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0); ++} ++ ++static int qcom_nandc_parse_dt(struct platform_device *pdev) ++{ ++ struct qcom_nandc_data *this = platform_get_drvdata(pdev); ++ struct device_node *np = this->dev->of_node; ++ int r; ++ ++ this->ecc_strength = of_get_nand_ecc_strength(np); ++ if (this->ecc_strength < 0) { ++ dev_warn(this->dev, ++ "incorrect ecc strength, setting to 4 bits/step\n"); ++ this->ecc_strength = 4; ++ } ++ ++ this->bus_width = of_get_nand_bus_width(np); ++ if (this->bus_width < 0) { ++ dev_warn(this->dev, "incorrect bus width, setting to 8\n"); ++ this->bus_width = 8; ++ } ++ ++ r = of_property_read_u32(np, "qcom,cmd-crci", &this->cmd_crci); ++ if (r) { ++ dev_err(this->dev, "command CRCI unspecified\n"); ++ return r; ++ } ++ ++ r = of_property_read_u32(np, "qcom,data-crci", &this->data_crci); ++ if (r) { ++ dev_err(this->dev, "data CRCI unspecified\n"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++static int qcom_nandc_probe(struct platform_device *pdev) ++{ ++ struct qcom_nandc_data *this; ++ const struct of_device_id *match; ++ int r; ++ ++ this = devm_kzalloc(&pdev->dev, sizeof(*this), GFP_KERNEL); ++ if (!this) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, this); ++ ++ this->pdev = pdev; ++ this->dev = &pdev->dev; ++ ++ match = of_match_device(pdev->dev.driver->of_match_table, &pdev->dev); ++ if (!match) { ++ dev_err(&pdev->dev, "failed to match device\n"); ++ return -ENODEV; ++ } ++ ++ if (!match->data) { ++ dev_err(&pdev->dev, "failed to get device data\n"); ++ return -ENODEV; ++ } ++ ++ this->ecc_modes = (u32) match->data; ++ ++ this->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ this->base = devm_ioremap_resource(&pdev->dev, this->res); ++ if (IS_ERR(this->base)) ++ return PTR_ERR(this->base); ++ ++ this->core_clk = devm_clk_get(&pdev->dev, "core"); ++ if (IS_ERR(this->core_clk)) ++ return PTR_ERR(this->core_clk); ++ ++ this->aon_clk = devm_clk_get(&pdev->dev, "aon"); ++ if (IS_ERR(this->aon_clk)) ++ return PTR_ERR(this->aon_clk); ++ ++ r = qcom_nandc_parse_dt(pdev); ++ if (r) ++ return r; ++ ++ r = qcom_nandc_alloc(this); ++ if (r) ++ return r; ++ ++ r = clk_prepare_enable(this->core_clk); ++ if (r) ++ goto err_core_clk; ++ ++ r = clk_prepare_enable(this->aon_clk); ++ if (r) ++ goto err_aon_clk; ++ ++ r = qcom_nandc_init(this); ++ if (r) ++ goto err_init; ++ ++ return 0; ++ ++err_init: ++ clk_disable_unprepare(this->aon_clk); ++err_aon_clk: ++ clk_disable_unprepare(this->core_clk); ++err_core_clk: ++ qcom_nandc_unalloc(this); ++ ++ return r; ++} ++ ++static int qcom_nandc_remove(struct platform_device *pdev) ++{ ++ struct qcom_nandc_data *this = platform_get_drvdata(pdev); ++ ++ qcom_nandc_unalloc(this); ++ ++ clk_disable_unprepare(this->aon_clk); ++ clk_disable_unprepare(this->core_clk); ++ ++ return 0; ++} ++ ++#define EBI2_NANDC_ECC_MODES (ECC_RS_4BIT | ECC_BCH_8BIT) ++ ++/* ++ * data will hold a struct pointer containing more differences once we support ++ * more IPs ++ */ ++static const struct of_device_id qcom_nandc_of_match[] = { ++ { .compatible = "qcom,ebi2-nandc", ++ .data = (void *) EBI2_NANDC_ECC_MODES, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, qcom_nandc_of_match); ++ ++static struct platform_driver qcom_nandc_driver = { ++ .driver = { ++ .name = "qcom-nandc", ++ .of_match_table = qcom_nandc_of_match, ++ }, ++ .probe = qcom_nandc_probe, ++ .remove = qcom_nandc_remove, ++}; ++module_platform_driver(qcom_nandc_driver); ++ ++MODULE_AUTHOR("Archit Taneja <architt@codeaurora.org>"); ++MODULE_DESCRIPTION("Qualcomm NAND Controller driver"); ++MODULE_LICENSE("GPL v2"); +--- a/drivers/mtd/nand/Makefile ++++ b/drivers/mtd/nand/Makefile +@@ -55,5 +55,6 @@ + obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o + obj-$(CONFIG_MTD_NAND_HISI504) += hisi504_nand.o + obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/ ++obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o + + nand-objs := nand_base.o nand_bbt.o nand_timings.o diff --git a/target/linux/ipq806x/patches-4.4/163-dt-bindings-qcom_nandc-Add-DT-bindings.patch b/target/linux/ipq806x/patches-4.4/163-dt-bindings-qcom_nandc-Add-DT-bindings.patch new file mode 100644 index 0000000..6530eb1 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/163-dt-bindings-qcom_nandc-Add-DT-bindings.patch @@ -0,0 +1,82 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,3/5] dt/bindings: qcom_nandc: Add DT bindings +From: Archit Taneja <architt@codeaurora.org> +X-Patchwork-Id: 6927141 +Message-Id: <1438578498-32254-4-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja <architt@codeaurora.org>, devicetree@vger.kernel.org +Date: Mon, 3 Aug 2015 10:38:16 +0530 + +Add DT bindings document for the Qualcomm NAND controller driver. + +Cc: devicetree@vger.kernel.org + +v3: +- Don't use '0x' when specifying nand controller address space +- Add optional property for on-flash bbt usage + +Acked-by: Andy Gross <agross@codeaurora.org> +Signed-off-by: Archit Taneja <architt@codeaurora.org> + +--- +.../devicetree/bindings/mtd/qcom_nandc.txt | 49 ++++++++++++++++++++++ + 1 file changed, 49 insertions(+) + create mode 100644 Documentation/devicetree/bindings/mtd/qcom_nandc.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/mtd/qcom_nandc.txt +@@ -0,0 +1,49 @@ ++* Qualcomm NAND controller ++ ++Required properties: ++- compatible: should be "qcom,ebi2-nand" for IPQ806x ++- reg: MMIO address range ++- clocks: must contain core clock and always on clock ++- clock-names: must contain "core" for the core clock and "aon" for the ++ always on clock ++- dmas: DMA specifier, consisting of a phandle to the ADM DMA ++ controller node and the channel number to be used for ++ NAND. Refer to dma.txt and qcom_adm.txt for more details ++- dma-names: must be "rxtx" ++- qcom,cmd-crci: must contain the ADM command type CRCI block instance ++ number specified for the NAND controller on the given ++ platform ++- qcom,data-crci: must contain the ADM data type CRCI block instance ++ number specified for the NAND controller on the given ++ platform ++ ++Optional properties: ++- nand-bus-width: bus width. Must be 8 or 16. If not present, 8 is chosen ++ as default ++ ++- nand-ecc-strength: number of bits to correct per ECC step. Must be 4 or 8 ++ bits. If not present, 4 is chosen as default ++- nand-on-flash-bbt: Create/use on-flash bad block table ++ ++The device tree may optionally contain sub-nodes describing partitions of the ++address space. See partition.txt for more detail. ++ ++Example: ++ ++nand@1ac00000 { ++ compatible = "qcom,ebi2-nandc"; ++ reg = <0x1ac00000 0x800>; ++ ++ clocks = <&gcc EBI2_CLK>, ++ <&gcc EBI2_AON_CLK>; ++ clock-names = "core", "aon"; ++ ++ dmas = <&adm_dma 3>; ++ dma-names = "rxtx"; ++ qcom,cmd-crci = <15>; ++ qcom,data-crci = <3>; ++ ++ partition@0 { ++ ... ++ }; ++}; diff --git a/target/linux/ipq806x/patches-4.4/164-arm-qcom-dts-Add-NAND-controller-node-for-ipq806x.patch b/target/linux/ipq806x/patches-4.4/164-arm-qcom-dts-Add-NAND-controller-node-for-ipq806x.patch new file mode 100644 index 0000000..4fc8a8c --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/164-arm-qcom-dts-Add-NAND-controller-node-for-ipq806x.patch @@ -0,0 +1,51 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,4/5] arm: qcom: dts: Add NAND controller node for ipq806x +From: Archit Taneja <architt@codeaurora.org> +X-Patchwork-Id: 6927121 +Message-Id: <1438578498-32254-5-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja <architt@codeaurora.org>, devicetree@vger.kernel.org +Date: Mon, 3 Aug 2015 10:38:17 +0530 + +The nand controller in IPQ806x is of the 'EBI2 type'. Use the corresponding +compatible string. + +Cc: devicetree@vger.kernel.org + +Reviewed-by: Andy Gross <agross@codeaurora.org> +Signed-off-by: Archit Taneja <architt@codeaurora.org> + +--- +arch/arm/boot/dts/qcom-ipq8064.dtsi | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -747,6 +747,22 @@ + + status = "disabled"; + }; ++ ++ nand@1ac00000 { ++ compatible = "qcom,ebi2-nandc"; ++ reg = <0x1ac00000 0x800>; ++ ++ clocks = <&gcc EBI2_CLK>, ++ <&gcc EBI2_AON_CLK>; ++ clock-names = "core", "aon"; ++ ++ dmas = <&adm_dma 3>; ++ dma-names = "rxtx"; ++ qcom,cmd-crci = <15>; ++ qcom,data-crci = <3>; ++ ++ status = "disabled"; ++ }; + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-4.4/165-arm-qcom-dts-Enable-NAND-node-on-IPQ8064-AP148-platform.patch b/target/linux/ipq806x/patches-4.4/165-arm-qcom-dts-Enable-NAND-node-on-IPQ8064-AP148-platform.patch new file mode 100644 index 0000000..9de3d8f --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/165-arm-qcom-dts-Enable-NAND-node-on-IPQ8064-AP148-platform.patch @@ -0,0 +1,76 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,5/5] arm: qcom: dts: Enable NAND node on IPQ8064 AP148 platform +From: Archit Taneja <architt@codeaurora.org> +X-Patchwork-Id: 6927091 +Message-Id: <1438578498-32254-6-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja <architt@codeaurora.org>, devicetree@vger.kernel.org +Date: Mon, 3 Aug 2015 10:38:18 +0530 + +Enable the NAND controller node on the AP148 platform. Provide pinmux +information. + +Cc: devicetree@vger.kernel.org + +Signed-off-by: Archit Taneja <architt@codeaurora.org> + +--- +arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 36 ++++++++++++++++++++++++++++++++ + 1 file changed, 36 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -43,6 +43,28 @@ + bias-none; + }; + }; ++ nand_pins: nand_pins { ++ mux { ++ pins = "gpio34", "gpio35", "gpio36", ++ "gpio37", "gpio38", "gpio39", ++ "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ function = "nand"; ++ drive-strength = <10>; ++ bias-disable; ++ }; ++ pullups { ++ pins = "gpio39"; ++ bias-pull-up; ++ }; ++ hold { ++ pins = "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ bias-bus-hold; ++ }; ++ }; + }; + + gsbi@16300000 { +@@ -126,5 +148,19 @@ + status = "ok"; + phy-tx0-term-offset = <7>; + }; ++ ++ nand@1ac00000 { ++ status = "ok"; ++ ++ pinctrl-0 = <&nand_pins>; ++ pinctrl-names = "default"; ++ ++ nand-ecc-strength = <4>; ++ nand-bus-width = <8>; ++ }; + }; + }; ++ ++&adm_dma { ++ status = "ok"; ++}; diff --git a/target/linux/ipq806x/patches-4.4/166-arch-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch b/target/linux/ipq806x/patches-4.4/166-arch-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch new file mode 100644 index 0000000..7060282 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/166-arch-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch @@ -0,0 +1,11 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -157,6 +157,8 @@ + + nand-ecc-strength = <4>; + nand-bus-width = <8>; ++ ++ linux,part-probe = "qcom-smem"; + }; + }; + }; diff --git a/target/linux/ipq806x/patches-4.4/300-arch-arm-force-ZRELADDR-on-arch-qcom.patch b/target/linux/ipq806x/patches-4.4/300-arch-arm-force-ZRELADDR-on-arch-qcom.patch new file mode 100644 index 0000000..c0281ff --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/300-arch-arm-force-ZRELADDR-on-arch-qcom.patch @@ -0,0 +1,62 @@ +From b12e230f09d4481424e6a5d7e2ae566b6954e83f Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Wed, 29 Apr 2015 15:21:46 -0700 +Subject: [PATCH] HACK: arch: arm: force ZRELADDR on arch-qcom + +ARCH_QCOM is using the ARCH_MULTIPLATFORM option, as now recommended +on most ARM architectures. This automatically calculate ZRELADDR by +masking PHYS_OFFSET with 0xf8000000. + +However, on IPQ806x, the first ~20MB of RAM is reserved for the hardware +network accelerators, and the bootloader removes this section from the +layout passed from the ATAGS (when used). + +For newer bootloader, when DT is used, this is not a problem, we just +reserve this memory in the device tree. But if the bootloader doesn't +have DT support, then ATAGS have to be used. In this case, the ARM +decompressor will position the kernel in this low mem, which will not be +in the RAM section mapped by the bootloader, which means the kernel will +freeze in the middle of the boot process trying to map the memory. + +As a work around, this patch allows disabling AUTO_ZRELADDR when +ARCH_QCOM is selected. It makes the zImage usage possible on bootloaders +which don't support device-tree, which is the case on certain early +IPQ806x based designs. + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + arch/arm/Kconfig | 2 +- + arch/arm/Makefile | 2 ++ + arch/arm/mach-qcom/Makefile.boot | 1 + + 3 files changed, 4 insertions(+), 1 deletion(-) + create mode 100644 arch/arm/mach-qcom/Makefile.boot + +--- a/arch/arm/Kconfig ++++ b/arch/arm/Kconfig +@@ -323,7 +323,7 @@ + select ARCH_WANT_OPTIONAL_GPIOLIB + select ARM_HAS_SG_CHAIN + select ARM_PATCH_PHYS_VIRT +- select AUTO_ZRELADDR ++ select AUTO_ZRELADDR if !ARCH_QCOM + select CLKSRC_OF + select COMMON_CLK + select GENERIC_CLOCKEVENTS +--- a/arch/arm/Makefile ++++ b/arch/arm/Makefile +@@ -256,9 +256,11 @@ + else + MACHINE := + endif ++ifeq ($(CONFIG_ARCH_QCOM),) + ifeq ($(CONFIG_ARCH_MULTIPLATFORM),y) + MACHINE := + endif ++endif + + machdirs := $(patsubst %,arch/arm/mach-%/,$(machine-y)) + platdirs := $(patsubst %,arch/arm/plat-%/,$(sort $(plat-y))) +--- /dev/null ++++ b/arch/arm/mach-qcom/Makefile.boot +@@ -0,0 +1 @@ ++zreladdr-y+= 0x42208000 diff --git a/target/linux/ipq806x/patches-4.4/302-mtd-qcom-smem-rename-rootfs-ubi.patch b/target/linux/ipq806x/patches-4.4/302-mtd-qcom-smem-rename-rootfs-ubi.patch new file mode 100644 index 0000000..8890a91 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/302-mtd-qcom-smem-rename-rootfs-ubi.patch @@ -0,0 +1,13 @@ +--- a/drivers/mtd/qcom_smem_part.c ++++ b/drivers/mtd/qcom_smem_part.c +@@ -189,6 +189,10 @@ static int parse_qcom_smem_partitions(st + m_part->size = le32_to_cpu(s_part->size) * (*smem_blksz); + m_part->offset = le32_to_cpu(s_part->start) * (*smem_blksz); + ++ /* "rootfs" conflicts with OpenWrt auto mounting */ ++ if (mtd_type_is_nand(master) && !strcmp(m_part->name, "rootfs")) ++ m_part->name = "ubi"; ++ + /* + * The last SMEM partition may have its size marked as + * something like 0xffffffff, which means "until the end of the diff --git a/target/linux/ipq806x/patches-4.4/707-ARM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch b/target/linux/ipq806x/patches-4.4/707-ARM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch new file mode 100644 index 0000000..625ff0a --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/707-ARM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch @@ -0,0 +1,146 @@ +From e81de9d28bd0421c236df322872e64edf4ee1852 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Mon, 11 May 2015 16:32:09 -0700 +Subject: [PATCH 7/8] ARM: dts: qcom: add mdio nodes to ap148 & db149 + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 40 ++++++++++++++++++++++++++- + arch/arm/boot/dts/qcom-ipq8064-db149.dts | 46 ++++++++++++++++++++++++++++++++ + 2 files changed, 85 insertions(+), 1 deletion(-) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -19,8 +19,9 @@ + }; + }; + +- alias { ++ aliases { + serial0 = &uart4; ++ mdio-gpio0 = &mdio0; + }; + + chosen { +@@ -65,6 +66,15 @@ + bias-bus-hold; + }; + }; ++ ++ mdio0_pins: mdio0_pins { ++ mux { ++ pins = "gpio0", "gpio1"; ++ function = "gpio"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi@16300000 { +@@ -160,6 +170,34 @@ + + linux,part-probe = "qcom-smem"; + }; ++ ++ mdio0: mdio { ++ compatible = "virtual,mdio-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ gpios = <&qcom_pinmux 1 0 &qcom_pinmux 0 0>; ++ pinctrl-0 = <&mdio0_pins>; ++ pinctrl-names = "default"; ++ ++ phy0: ethernet-phy@0 { ++ device_type = "ethernet-phy"; ++ reg = <0>; ++ qca,ar8327-initvals = < ++ 0x00004 0x7600000 /* PAD0_MODE */ ++ 0x00008 0x1000000 /* PAD5_MODE */ ++ 0x0000c 0x80 /* PAD6_MODE */ ++ 0x000e4 0xaa545 /* MAC_POWER_SEL */ ++ 0x000e0 0xc74164de /* SGMII_CTRL */ ++ 0x0007c 0x4e /* PORT0_STATUS */ ++ 0x00094 0x4e /* PORT6_STATUS */ ++ >; ++ }; ++ ++ phy4: ethernet-phy@4 { ++ device_type = "ethernet-phy"; ++ reg = <4>; ++ }; ++ }; + }; + }; + +--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -16,6 +16,7 @@ + + alias { + serial0 = &uart2; ++ mdio-gpio0 = &mdio0; + }; + + chosen { +@@ -38,6 +39,15 @@ + bias-none; + }; + }; ++ ++ mdio0_pins: mdio0_pins { ++ mux { ++ pins = "gpio0", "gpio1"; ++ function = "gpio"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi2: gsbi@12480000 { +@@ -140,5 +150,44 @@ + pcie2: pci@1b900000 { + status = "ok"; + }; ++ ++ mdio0: mdio { ++ compatible = "virtual,mdio-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ gpios = <&qcom_pinmux 1 0 &qcom_pinmux 0 0>; ++ ++ pinctrl-0 = <&mdio0_pins>; ++ pinctrl-names = "default"; ++ ++ phy0: ethernet-phy@0 { ++ device_type = "ethernet-phy"; ++ reg = <0>; ++ qca,ar8327-initvals = < ++ 0x00004 0x7600000 /* PAD0_MODE */ ++ 0x00008 0x1000000 /* PAD5_MODE */ ++ 0x0000c 0x80 /* PAD6_MODE */ ++ 0x000e4 0xaa545 /* MAC_POWER_SEL */ ++ 0x000e0 0xc74164de /* SGMII_CTRL */ ++ 0x0007c 0x4e /* PORT0_STATUS */ ++ 0x00094 0x4e /* PORT6_STATUS */ ++ >; ++ }; ++ ++ phy4: ethernet-phy@4 { ++ device_type = "ethernet-phy"; ++ reg = <4>; ++ }; ++ ++ phy6: ethernet-phy@6 { ++ device_type = "ethernet-phy"; ++ reg = <6>; ++ }; ++ ++ phy7: ethernet-phy@7 { ++ device_type = "ethernet-phy"; ++ reg = <7>; ++ }; ++ }; + }; + }; diff --git a/target/linux/ipq806x/patches-4.4/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch b/target/linux/ipq806x/patches-4.4/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch new file mode 100644 index 0000000..691ebb6 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch @@ -0,0 +1,216 @@ +From cab1f4720e82f2e17eaeed9a9ad9e4f07c742977 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Mon, 11 May 2015 12:29:18 -0700 +Subject: [PATCH 8/8] ARM: dts: qcom: add gmac nodes to ipq806x platforms + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 31 ++++++++++++ + arch/arm/boot/dts/qcom-ipq8064-db149.dts | 43 ++++++++++++++++ + arch/arm/boot/dts/qcom-ipq8064.dtsi | 86 ++++++++++++++++++++++++++++++++ + 3 files changed, 160 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -75,6 +75,16 @@ + bias-disable; + }; + }; ++ ++ rgmii2_pins: rgmii2_pins { ++ mux { ++ pins = "gpio27", "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", ++ "gpio51", "gpio52", "gpio59", "gpio60", "gpio61", "gpio62" ; ++ function = "rgmii2"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi@16300000 { +@@ -198,6 +208,31 @@ + reg = <4>; + }; + }; ++ ++ gmac1: ethernet@37200000 { ++ status = "ok"; ++ phy-mode = "rgmii"; ++ qcom,id = <1>; ++ ++ pinctrl-0 = <&rgmii2_pins>; ++ pinctrl-names = "default"; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <2>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; + }; + }; + +--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -48,6 +48,14 @@ + bias-disable; + }; + }; ++ ++ rgmii0_pins: rgmii0_pins { ++ mux { ++ pins = "gpio2", "gpio66"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi2: gsbi@12480000 { +@@ -189,5 +197,40 @@ + reg = <7>; + }; + }; ++ ++ gmac0: ethernet@37000000 { ++ status = "ok"; ++ phy-mode = "rgmii"; ++ qcom,id = <0>; ++ phy-handle = <&phy4>; ++ ++ pinctrl-0 = <&rgmii0_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <1>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <2>; ++ phy-handle = <&phy6>; ++ }; ++ ++ gmac3: ethernet@37600000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <3>; ++ phy-handle = <&phy7>; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -763,6 +763,92 @@ + + status = "disabled"; + }; ++ ++ nss_common: syscon@03000000 { ++ compatible = "syscon"; ++ reg = <0x03000000 0x0000FFFF>; ++ }; ++ ++ qsgmii_csr: syscon@1bb00000 { ++ compatible = "syscon"; ++ reg = <0x1bb00000 0x000001FF>; ++ }; ++ ++ gmac0: ethernet@37000000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37000000 0x200000>; ++ interrupts = <GIC_SPI 220 IRQ_TYPE_LEVEL_HIGH>; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE1_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE1_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37200000 0x200000>; ++ interrupts = <GIC_SPI 223 IRQ_TYPE_LEVEL_HIGH>; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE2_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE2_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37400000 0x200000>; ++ interrupts = <GIC_SPI 226 IRQ_TYPE_LEVEL_HIGH>; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE3_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE3_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; ++ ++ gmac3: ethernet@37600000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37600000 0x200000>; ++ interrupts = <GIC_SPI 229 IRQ_TYPE_LEVEL_HIGH>; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE4_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE4_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-4.4/709-spi-qup-Fix-fifo-and-dma-support-for-IPQ806x.patch b/target/linux/ipq806x/patches-4.4/709-spi-qup-Fix-fifo-and-dma-support-for-IPQ806x.patch new file mode 100644 index 0000000..8c4718e --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/709-spi-qup-Fix-fifo-and-dma-support-for-IPQ806x.patch @@ -0,0 +1,134 @@ +From 16d2871830ff3fe12a6bff582549a9264adff278 Mon Sep 17 00:00:00 2001 +From: Ram Chandra Jangir <rjangi@codeaurora.org> +Date: Tue, 10 May 2016 20:19:31 +0530 +Subject: [PATCH] spi: qup: Fix fifo and dma support for IPQ806x + +Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org> +--- + drivers/spi/spi-qup.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 52 insertions(+), 2 deletions(-) + +diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c +index 810a7fa..0808017 100644 +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -24,6 +24,7 @@ + #include <linux/spi/spi.h> + #include <linux/dmaengine.h> + #include <linux/dma-mapping.h> ++#include <linux/gpio.h> + + #define QUP_CONFIG 0x0000 + #define QUP_STATE 0x0004 +@@ -152,6 +153,7 @@ struct spi_qup { + int use_dma; + struct dma_slave_config rx_conf; + struct dma_slave_config tx_conf; ++ int mode; + }; + + +@@ -370,7 +372,8 @@ static int spi_qup_do_pio(struct spi_master *master, struct spi_transfer *xfer) + return ret; + } + +- spi_qup_fifo_write(qup, xfer); ++ if (qup->mode == QUP_IO_M_MODE_FIFO) ++ spi_qup_fifo_write(qup, xfer); + + return 0; + } +@@ -448,6 +451,7 @@ spi_qup_get_mode(struct spi_master *master, struct spi_transfer *xfer) + { + struct spi_qup *qup = spi_master_get_devdata(master); + u32 mode; ++ size_t dma_align = dma_get_cache_alignment(); + + qup->w_size = 4; + +@@ -458,6 +462,14 @@ spi_qup_get_mode(struct spi_master *master, struct spi_transfer *xfer) + + qup->n_words = xfer->len / qup->w_size; + ++ if (!IS_ERR_OR_NULL(master->dma_rx) && ++ IS_ALIGNED((size_t)xfer->tx_buf, dma_align) && ++ IS_ALIGNED((size_t)xfer->rx_buf, dma_align) && ++ !is_vmalloc_addr(xfer->tx_buf) && ++ !is_vmalloc_addr(xfer->rx_buf) && ++ (xfer->len > 3*qup->in_blk_sz)) ++ qup->use_dma = 1; ++ + if (qup->n_words <= (qup->in_fifo_sz / sizeof(u32))) + mode = QUP_IO_M_MODE_FIFO; + else +@@ -491,7 +503,7 @@ static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer) + return -EIO; + } + +- mode = spi_qup_get_mode(spi->master, xfer); ++ controller->mode = mode = spi_qup_get_mode(spi->master, xfer); + n_words = controller->n_words; + + if (mode == QUP_IO_M_MODE_FIFO) { +@@ -500,6 +512,7 @@ static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer) + /* must be zero for FIFO */ + writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT); + writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT); ++ controller->use_dma = 0; + } else if (!controller->use_dma) { + writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT); + writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT); +@@ -750,6 +763,38 @@ err_tx: + return ret; + } + ++static void spi_qup_set_cs(struct spi_device *spi, bool val) ++{ ++ struct spi_qup *controller; ++ u32 spi_ioc; ++ u32 spi_ioc_orig; ++ ++ controller = spi_master_get_devdata(spi->master); ++ spi_ioc = readl_relaxed(controller->base + SPI_IO_CONTROL); ++ spi_ioc_orig = spi_ioc; ++ if (!val) ++ spi_ioc |= SPI_IO_C_FORCE_CS; ++ else ++ spi_ioc &= ~SPI_IO_C_FORCE_CS; ++ ++ if (spi_ioc != spi_ioc_orig) ++ writel_relaxed(spi_ioc, controller->base + SPI_IO_CONTROL); ++} ++ ++static int spi_qup_setup(struct spi_device *spi) ++{ ++ if (spi->cs_gpio >= 0) { ++ if (spi->mode & SPI_CS_HIGH) ++ gpio_set_value(spi->cs_gpio, 0); ++ else ++ gpio_set_value(spi->cs_gpio, 1); ++ ++ udelay(10); ++ } ++ ++ return 0; ++} ++ + static int spi_qup_probe(struct platform_device *pdev) + { + struct spi_master *master; +@@ -846,6 +891,11 @@ static int spi_qup_probe(struct platform_device *pdev) + if (of_device_is_compatible(dev->of_node, "qcom,spi-qup-v1.1.1")) + controller->qup_v1 = 1; + ++ if (!controller->qup_v1) ++ master->set_cs = spi_qup_set_cs; ++ else ++ master->setup = spi_qup_setup; ++ + spin_lock_init(&controller->lock); + init_completion(&controller->done); + +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/710-watchdog-qcom-set-WDT_BARK_TIME-register-offset-to-o.patch b/target/linux/ipq806x/patches-4.4/710-watchdog-qcom-set-WDT_BARK_TIME-register-offset-to-o.patch new file mode 100644 index 0000000..7573c96 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/710-watchdog-qcom-set-WDT_BARK_TIME-register-offset-to-o.patch @@ -0,0 +1,43 @@ +From abc9f55079169806bcc31f29ec27f7df11c6184c Mon Sep 17 00:00:00 2001 +From: Ram Chandra Jangir <rjangi@codeaurora.org> +Date: Thu, 4 Feb 2016 12:41:56 +0530 +Subject: [PATCH 2/2] watchdog: qcom: set WDT_BARK_TIME register offset to one + second less of bite time + +Currently WDT_BARK_TIME register offset is not configured with bark +timeout during wdt_start,and it is taking bark timeout's default value. +For some versions of TZ (secure mode) will consider a BARK the same +as BITE and reset the board. + +So instead let's just configure the BARK time to be less than a second +of the bite timeout so the board does not reset in this scenario + +Change-Id: Ie09850ad7e0470ed721e6924911ca2a81fd9ff8a +Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org> +--- + drivers/watchdog/qcom-wdt.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c +index 773dcfa..002274a 100644 +--- a/drivers/watchdog/qcom-wdt.c ++++ b/drivers/watchdog/qcom-wdt.c +@@ -22,6 +22,7 @@ + + #define WDT_RST 0x38 + #define WDT_EN 0x40 ++#define WDT_BARK_TIME 0x4C + #define WDT_BITE_TIME 0x5C + + struct qcom_wdt { +@@ -44,6 +45,7 @@ static int qcom_wdt_start(struct watchdog_device *wdd) + + writel(0, wdt->base + WDT_EN); + writel(1, wdt->base + WDT_RST); ++ writel((wdd->timeout - 1) * wdt->rate, wdt->base + WDT_BARK_TIME); + writel(wdd->timeout * wdt->rate, wdt->base + WDT_BITE_TIME); + writel(1, wdt->base + WDT_EN); + return 0; +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/800-ARM-qcom-add-Netgear-Nighthawk-X4-R7500-device-tree.patch b/target/linux/ipq806x/patches-4.4/800-ARM-qcom-add-Netgear-Nighthawk-X4-R7500-device-tree.patch new file mode 100644 index 0000000..1f5eba8 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/800-ARM-qcom-add-Netgear-Nighthawk-X4-R7500-device-tree.patch @@ -0,0 +1,367 @@ +From 7e77aa188a7a7c4391856a9e5ef5ef58f769e679 Mon Sep 17 00:00:00 2001 +From: Jonas Gorski <jogo@openwrt.org> +Date: Sun, 9 Aug 2015 13:02:38 +0200 +Subject: [PATCH] ARM: qcom: add Netgear Nighthawk X4 R7500 device tree + +Signed-off-by: Jonas Gorski <jogo@openwrt.org> +--- + arch/arm/boot/dts/Makefile | 1 + + arch/arm/boot/dts/qcom-ipq8064-r7500.dts | 370 +++++++++++++++++++++++++++++++ + 2 files changed, 371 insertions(+) + create mode 100644 arch/arm/boot/dts/qcom-ipq8064-r7500.dts + +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -452,6 +452,7 @@ dtb-$(CONFIG_ARCH_QCOM) += \ + qcom-apq8084-mtp.dtb \ + qcom-ipq8064-ap148.dtb \ + qcom-ipq8064-db149.dtb \ ++ qcom-ipq8064-r7500.dtb \ + qcom-msm8660-surf.dtb \ + qcom-msm8960-cdp.dtb \ + qcom-msm8974-sony-xperia-honami.dtb +--- /dev/null ++++ b/arch/arm/boot/dts/qcom-ipq8064-r7500.dts +@@ -0,0 +1,342 @@ ++#include "qcom-ipq8064-v1.0.dtsi" ++ ++#include <dt-bindings/input/input.h> ++ ++/ { ++ model = "Netgear Nighthawk X4 R7500"; ++ compatible = "netgear,r7500", "qcom,ipq8064"; ++ ++ memory@0 { ++ reg = <0x42000000 0xe000000>; ++ device_type = "memory"; ++ }; ++ ++ reserved-memory { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ rsvd@41200000 { ++ reg = <0x41200000 0x300000>; ++ no-map; ++ }; ++ }; ++ ++ aliases { ++ serial0 = &uart4; ++ mdio-gpio0 = &mdio0; ++ }; ++ ++ chosen { ++ bootargs = "rootfstype=squashfs noinitrd"; ++ linux,stdout-path = "serial0:115200n8"; ++ }; ++ ++ soc { ++ pinmux@800000 { ++ i2c4_pins: i2c4_pinmux { ++ pins = "gpio12", "gpio13"; ++ function = "gsbi4"; ++ bias-disable; ++ }; ++ ++ nand_pins: nand_pins { ++ mux { ++ pins = "gpio34", "gpio35", "gpio36", ++ "gpio37", "gpio38", "gpio39", ++ "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ function = "nand"; ++ drive-strength = <10>; ++ bias-disable; ++ }; ++ pullups { ++ pins = "gpio39"; ++ bias-pull-up; ++ }; ++ hold { ++ pins = "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ bias-bus-hold; ++ }; ++ }; ++ ++ mdio0_pins: mdio0_pins { ++ mux { ++ pins = "gpio0", "gpio1"; ++ function = "gpio"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; ++ ++ rgmii2_pins: rgmii2_pins { ++ mux { ++ pins = "gpio27", "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", ++ "gpio51", "gpio52", "gpio59", "gpio60", "gpio61", "gpio62" ; ++ function = "rgmii2"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; ++ }; ++ ++ gsbi@16300000 { ++ qcom,mode = <GSBI_PROT_I2C_UART>; ++ status = "ok"; ++ serial@16340000 { ++ status = "ok"; ++ }; ++ /* ++ * The i2c device on gsbi4 should not be enabled. ++ * On ipq806x designs gsbi4 i2c is meant for exclusive ++ * RPM usage. Turning this on in kernel manifests as ++ * i2c failure for the RPM. ++ */ ++ }; ++ ++ sata-phy@1b400000 { ++ status = "ok"; ++ }; ++ ++ sata@29000000 { ++ status = "ok"; ++ }; ++ ++ phy@100f8800 { /* USB3 port 1 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@100f8830 { /* USB3 port 1 SS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8800 { /* USB3 port 0 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8830 { /* USB3 port 0 SS phy */ ++ status = "ok"; ++ }; ++ ++ usb30@0 { ++ status = "ok"; ++ }; ++ ++ usb30@1 { ++ status = "ok"; ++ }; ++ ++ pcie0: pci@1b500000 { ++ status = "ok"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ status = "ok"; ++ }; ++ ++ nand@1ac00000 { ++ status = "ok"; ++ ++ pinctrl-0 = <&nand_pins>; ++ pinctrl-names = "default"; ++ ++ nand-ecc-strength = <4>; ++ nand-bus-width = <8>; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ qcadata@0 { ++ label = "qcadata"; ++ reg = <0x0000000 0x0c80000>; ++ read-only; ++ }; ++ ++ APPSBL@c80000 { ++ label = "APPSBL"; ++ reg = <0x0c80000 0x0500000>; ++ read-only; ++ }; ++ ++ APPSBLENV@1180000 { ++ label = "APPSBLENV"; ++ reg = <0x1180000 0x0080000>; ++ read-only; ++ }; ++ ++ art: art@1200000 { ++ label = "art"; ++ reg = <0x1200000 0x0140000>; ++ read-only; ++ }; ++ ++ kernel@1340000 { ++ label = "kernel"; ++ reg = <0x1340000 0x0200000>; ++ }; ++ ++ ubi@1540000 { ++ label = "ubi"; ++ reg = <0x1540000 0x1800000>; ++ }; ++ ++ netgear@2d40000 { ++ label = "netgear"; ++ reg = <0x2d40000 0x0c00000>; ++ read-only; ++ }; ++ ++ reserve@3940000 { ++ label = "reserve"; ++ reg = <0x3940000 0x46c0000>; ++ read-only; ++ }; ++ ++ firmware@1340000 { ++ label = "firmware"; ++ reg = <0x1340000 0x1a00000>; ++ }; ++ ++ }; ++ ++ mdio0: mdio { ++ compatible = "virtual,mdio-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ gpios = <&qcom_pinmux 1 0 &qcom_pinmux 0 0>; ++ pinctrl-0 = <&mdio0_pins>; ++ pinctrl-names = "default"; ++ ++ phy0: ethernet-phy@0 { ++ device_type = "ethernet-phy"; ++ reg = <0>; ++ qca,ar8327-initvals = < ++ 0x00004 0x7600000 /* PAD0_MODE */ ++ 0x00008 0x1000000 /* PAD5_MODE */ ++ 0x0000c 0x80 /* PAD6_MODE */ ++ 0x000e4 0xaa545 /* MAC_POWER_SEL */ ++ 0x000e0 0xc74164de /* SGMII_CTRL */ ++ 0x0007c 0x4e /* PORT0_STATUS */ ++ 0x00094 0x4e /* PORT6_STATUS */ ++ >; ++ }; ++ ++ phy4: ethernet-phy@4 { ++ device_type = "ethernet-phy"; ++ reg = <4>; ++ }; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ status = "ok"; ++ phy-mode = "rgmii"; ++ qcom,id = <1>; ++ ++ pinctrl-0 = <&rgmii2_pins>; ++ pinctrl-names = "default"; ++ ++ mtd-mac-address = <&art 6>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <2>; ++ ++ mtd-mac-address = <&art 0>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ }; ++ ++ gpio-keys { ++ compatible = "gpio-keys"; ++ ++ wifi { ++ label = "wifi"; ++ gpios = <&qcom_pinmux 6 1>; ++ linux,code = <KEY_WLAN>; ++ }; ++ ++ reset { ++ label = "reset"; ++ gpios = <&qcom_pinmux 54 1>; ++ linux,code = <KEY_RESTART>; ++ }; ++ ++ wps { ++ label = "wps"; ++ gpios = <&qcom_pinmux 65 1>; ++ linux,code = <KEY_WPS_BUTTON>; ++ }; ++ }; ++ ++ gpio-leds { ++ compatible = "gpio-leds"; ++ ++ usb1 { ++ label = "r7500:amber:usb1"; ++ gpios = <&qcom_pinmux 7 0>; ++ }; ++ ++ usb3 { ++ label = "r7500:amber:usb3"; ++ gpios = <&qcom_pinmux 8 0>; ++ }; ++ ++ status { ++ label = "r7500:amber:status"; ++ gpios = <&qcom_pinmux 9 0>; ++ }; ++ ++ internet { ++ label = "r7500:white:internet"; ++ gpios = <&qcom_pinmux 22 0>; ++ }; ++ ++ wan { ++ label = "r7500:white:wan"; ++ gpios = <&qcom_pinmux 23 0>; ++ }; ++ ++ wps { ++ label = "r7500:white:wps"; ++ gpios = <&qcom_pinmux 24 0>; ++ }; ++ ++ esata { ++ label = "r7500:white:esata"; ++ gpios = <&qcom_pinmux 26 0>; ++ }; ++ ++ power { ++ label = "r7500:white:power"; ++ gpios = <&qcom_pinmux 53 0>; ++ default-state = "on"; ++ }; ++ ++ rfkill { ++ label = "r7500:white:rfkill"; ++ gpios = <&qcom_pinmux 64 0>; ++ }; ++ ++ wifi5g { ++ label = "r7500:white:wifi5g"; ++ gpios = <&qcom_pinmux 67 0>; ++ }; ++ }; ++}; ++ ++&adm_dma { ++ status = "ok"; ++}; diff --git a/target/linux/ipq806x/patches-4.4/801-ARM-qcom-add-Netgear-Nighthawk-X4-D7800-device-tree.patch b/target/linux/ipq806x/patches-4.4/801-ARM-qcom-add-Netgear-Nighthawk-X4-D7800-device-tree.patch new file mode 100644 index 0000000..eaf037f --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/801-ARM-qcom-add-Netgear-Nighthawk-X4-D7800-device-tree.patch @@ -0,0 +1,381 @@ +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -362,6 +362,7 @@ dtb-$(CONFIG_ARCH_QCOM) += \ + qcom-ipq8064-ap148.dtb \ + qcom-ipq8064-db149.dtb \ + qcom-ipq8064-r7500.dtb \ ++ qcom-ipq8064-d7800.dtb \ + qcom-msm8660-surf.dtb \ + qcom-msm8960-cdp.dtb \ + qcom-msm8974-sony-xperia-honami.dtb +--- /dev/null ++++ b/arch/arm/boot/dts/qcom-ipq8064-d7800.dts +@@ -0,0 +1,368 @@ ++#include "qcom-ipq8064-v1.0.dtsi" ++ ++#include <dt-bindings/input/input.h> ++ ++/ { ++ model = "Netgear Nighthawk X4 D7800"; ++ compatible = "netgear,d7800", "qcom,ipq8064"; ++ ++ memory@0 { ++ reg = <0x42000000 0xe000000>; ++ device_type = "memory"; ++ }; ++ ++ reserved-memory { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ rsvd@41200000 { ++ reg = <0x41200000 0x300000>; ++ no-map; ++ }; ++ }; ++ ++ aliases { ++ serial0 = &uart4; ++ mdio-gpio0 = &mdio0; ++ }; ++ ++ chosen { ++ bootargs = "rootfstype=squashfs noinitrd"; ++ linux,stdout-path = "serial0:115200n8"; ++ }; ++ ++ soc { ++ pinmux@800000 { ++ i2c4_pins: i2c4_pinmux { ++ pins = "gpio12", "gpio13"; ++ function = "gsbi4"; ++ bias-disable; ++ }; ++ ++ pcie0_pins: pcie0_pinmux { ++ mux { ++ pins = "gpio3"; ++ function = "pcie1_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ pcie1_pins: pcie1_pinmux { ++ mux { ++ pins = "gpio48"; ++ function = "pcie2_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ nand_pins: nand_pins { ++ mux { ++ pins = "gpio34", "gpio35", "gpio36", ++ "gpio37", "gpio38", "gpio39", ++ "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ function = "nand"; ++ drive-strength = <10>; ++ bias-disable; ++ }; ++ pullups { ++ pins = "gpio39"; ++ bias-pull-up; ++ }; ++ hold { ++ pins = "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ bias-bus-hold; ++ }; ++ }; ++ ++ mdio0_pins: mdio0_pins { ++ mux { ++ pins = "gpio0", "gpio1"; ++ function = "gpio"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; ++ ++ rgmii2_pins: rgmii2_pins { ++ mux { ++ pins = "gpio27", "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", ++ "gpio51", "gpio52", "gpio59", "gpio60", "gpio61", "gpio62" ; ++ function = "rgmii2"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; ++ }; ++ ++ gsbi@16300000 { ++ qcom,mode = <GSBI_PROT_I2C_UART>; ++ status = "ok"; ++ serial@16340000 { ++ status = "ok"; ++ }; ++ /* ++ * The i2c device on gsbi4 should not be enabled. ++ * On ipq806x designs gsbi4 i2c is meant for exclusive ++ * RPM usage. Turning this on in kernel manifests as ++ * i2c failure for the RPM. ++ */ ++ }; ++ ++ sata-phy@1b400000 { ++ status = "ok"; ++ }; ++ ++ sata@29000000 { ++ status = "ok"; ++ }; ++ ++ phy@100f8800 { /* USB3 port 1 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@100f8830 { /* USB3 port 1 SS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8800 { /* USB3 port 0 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8830 { /* USB3 port 0 SS phy */ ++ status = "ok"; ++ }; ++ ++ usb30@0 { ++ status = "ok"; ++ }; ++ ++ usb30@1 { ++ status = "ok"; ++ }; ++ ++ pcie0: pci@1b500000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 3 0>; ++ pinctrl-0 = <&pcie0_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 48 0>; ++ pinctrl-0 = <&pcie1_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ nand@1ac00000 { ++ status = "ok"; ++ ++ pinctrl-0 = <&nand_pins>; ++ pinctrl-names = "default"; ++ ++ nand-ecc-strength = <4>; ++ nand-bus-width = <8>; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ qcadata@0 { ++ label = "qcadata"; ++ reg = <0x0000000 0x0c80000>; ++ read-only; ++ }; ++ ++ APPSBL@c80000 { ++ label = "APPSBL"; ++ reg = <0x0c80000 0x0500000>; ++ read-only; ++ }; ++ ++ APPSBLENV@1180000 { ++ label = "APPSBLENV"; ++ reg = <0x1180000 0x0080000>; ++ read-only; ++ }; ++ ++ art: art@1200000 { ++ label = "art"; ++ reg = <0x1200000 0x0140000>; ++ read-only; ++ }; ++ ++ artbak: art@1340000 { ++ label = "artbak"; ++ reg = <0x1340000 0x0140000>; ++ read-only; ++ }; ++ ++ kernel@1480000 { ++ label = "kernel"; ++ reg = <0x1480000 0x0200000>; ++ }; ++ ++ ubi@1680000 { ++ label = "ubi"; ++ reg = <0x1680000 0x1E00000>; ++ }; ++ ++ netgear@3480000 { ++ label = "netgear"; ++ reg = <0x3480000 0x4480000>; ++ read-only; ++ }; ++ ++ reserve@7900000 { ++ label = "reserve"; ++ reg = <0x7900000 0x0700000>; ++ read-only; ++ }; ++ ++ firmware@1480000 { ++ label = "firmware"; ++ reg = <0x1480000 0x2000000>; ++ }; ++ ++ }; ++ ++ mdio0: mdio { ++ compatible = "virtual,mdio-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ gpios = <&qcom_pinmux 1 0 &qcom_pinmux 0 0>; ++ pinctrl-0 = <&mdio0_pins>; ++ pinctrl-names = "default"; ++ ++ phy0: ethernet-phy@0 { ++ device_type = "ethernet-phy"; ++ reg = <0>; ++ qca,ar8327-initvals = < ++ 0x00004 0x7600000 /* PAD0_MODE */ ++ 0x00008 0x1000000 /* PAD5_MODE */ ++ 0x0000c 0x80 /* PAD6_MODE */ ++ 0x000e4 0xaa545 /* MAC_POWER_SEL */ ++ 0x000e0 0xc74164de /* SGMII_CTRL */ ++ 0x0007c 0x4e /* PORT0_STATUS */ ++ 0x00094 0x4e /* PORT6_STATUS */ ++ >; ++ }; ++ ++ phy4: ethernet-phy@4 { ++ device_type = "ethernet-phy"; ++ reg = <4>; ++ }; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ status = "ok"; ++ phy-mode = "rgmii"; ++ phy-handle = <&phy4>; ++ qcom,id = <1>; ++ ++ pinctrl-0 = <&rgmii2_pins>; ++ pinctrl-names = "default"; ++ ++ mtd-mac-address = <&art 6>; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <2>; ++ ++ mtd-mac-address = <&art 0>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ }; ++ ++ gpio-keys { ++ compatible = "gpio-keys"; ++ ++ wifi { ++ label = "wifi"; ++ gpios = <&qcom_pinmux 6 1>; ++ linux,code = <KEY_WLAN>; ++ }; ++ ++ reset { ++ label = "reset"; ++ gpios = <&qcom_pinmux 54 1>; ++ linux,code = <KEY_RESTART>; ++ }; ++ ++ wps { ++ label = "wps"; ++ gpios = <&qcom_pinmux 65 1>; ++ linux,code = <KEY_WPS_BUTTON>; ++ }; ++ }; ++ ++ gpio-leds { ++ compatible = "gpio-leds"; ++ ++ usb1 { ++ label = "d7800:amber:usb1"; ++ gpios = <&qcom_pinmux 7 0>; ++ }; ++ ++ usb3 { ++ label = "d7800:amber:usb3"; ++ gpios = <&qcom_pinmux 8 0>; ++ }; ++ ++ status { ++ label = "d7800:amber:status"; ++ gpios = <&qcom_pinmux 9 0>; ++ }; ++ ++ internet { ++ label = "d7800:white:internet"; ++ gpios = <&qcom_pinmux 22 0>; ++ }; ++ ++ wan { ++ label = "d7800:white:wan"; ++ gpios = <&qcom_pinmux 23 0>; ++ }; ++ ++ wps { ++ label = "d7800:white:wps"; ++ gpios = <&qcom_pinmux 24 0>; ++ }; ++ ++ esata { ++ label = "d7800:white:esata"; ++ gpios = <&qcom_pinmux 26 0>; ++ }; ++ ++ power { ++ label = "d7800:white:power"; ++ gpios = <&qcom_pinmux 53 0>; ++ default-state = "on"; ++ }; ++ ++ rfkill { ++ label = "d7800:white:rfkill"; ++ gpios = <&qcom_pinmux 64 0>; ++ }; ++ ++ wifi5g { ++ label = "d7800:white:wifi5g"; ++ gpios = <&qcom_pinmux 67 0>; ++ }; ++ }; ++}; ++ ++&adm_dma { ++ status = "ok"; ++}; |