From 7f4fbf79f4d4c00466e1b4158b17c5a095c83f2c Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 20 Sep 2017 17:40:16 +0800 Subject: reset: mediatek: add reset controller dt-bindings required header for MT7622 SoC Add the reset controller dt-bindings exported from infracfg, pericfg, hifsys and ethsys which could be found on MT7622 SoC. So that we can reference them from within a device-tree file. Signed-off-by: Sean Wang Signed-off-by: Philipp Zabel --- include/dt-bindings/reset/mt7622-reset.h | 94 ++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 include/dt-bindings/reset/mt7622-reset.h (limited to 'include') diff --git a/include/dt-bindings/reset/mt7622-reset.h b/include/dt-bindings/reset/mt7622-reset.h new file mode 100644 index 000000000000..234052f80417 --- /dev/null +++ b/include/dt-bindings/reset/mt7622-reset.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2017 MediaTek Inc. + * Author: Sean Wang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _DT_BINDINGS_RESET_CONTROLLER_MT7622 +#define _DT_BINDINGS_RESET_CONTROLLER_MT7622 + +/* INFRACFG resets */ +#define MT7622_INFRA_EMI_REG_RST 0 +#define MT7622_INFRA_DRAMC0_A0_RST 1 +#define MT7622_INFRA_APCIRQ_EINT_RST 3 +#define MT7622_INFRA_APXGPT_RST 4 +#define MT7622_INFRA_SCPSYS_RST 5 +#define MT7622_INFRA_PMIC_WRAP_RST 7 +#define MT7622_INFRA_IRRX_RST 9 +#define MT7622_INFRA_EMI_RST 16 +#define MT7622_INFRA_WED0_RST 17 +#define MT7622_INFRA_DRAMC_RST 18 +#define MT7622_INFRA_CCI_INTF_RST 19 +#define MT7622_INFRA_TRNG_RST 21 +#define MT7622_INFRA_SYSIRQ_RST 22 +#define MT7622_INFRA_WED1_RST 25 + +/* PERICFG Subsystem resets */ +#define MT7622_PERI_UART0_SW_RST 0 +#define MT7622_PERI_UART1_SW_RST 1 +#define MT7622_PERI_UART2_SW_RST 2 +#define MT7622_PERI_UART3_SW_RST 3 +#define MT7622_PERI_UART4_SW_RST 4 +#define MT7622_PERI_BTIF_SW_RST 6 +#define MT7622_PERI_PWM_SW_RST 8 +#define MT7622_PERI_AUXADC_SW_RST 10 +#define MT7622_PERI_DMA_SW_RST 11 +#define MT7622_PERI_IRTX_SW_RST 13 +#define MT7622_PERI_NFI_SW_RST 14 +#define MT7622_PERI_THERM_SW_RST 16 +#define MT7622_PERI_MSDC0_SW_RST 19 +#define MT7622_PERI_MSDC1_SW_RST 20 +#define MT7622_PERI_I2C0_SW_RST 22 +#define MT7622_PERI_I2C1_SW_RST 23 +#define MT7622_PERI_I2C2_SW_RST 24 +#define MT7622_PERI_SPI0_SW_RST 33 +#define MT7622_PERI_SPI1_SW_RST 34 +#define MT7622_PERI_FLASHIF_SW_RST 36 + +/* TOPRGU resets */ +#define MT7622_TOPRGU_INFRA_RST 0 +#define MT7622_TOPRGU_ETHDMA_RST 1 +#define MT7622_TOPRGU_DDRPHY_RST 6 +#define MT7622_TOPRGU_INFRA_AO_RST 8 +#define MT7622_TOPRGU_CONN_RST 9 +#define MT7622_TOPRGU_APMIXED_RST 10 +#define MT7622_TOPRGU_CONN_MCU_RST 12 + +/* PCIe/SATA Subsystem resets */ +#define MT7622_SATA_PHY_REG_RST 12 +#define MT7622_SATA_PHY_SW_RST 13 +#define MT7622_SATA_AXI_BUS_RST 15 +#define MT7622_PCIE1_CORE_RST 19 +#define MT7622_PCIE1_MMIO_RST 20 +#define MT7622_PCIE1_HRST 21 +#define MT7622_PCIE1_USER_RST 22 +#define MT7622_PCIE1_PIPE_RST 23 +#define MT7622_PCIE0_CORE_RST 27 +#define MT7622_PCIE0_MMIO_RST 28 +#define MT7622_PCIE0_HRST 29 +#define MT7622_PCIE0_USER_RST 30 +#define MT7622_PCIE0_PIPE_RST 31 + +/* SSUSB Subsystem resets */ +#define MT7622_SSUSB_PHY_PWR_RST 3 +#define MT7622_SSUSB_MAC_PWR_RST 4 + +/* ETHSYS Subsystem resets */ +#define MT7622_ETHSYS_SYS_RST 0 +#define MT7622_ETHSYS_MCM_RST 2 +#define MT7622_ETHSYS_HSDMA_RST 5 +#define MT7622_ETHSYS_FE_RST 6 +#define MT7622_ETHSYS_GMAC_RST 23 +#define MT7622_ETHSYS_EPHY_RST 24 +#define MT7622_ETHSYS_CRYPTO_RST 29 +#define MT7622_ETHSYS_PPE_RST 31 + +#endif /* _DT_BINDINGS_RESET_CONTROLLER_MT7622 */ -- cgit v1.2.3 From 4e659dbe2d02a56ca0df25c77e099760252a329c Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 14 Aug 2017 15:46:17 -0700 Subject: firmware: qcom: scm: Expose secure IO service The secure IO service provides operations for reading and writing secure memory from non-secure mode, expose this API through SCM. Reviewed-by: Stephen Boyd Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/firmware/qcom_scm-32.c | 18 ++++++++++++++++++ drivers/firmware/qcom_scm-64.c | 31 +++++++++++++++++++++++++++++++ drivers/firmware/qcom_scm.c | 12 ++++++++++++ drivers/firmware/qcom_scm.h | 6 ++++++ include/linux/qcom_scm.h | 4 ++++ 5 files changed, 71 insertions(+) (limited to 'include') diff --git a/drivers/firmware/qcom_scm-32.c b/drivers/firmware/qcom_scm-32.c index 93e3b96b6dfa..11fdb1584823 100644 --- a/drivers/firmware/qcom_scm-32.c +++ b/drivers/firmware/qcom_scm-32.c @@ -596,3 +596,21 @@ int __qcom_scm_iommu_secure_ptbl_init(struct device *dev, u64 addr, u32 size, { return -ENODEV; } + +int __qcom_scm_io_readl(struct device *dev, phys_addr_t addr, + unsigned int *val) +{ + int ret; + + ret = qcom_scm_call_atomic1(QCOM_SCM_SVC_IO, QCOM_SCM_IO_READ, addr); + if (ret >= 0) + *val = ret; + + return ret < 0 ? ret : 0; +} + +int __qcom_scm_io_writel(struct device *dev, phys_addr_t addr, unsigned int val) +{ + return qcom_scm_call_atomic2(QCOM_SCM_SVC_IO, QCOM_SCM_IO_WRITE, + addr, val); +} diff --git a/drivers/firmware/qcom_scm-64.c b/drivers/firmware/qcom_scm-64.c index 6e6d561708e2..bf50fb59852e 100644 --- a/drivers/firmware/qcom_scm-64.c +++ b/drivers/firmware/qcom_scm-64.c @@ -439,3 +439,34 @@ int __qcom_scm_iommu_secure_ptbl_init(struct device *dev, u64 addr, u32 size, return ret; } + +int __qcom_scm_io_readl(struct device *dev, phys_addr_t addr, + unsigned int *val) +{ + struct qcom_scm_desc desc = {0}; + struct arm_smccc_res res; + int ret; + + desc.args[0] = addr; + desc.arginfo = QCOM_SCM_ARGS(1); + + ret = qcom_scm_call(dev, QCOM_SCM_SVC_IO, QCOM_SCM_IO_READ, + &desc, &res); + if (ret >= 0) + *val = res.a1; + + return ret < 0 ? ret : 0; +} + +int __qcom_scm_io_writel(struct device *dev, phys_addr_t addr, unsigned int val) +{ + struct qcom_scm_desc desc = {0}; + struct arm_smccc_res res; + + desc.args[0] = addr; + desc.args[1] = val; + desc.arginfo = QCOM_SCM_ARGS(2); + + return qcom_scm_call(dev, QCOM_SCM_SVC_IO, QCOM_SCM_IO_WRITE, + &desc, &res); +} diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c index bb16510d75ba..e18d63935648 100644 --- a/drivers/firmware/qcom_scm.c +++ b/drivers/firmware/qcom_scm.c @@ -333,6 +333,18 @@ int qcom_scm_iommu_secure_ptbl_init(u64 addr, u32 size, u32 spare) } EXPORT_SYMBOL(qcom_scm_iommu_secure_ptbl_init); +int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val) +{ + return __qcom_scm_io_readl(__scm->dev, addr, val); +} +EXPORT_SYMBOL(qcom_scm_io_readl); + +int qcom_scm_io_writel(phys_addr_t addr, unsigned int val) +{ + return __qcom_scm_io_writel(__scm->dev, addr, val); +} +EXPORT_SYMBOL(qcom_scm_io_writel); + /** * qcom_scm_is_available() - Checks if SCM is available */ diff --git a/drivers/firmware/qcom_scm.h b/drivers/firmware/qcom_scm.h index 9bea691f30fb..a60e4b9b1394 100644 --- a/drivers/firmware/qcom_scm.h +++ b/drivers/firmware/qcom_scm.h @@ -30,6 +30,12 @@ extern int __qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus); #define QCOM_SCM_CMD_CORE_HOTPLUGGED 0x10 extern void __qcom_scm_cpu_power_down(u32 flags); +#define QCOM_SCM_SVC_IO 0x5 +#define QCOM_SCM_IO_READ 0x1 +#define QCOM_SCM_IO_WRITE 0x2 +extern int __qcom_scm_io_readl(struct device *dev, phys_addr_t addr, unsigned int *val); +extern int __qcom_scm_io_writel(struct device *dev, phys_addr_t addr, unsigned int val); + #define QCOM_SCM_SVC_INFO 0x6 #define QCOM_IS_CALL_AVAIL_CMD 0x1 extern int __qcom_scm_is_call_available(struct device *dev, u32 svc_id, diff --git a/include/linux/qcom_scm.h b/include/linux/qcom_scm.h index e5380471c2cd..e8357f570695 100644 --- a/include/linux/qcom_scm.h +++ b/include/linux/qcom_scm.h @@ -43,6 +43,8 @@ extern int qcom_scm_set_remote_state(u32 state, u32 id); extern int qcom_scm_restore_sec_cfg(u32 device_id, u32 spare); extern int qcom_scm_iommu_secure_ptbl_size(u32 spare, size_t *size); extern int qcom_scm_iommu_secure_ptbl_init(u64 addr, u32 size, u32 spare); +extern int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val); +extern int qcom_scm_io_writel(phys_addr_t addr, unsigned int val); #else static inline int qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus) @@ -73,5 +75,7 @@ qcom_scm_set_remote_state(u32 state,u32 id) { return -ENODEV; } static inline int qcom_scm_restore_sec_cfg(u32 device_id, u32 spare) { return -ENODEV; } static inline int qcom_scm_iommu_secure_ptbl_size(u32 spare, size_t *size) { return -ENODEV; } static inline int qcom_scm_iommu_secure_ptbl_init(u64 addr, u32 size, u32 spare) { return -ENODEV; } +static inline int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val) { return -ENODEV; } +static inline int qcom_scm_io_writel(phys_addr_t addr, unsigned int val) { return -ENODEV; } #endif #endif -- cgit v1.2.3 From 370d010f0ef09db7ab157a6b5d6d9a737b148f2a Mon Sep 17 00:00:00 2001 From: Timo Alho Date: Thu, 7 Sep 2017 12:31:01 +0300 Subject: firmware: tegra: Propagate error code to caller Response messages from Tegra BPMP firmware contain an error return code as the first word of payload. The error code is used to indicate incorrectly formatted request message or use of non-existing resource (clk, reset, powergate) identifier. Current implementation of tegra_bpmp_transfer() ignores this code and does not pass it to caller. Fix this by adding an extra struct member to tegra_bpmp_message and populate that with return code. Signed-off-by: Timo Alho Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/firmware/tegra/bpmp.c | 22 ++++++++++++++++------ include/soc/tegra/bpmp.h | 1 + 2 files changed, 17 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index 73ca55b7b7ec..33683b5a0d48 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -194,16 +194,24 @@ static int tegra_bpmp_wait_master_free(struct tegra_bpmp_channel *channel) } static ssize_t __tegra_bpmp_channel_read(struct tegra_bpmp_channel *channel, - void *data, size_t size) + void *data, size_t size, int *ret) { + int err; + if (data && size > 0) memcpy(data, channel->ib->data, size); - return tegra_ivc_read_advance(channel->ivc); + err = tegra_ivc_read_advance(channel->ivc); + if (err < 0) + return err; + + *ret = channel->ib->code; + + return 0; } static ssize_t tegra_bpmp_channel_read(struct tegra_bpmp_channel *channel, - void *data, size_t size) + void *data, size_t size, int *ret) { struct tegra_bpmp *bpmp = channel->bpmp; unsigned long flags; @@ -217,7 +225,7 @@ static ssize_t tegra_bpmp_channel_read(struct tegra_bpmp_channel *channel, } spin_lock_irqsave(&bpmp->lock, flags); - err = __tegra_bpmp_channel_read(channel, data, size); + err = __tegra_bpmp_channel_read(channel, data, size, ret); clear_bit(index, bpmp->threaded.allocated); spin_unlock_irqrestore(&bpmp->lock, flags); @@ -337,7 +345,8 @@ int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp, if (err < 0) return err; - return __tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size); + return __tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size, + &msg->rx.ret); } EXPORT_SYMBOL_GPL(tegra_bpmp_transfer_atomic); @@ -371,7 +380,8 @@ int tegra_bpmp_transfer(struct tegra_bpmp *bpmp, if (err == 0) return -ETIMEDOUT; - return tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size); + return tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size, + &msg->rx.ret); } EXPORT_SYMBOL_GPL(tegra_bpmp_transfer); diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h index 9ba65222bd3f..57519f4c126a 100644 --- a/include/soc/tegra/bpmp.h +++ b/include/soc/tegra/bpmp.h @@ -110,6 +110,7 @@ struct tegra_bpmp_message { struct { void *data; size_t size; + int ret; } rx; }; -- cgit v1.2.3 From 0d96a4f6a03797b73bee465cada39133b7972e8d Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Fri, 6 Oct 2017 13:45:53 +0200 Subject: memory: omap-gpmc: Drop gpmc_status This field is no longer used, drop it. Signed-off-by: Ladislav Michl Signed-off-by: Roger Quadros --- drivers/memory/omap-gpmc.c | 1 - include/linux/platform_data/mtd-nand-omap2.h | 2 -- 2 files changed, 3 deletions(-) (limited to 'include') diff --git a/drivers/memory/omap-gpmc.c b/drivers/memory/omap-gpmc.c index 7059bbda2fac..f2aef0b87bc6 100644 --- a/drivers/memory/omap-gpmc.c +++ b/drivers/memory/omap-gpmc.c @@ -1079,7 +1079,6 @@ void gpmc_update_nand_reg(struct gpmc_nand_regs *reg, int cs) { int i; - reg->gpmc_status = NULL; /* deprecated */ reg->gpmc_nand_command = gpmc_base + GPMC_CS0_OFFSET + GPMC_CS_NAND_COMMAND + GPMC_CS_SIZE * cs; reg->gpmc_nand_address = gpmc_base + GPMC_CS0_OFFSET + diff --git a/include/linux/platform_data/mtd-nand-omap2.h b/include/linux/platform_data/mtd-nand-omap2.h index 17d57a18bac5..25e267f1970c 100644 --- a/include/linux/platform_data/mtd-nand-omap2.h +++ b/include/linux/platform_data/mtd-nand-omap2.h @@ -63,8 +63,6 @@ struct gpmc_nand_regs { void __iomem *gpmc_bch_result4[GPMC_BCH_NUM_REMAINDER]; void __iomem *gpmc_bch_result5[GPMC_BCH_NUM_REMAINDER]; void __iomem *gpmc_bch_result6[GPMC_BCH_NUM_REMAINDER]; - /* Deprecated. Do not use */ - void __iomem *gpmc_status; }; struct omap_nand_platform_data { -- cgit v1.2.3 From 2e1e09ed423781d209c1fea773b1c3e614550ea5 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Mon, 24 Jul 2017 19:29:16 +0300 Subject: firmware: tegra: Expose tegra_bpmp_mrq_return() Expose and export the tegra_bpmp_mrq_return() function for use by drivers outside the core BPMP driver. This function is used to reply to messages originating from the BPMP, which is required in the thermal driver. Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding --- drivers/firmware/tegra/bpmp.c | 5 +++-- include/soc/tegra/bpmp.h | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index 33683b5a0d48..aa4bbbd7bdb4 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -397,8 +397,8 @@ static struct tegra_bpmp_mrq *tegra_bpmp_find_mrq(struct tegra_bpmp *bpmp, return NULL; } -static void tegra_bpmp_mrq_return(struct tegra_bpmp_channel *channel, - int code, const void *data, size_t size) +void tegra_bpmp_mrq_return(struct tegra_bpmp_channel *channel, int code, + const void *data, size_t size) { unsigned long flags = channel->ib->flags; struct tegra_bpmp *bpmp = channel->bpmp; @@ -436,6 +436,7 @@ static void tegra_bpmp_mrq_return(struct tegra_bpmp_channel *channel, mbox_client_txdone(bpmp->mbox.channel, 0); } } +EXPORT_SYMBOL_GPL(tegra_bpmp_mrq_return); static void tegra_bpmp_handle_mrq(struct tegra_bpmp *bpmp, unsigned int mrq, diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h index 57519f4c126a..b382ed01af09 100644 --- a/include/soc/tegra/bpmp.h +++ b/include/soc/tegra/bpmp.h @@ -118,6 +118,8 @@ int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp, struct tegra_bpmp_message *msg); int tegra_bpmp_transfer(struct tegra_bpmp *bpmp, struct tegra_bpmp_message *msg); +void tegra_bpmp_mrq_return(struct tegra_bpmp_channel *channel, int code, + const void *data, size_t size); int tegra_bpmp_request_mrq(struct tegra_bpmp *bpmp, unsigned int mrq, tegra_bpmp_mrq_handler_t handler, void *data); -- cgit v1.2.3 From 80d47a91e5db96e5db391aaad414fb6ceb40e7c0 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Mon, 24 Jul 2017 19:29:17 +0300 Subject: firmware: tegra: Add stubs when BPMP not enabled Add static inline stubs to bpmp.h when CONFIG_BPMP is not enabled. This allows building BPMP-related drivers with COMPILE_TEST. Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding --- include/soc/tegra/bpmp.h | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h index b382ed01af09..1cf210504814 100644 --- a/include/soc/tegra/bpmp.h +++ b/include/soc/tegra/bpmp.h @@ -96,9 +96,6 @@ struct tegra_bpmp { struct genpd_onecell_data genpd; }; -struct tegra_bpmp *tegra_bpmp_get(struct device *dev); -void tegra_bpmp_put(struct tegra_bpmp *bpmp); - struct tegra_bpmp_message { unsigned int mrq; @@ -114,6 +111,9 @@ struct tegra_bpmp_message { } rx; }; +#if IS_ENABLED(CONFIG_TEGRA_BPMP) +struct tegra_bpmp *tegra_bpmp_get(struct device *dev); +void tegra_bpmp_put(struct tegra_bpmp *bpmp); int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp, struct tegra_bpmp_message *msg); int tegra_bpmp_transfer(struct tegra_bpmp *bpmp, @@ -125,6 +125,42 @@ int tegra_bpmp_request_mrq(struct tegra_bpmp *bpmp, unsigned int mrq, tegra_bpmp_mrq_handler_t handler, void *data); void tegra_bpmp_free_mrq(struct tegra_bpmp *bpmp, unsigned int mrq, void *data); +#else +static inline struct tegra_bpmp *tegra_bpmp_get(struct device *dev) +{ + return ERR_PTR(-ENOTSUPP); +} +static inline void tegra_bpmp_put(struct tegra_bpmp *bpmp) +{ +} +static inline int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp, + struct tegra_bpmp_message *msg) +{ + return -ENOTSUPP; +} +static inline int tegra_bpmp_transfer(struct tegra_bpmp *bpmp, + struct tegra_bpmp_message *msg) +{ + return -ENOTSUPP; +} +static inline void tegra_bpmp_mrq_return(struct tegra_bpmp_channel *channel, + int code, const void *data, + size_t size) +{ +} + +static inline int tegra_bpmp_request_mrq(struct tegra_bpmp *bpmp, + unsigned int mrq, + tegra_bpmp_mrq_handler_t handler, + void *data) +{ + return -ENOTSUPP; +} +static inline void tegra_bpmp_free_mrq(struct tegra_bpmp *bpmp, + unsigned int mrq, void *data) +{ +} +#endif #if IS_ENABLED(CONFIG_CLK_TEGRA_BPMP) int tegra_bpmp_init_clocks(struct tegra_bpmp *bpmp); -- cgit v1.2.3 From f2381f652266fabfb7a8f5c4b2a05de36cad3a73 Mon Sep 17 00:00:00 2001 From: Timo Alho Date: Tue, 3 Oct 2017 09:12:13 +0300 Subject: firmware: tegra: Add BPMP debugfs support Tegra power management firmware running on the co-processor (BPMP) implements a simple pseudo file system akin to debugfs. The file system can be used for debugging purposes to examine and change the status of selected resources controlled by the firmware (such as clocks, resets, voltages, powergates, ...). Add support to "mirror" the firmware's file system to debugfs. At boot, query firmware for a list of all possible files and create corresponding debugfs entries. Read/write of individual files is implemented by sending a Message ReQuest (MRQ) that passes the full file path name and data to firmware via DRAM. Signed-off-by: Timo Alho Reviewed-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/firmware/tegra/Makefile | 4 +- drivers/firmware/tegra/bpmp-debugfs.c | 444 ++++++++++++++++++++++++++++++++++ drivers/firmware/tegra/bpmp.c | 4 + include/soc/tegra/bpmp.h | 14 ++ 4 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/tegra/bpmp-debugfs.c (limited to 'include') diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile index e34a2f79e1ad..1b826dcca719 100644 --- a/drivers/firmware/tegra/Makefile +++ b/drivers/firmware/tegra/Makefile @@ -1,2 +1,4 @@ -obj-$(CONFIG_TEGRA_BPMP) += bpmp.o +tegra-bpmp-y = bpmp.o +tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp-debugfs.o +obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o obj-$(CONFIG_TEGRA_IVC) += ivc.o diff --git a/drivers/firmware/tegra/bpmp-debugfs.c b/drivers/firmware/tegra/bpmp-debugfs.c new file mode 100644 index 000000000000..f7f6a0a5cb07 --- /dev/null +++ b/drivers/firmware/tegra/bpmp-debugfs.c @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#include +#include +#include + +#include +#include + +struct seqbuf { + char *buf; + size_t pos; + size_t size; +}; + +static void seqbuf_init(struct seqbuf *seqbuf, void *buf, size_t size) +{ + seqbuf->buf = buf; + seqbuf->size = size; + seqbuf->pos = 0; +} + +static size_t seqbuf_avail(struct seqbuf *seqbuf) +{ + return seqbuf->pos < seqbuf->size ? seqbuf->size - seqbuf->pos : 0; +} + +static size_t seqbuf_status(struct seqbuf *seqbuf) +{ + return seqbuf->pos <= seqbuf->size ? 0 : -EOVERFLOW; +} + +static int seqbuf_eof(struct seqbuf *seqbuf) +{ + return seqbuf->pos >= seqbuf->size; +} + +static int seqbuf_read(struct seqbuf *seqbuf, void *buf, size_t nbyte) +{ + nbyte = min(nbyte, seqbuf_avail(seqbuf)); + memcpy(buf, seqbuf->buf + seqbuf->pos, nbyte); + seqbuf->pos += nbyte; + return seqbuf_status(seqbuf); +} + +static int seqbuf_read_u32(struct seqbuf *seqbuf, uint32_t *v) +{ + int err; + + err = seqbuf_read(seqbuf, v, 4); + *v = le32_to_cpu(*v); + return err; +} + +static int seqbuf_read_str(struct seqbuf *seqbuf, const char **str) +{ + *str = seqbuf->buf + seqbuf->pos; + seqbuf->pos += strnlen(*str, seqbuf_avail(seqbuf)); + seqbuf->pos++; + return seqbuf_status(seqbuf); +} + +static void seqbuf_seek(struct seqbuf *seqbuf, ssize_t offset) +{ + seqbuf->pos += offset; +} + +/* map filename in Linux debugfs to corresponding entry in BPMP */ +static const char *get_filename(struct tegra_bpmp *bpmp, + const struct file *file, char *buf, int size) +{ + char root_path_buf[512]; + const char *root_path; + const char *filename; + size_t root_len; + + root_path = dentry_path(bpmp->debugfs_mirror, root_path_buf, + sizeof(root_path_buf)); + if (IS_ERR(root_path)) + return NULL; + + root_len = strlen(root_path); + + filename = dentry_path(file->f_path.dentry, buf, size); + if (IS_ERR(filename)) + return NULL; + + if (strlen(filename) < root_len || + strncmp(filename, root_path, root_len)) + return NULL; + + filename += root_len; + + return filename; +} + +static int mrq_debugfs_read(struct tegra_bpmp *bpmp, + dma_addr_t name, size_t sz_name, + dma_addr_t data, size_t sz_data, + size_t *nbytes) +{ + struct mrq_debugfs_request req = { + .cmd = cpu_to_le32(CMD_DEBUGFS_READ), + .fop = { + .fnameaddr = cpu_to_le32((uint32_t)name), + .fnamelen = cpu_to_le32((uint32_t)sz_name), + .dataaddr = cpu_to_le32((uint32_t)data), + .datalen = cpu_to_le32((uint32_t)sz_data), + }, + }; + struct mrq_debugfs_response resp; + struct tegra_bpmp_message msg = { + .mrq = MRQ_DEBUGFS, + .tx = { + .data = &req, + .size = sizeof(req), + }, + .rx = { + .data = &resp, + .size = sizeof(resp), + }, + }; + int err; + + err = tegra_bpmp_transfer(bpmp, &msg); + if (err < 0) + return err; + + *nbytes = (size_t)resp.fop.nbytes; + + return 0; +} + +static int mrq_debugfs_write(struct tegra_bpmp *bpmp, + dma_addr_t name, size_t sz_name, + dma_addr_t data, size_t sz_data) +{ + const struct mrq_debugfs_request req = { + .cmd = cpu_to_le32(CMD_DEBUGFS_WRITE), + .fop = { + .fnameaddr = cpu_to_le32((uint32_t)name), + .fnamelen = cpu_to_le32((uint32_t)sz_name), + .dataaddr = cpu_to_le32((uint32_t)data), + .datalen = cpu_to_le32((uint32_t)sz_data), + }, + }; + struct tegra_bpmp_message msg = { + .mrq = MRQ_DEBUGFS, + .tx = { + .data = &req, + .size = sizeof(req), + }, + }; + + return tegra_bpmp_transfer(bpmp, &msg); +} + +static int mrq_debugfs_dumpdir(struct tegra_bpmp *bpmp, dma_addr_t addr, + size_t size, size_t *nbytes) +{ + const struct mrq_debugfs_request req = { + .cmd = cpu_to_le32(CMD_DEBUGFS_DUMPDIR), + .dumpdir = { + .dataaddr = cpu_to_le32((uint32_t)addr), + .datalen = cpu_to_le32((uint32_t)size), + }, + }; + struct mrq_debugfs_response resp; + struct tegra_bpmp_message msg = { + .mrq = MRQ_DEBUGFS, + .tx = { + .data = &req, + .size = sizeof(req), + }, + .rx = { + .data = &resp, + .size = sizeof(resp), + }, + }; + int err; + + err = tegra_bpmp_transfer(bpmp, &msg); + if (err < 0) + return err; + + *nbytes = (size_t)resp.dumpdir.nbytes; + + return 0; +} + +static int debugfs_show(struct seq_file *m, void *p) +{ + struct file *file = m->private; + struct inode *inode = file_inode(file); + struct tegra_bpmp *bpmp = inode->i_private; + const size_t datasize = m->size; + const size_t namesize = SZ_256; + void *datavirt, *namevirt; + dma_addr_t dataphys, namephys; + char buf[256]; + const char *filename; + size_t len, nbytes; + int ret; + + filename = get_filename(bpmp, file, buf, sizeof(buf)); + if (!filename) + return -ENOENT; + + namevirt = dma_alloc_coherent(bpmp->dev, namesize, &namephys, + GFP_KERNEL | GFP_DMA32); + if (!namevirt) + return -ENOMEM; + + datavirt = dma_alloc_coherent(bpmp->dev, datasize, &dataphys, + GFP_KERNEL | GFP_DMA32); + if (!datavirt) { + ret = -ENOMEM; + goto free_namebuf; + } + + len = strlen(filename); + strncpy(namevirt, filename, namesize); + + ret = mrq_debugfs_read(bpmp, namephys, len, dataphys, datasize, + &nbytes); + + if (!ret) + seq_write(m, datavirt, nbytes); + + dma_free_coherent(bpmp->dev, datasize, datavirt, dataphys); +free_namebuf: + dma_free_coherent(bpmp->dev, namesize, namevirt, namephys); + + return ret; +} + +static int debugfs_open(struct inode *inode, struct file *file) +{ + return single_open_size(file, debugfs_show, file, SZ_128K); +} + +static ssize_t debugfs_store(struct file *file, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct inode *inode = file_inode(file); + struct tegra_bpmp *bpmp = inode->i_private; + const size_t datasize = count; + const size_t namesize = SZ_256; + void *datavirt, *namevirt; + dma_addr_t dataphys, namephys; + char fnamebuf[256]; + const char *filename; + size_t len; + int ret; + + filename = get_filename(bpmp, file, fnamebuf, sizeof(fnamebuf)); + if (!filename) + return -ENOENT; + + namevirt = dma_alloc_coherent(bpmp->dev, namesize, &namephys, + GFP_KERNEL | GFP_DMA32); + if (!namevirt) + return -ENOMEM; + + datavirt = dma_alloc_coherent(bpmp->dev, datasize, &dataphys, + GFP_KERNEL | GFP_DMA32); + if (!datavirt) { + ret = -ENOMEM; + goto free_namebuf; + } + + len = strlen(filename); + strncpy(namevirt, filename, namesize); + + if (copy_from_user(datavirt, buf, count)) { + ret = -EFAULT; + goto free_databuf; + } + + ret = mrq_debugfs_write(bpmp, namephys, len, dataphys, + count); + +free_databuf: + dma_free_coherent(bpmp->dev, datasize, datavirt, dataphys); +free_namebuf: + dma_free_coherent(bpmp->dev, namesize, namevirt, namephys); + + return ret ?: count; +} + +static const struct file_operations debugfs_fops = { + .open = debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .write = debugfs_store, + .release = single_release, +}; + +static int bpmp_populate_dir(struct tegra_bpmp *bpmp, struct seqbuf *seqbuf, + struct dentry *parent, uint32_t depth) +{ + int err; + uint32_t d, t; + const char *name; + struct dentry *dentry; + + while (!seqbuf_eof(seqbuf)) { + err = seqbuf_read_u32(seqbuf, &d); + if (err < 0) + return err; + + if (d < depth) { + seqbuf_seek(seqbuf, -4); + /* go up a level */ + return 0; + } else if (d != depth) { + /* malformed data received from BPMP */ + return -EIO; + } + + err = seqbuf_read_u32(seqbuf, &t); + if (err < 0) + return err; + err = seqbuf_read_str(seqbuf, &name); + if (err < 0) + return err; + + if (t & DEBUGFS_S_ISDIR) { + dentry = debugfs_create_dir(name, parent); + if (!dentry) + return -ENOMEM; + err = bpmp_populate_dir(bpmp, seqbuf, dentry, depth+1); + if (err < 0) + return err; + } else { + umode_t mode; + + mode = t & DEBUGFS_S_IRUSR ? S_IRUSR : 0; + mode |= t & DEBUGFS_S_IWUSR ? S_IWUSR : 0; + dentry = debugfs_create_file(name, mode, + parent, bpmp, + &debugfs_fops); + if (!dentry) + return -ENOMEM; + } + } + + return 0; +} + +static int create_debugfs_mirror(struct tegra_bpmp *bpmp, void *buf, + size_t bufsize, struct dentry *root) +{ + struct seqbuf seqbuf; + int err; + + bpmp->debugfs_mirror = debugfs_create_dir("debug", root); + if (!bpmp->debugfs_mirror) + return -ENOMEM; + + seqbuf_init(&seqbuf, buf, bufsize); + err = bpmp_populate_dir(bpmp, &seqbuf, bpmp->debugfs_mirror, 0); + if (err < 0) { + debugfs_remove_recursive(bpmp->debugfs_mirror); + bpmp->debugfs_mirror = NULL; + } + + return err; +} + +static int mrq_is_supported(struct tegra_bpmp *bpmp, unsigned int mrq) +{ + struct mrq_query_abi_request req = { .mrq = cpu_to_le32(mrq) }; + struct mrq_query_abi_response resp; + struct tegra_bpmp_message msg = { + .mrq = MRQ_QUERY_ABI, + .tx = { + .data = &req, + .size = sizeof(req), + }, + .rx = { + .data = &resp, + .size = sizeof(resp), + }, + }; + int ret; + + ret = tegra_bpmp_transfer(bpmp, &msg); + if (ret < 0) { + /* something went wrong; assume not supported */ + dev_warn(bpmp->dev, "tegra_bpmp_transfer failed (%d)\n", ret); + return 0; + } + + return resp.status ? 0 : 1; +} + +int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp) +{ + dma_addr_t phys; + void *virt; + const size_t sz = SZ_256K; + size_t nbytes; + int ret; + struct dentry *root; + + if (!mrq_is_supported(bpmp, MRQ_DEBUGFS)) + return 0; + + root = debugfs_create_dir("bpmp", NULL); + if (!root) + return -ENOMEM; + + virt = dma_alloc_coherent(bpmp->dev, sz, &phys, + GFP_KERNEL | GFP_DMA32); + if (!virt) { + ret = -ENOMEM; + goto out; + } + + ret = mrq_debugfs_dumpdir(bpmp, phys, sz, &nbytes); + if (ret < 0) + goto free; + + ret = create_debugfs_mirror(bpmp, virt, nbytes, root); +free: + dma_free_coherent(bpmp->dev, sz, virt, phys); +out: + if (ret < 0) + debugfs_remove(root); + + return ret; +} diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index aa4bbbd7bdb4..a7f461f2e650 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -835,6 +835,10 @@ static int tegra_bpmp_probe(struct platform_device *pdev) if (err < 0) goto free_mrq; + err = tegra_bpmp_init_debugfs(bpmp); + if (err < 0) + dev_err(&pdev->dev, "debugfs initialization failed: %d\n", err); + return 0; free_mrq: diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h index 1cf210504814..aeae4466dd25 100644 --- a/include/soc/tegra/bpmp.h +++ b/include/soc/tegra/bpmp.h @@ -94,6 +94,10 @@ struct tegra_bpmp { struct reset_controller_dev rstc; struct genpd_onecell_data genpd; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_mirror; +#endif }; struct tegra_bpmp_message { @@ -189,4 +193,14 @@ static inline int tegra_bpmp_init_powergates(struct tegra_bpmp *bpmp) } #endif +#if IS_ENABLED(CONFIG_DEBUG_FS) +int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp); +#else +static inline int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp) +{ + return 0; +} +#endif + + #endif /* __SOC_TEGRA_BPMP_H */ -- cgit v1.2.3 From eb297bc716ec2cb3147e6e99002e37058c65cba3 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Tue, 10 Oct 2017 22:08:54 -0700 Subject: of: reserved_mem: Accessor for acquiring reserved_mem In some cases drivers referencing a reserved-memory region might want to remap the entire region, but when defining the reserved-memory by "size" the client driver has no means to know the associated base address of the reserved memory region. This patch adds an accessor for such drivers to acquire a handle to their associated reserved-memory for this purpose. A complicating factor for the implementation is that the reserved_mem objects are created from the flattened DeviceTree, as such we can't use the device_node address for comparison. Fortunately the name of the node will be used as "name" of the reserved_mem and will be used when building the full_name, so we can compare the "name" with the basename of the full_name to find the match. Reviewed-by: Rob Herring Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/of/of_reserved_mem.c | 26 ++++++++++++++++++++++++++ include/linux/of_reserved_mem.h | 5 +++++ 2 files changed, 31 insertions(+) (limited to 'include') diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index d507c3569a88..b108c7a1f74c 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -397,3 +397,29 @@ void of_reserved_mem_device_release(struct device *dev) rmem->ops->device_release(rmem, dev); } EXPORT_SYMBOL_GPL(of_reserved_mem_device_release); + +/** + * of_reserved_mem_lookup() - acquire reserved_mem from a device node + * @np: node pointer of the desired reserved-memory region + * + * This function allows drivers to acquire a reference to the reserved_mem + * struct based on a device node handle. + * + * Returns a reserved_mem reference, or NULL on error. + */ +struct reserved_mem *of_reserved_mem_lookup(struct device_node *np) +{ + const char *name; + int i; + + if (!np->full_name) + return NULL; + + name = kbasename(np->full_name); + for (i = 0; i < reserved_mem_count; i++) + if (!strcmp(reserved_mem[i].name, name)) + return &reserved_mem[i]; + + return NULL; +} +EXPORT_SYMBOL_GPL(of_reserved_mem_lookup); diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h index f8e1992d6423..c58f780104f9 100644 --- a/include/linux/of_reserved_mem.h +++ b/include/linux/of_reserved_mem.h @@ -44,6 +44,7 @@ int early_init_dt_alloc_reserved_memory_arch(phys_addr_t size, void fdt_init_reserved_mem(void); void fdt_reserved_mem_save_node(unsigned long node, const char *uname, phys_addr_t base, phys_addr_t size); +struct reserved_mem *of_reserved_mem_lookup(struct device_node *np); #else static inline int of_reserved_mem_device_init_by_idx(struct device *dev, struct device_node *np, int idx) @@ -55,6 +56,10 @@ static inline void of_reserved_mem_device_release(struct device *pdev) { } static inline void fdt_init_reserved_mem(void) { } static inline void fdt_reserved_mem_save_node(unsigned long node, const char *uname, phys_addr_t base, phys_addr_t size) { } +static inline struct reserved_mem *of_reserved_mem_lookup(struct device_node *np) +{ + return NULL; +} #endif /** -- cgit v1.2.3 From a622c641665c32a879348efb2abc389527479ee4 Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Wed, 25 Oct 2017 20:42:57 +0200 Subject: memory: omap-gpmc: Remove deprecated gpmc_update_nand_reg() Deprecated gpmc_update_nand_reg() is no longer used, remove. Signed-off-by: Ladislav Michl Signed-off-by: Roger Quadros --- drivers/memory/omap-gpmc.c | 53 +++++++++++++++++++++------------------------- include/linux/omap-gpmc.h | 12 ----------- 2 files changed, 24 insertions(+), 41 deletions(-) (limited to 'include') diff --git a/drivers/memory/omap-gpmc.c b/drivers/memory/omap-gpmc.c index f2aef0b87bc6..a385a35c7de9 100644 --- a/drivers/memory/omap-gpmc.c +++ b/drivers/memory/omap-gpmc.c @@ -1075,10 +1075,33 @@ int gpmc_configure(int cmd, int wval) } EXPORT_SYMBOL(gpmc_configure); -void gpmc_update_nand_reg(struct gpmc_nand_regs *reg, int cs) +static bool gpmc_nand_writebuffer_empty(void) +{ + if (gpmc_read_reg(GPMC_STATUS) & GPMC_STATUS_EMPTYWRITEBUFFERSTATUS) + return true; + + return false; +} + +static struct gpmc_nand_ops nand_ops = { + .nand_writebuffer_empty = gpmc_nand_writebuffer_empty, +}; + +/** + * gpmc_omap_get_nand_ops - Get the GPMC NAND interface + * @regs: the GPMC NAND register map exclusive for NAND use. + * @cs: GPMC chip select number on which the NAND sits. The + * register map returned will be specific to this chip select. + * + * Returns NULL on error e.g. invalid cs. + */ +struct gpmc_nand_ops *gpmc_omap_get_nand_ops(struct gpmc_nand_regs *reg, int cs) { int i; + if (cs >= gpmc_cs_num) + return NULL; + reg->gpmc_nand_command = gpmc_base + GPMC_CS0_OFFSET + GPMC_CS_NAND_COMMAND + GPMC_CS_SIZE * cs; reg->gpmc_nand_address = gpmc_base + GPMC_CS0_OFFSET + @@ -1110,34 +1133,6 @@ void gpmc_update_nand_reg(struct gpmc_nand_regs *reg, int cs) reg->gpmc_bch_result6[i] = gpmc_base + GPMC_ECC_BCH_RESULT_6 + i * GPMC_BCH_SIZE; } -} - -static bool gpmc_nand_writebuffer_empty(void) -{ - if (gpmc_read_reg(GPMC_STATUS) & GPMC_STATUS_EMPTYWRITEBUFFERSTATUS) - return true; - - return false; -} - -static struct gpmc_nand_ops nand_ops = { - .nand_writebuffer_empty = gpmc_nand_writebuffer_empty, -}; - -/** - * gpmc_omap_get_nand_ops - Get the GPMC NAND interface - * @regs: the GPMC NAND register map exclusive for NAND use. - * @cs: GPMC chip select number on which the NAND sits. The - * register map returned will be specific to this chip select. - * - * Returns NULL on error e.g. invalid cs. - */ -struct gpmc_nand_ops *gpmc_omap_get_nand_ops(struct gpmc_nand_regs *reg, int cs) -{ - if (cs >= gpmc_cs_num) - return NULL; - - gpmc_update_nand_reg(reg, cs); return &nand_ops; } diff --git a/include/linux/omap-gpmc.h b/include/linux/omap-gpmc.h index fd0de00c0d77..edfa280c3d56 100644 --- a/include/linux/omap-gpmc.h +++ b/include/linux/omap-gpmc.h @@ -36,18 +36,6 @@ static inline struct gpmc_nand_ops *gpmc_omap_get_nand_ops(struct gpmc_nand_regs } #endif /* CONFIG_OMAP_GPMC */ -/*--------------------------------*/ - -/* deprecated APIs */ -#if IS_ENABLED(CONFIG_OMAP_GPMC) -void gpmc_update_nand_reg(struct gpmc_nand_regs *reg, int cs); -#else -static inline void gpmc_update_nand_reg(struct gpmc_nand_regs *reg, int cs) -{ -} -#endif /* CONFIG_OMAP_GPMC */ -/*--------------------------------*/ - extern int gpmc_calc_timings(struct gpmc_timings *gpmc_t, struct gpmc_settings *gpmc_s, struct gpmc_device_timings *dev_t); -- cgit v1.2.3 From 5b143d2a6edeae59700420c948f7d793da3356a8 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdelin Date: Wed, 1 Nov 2017 13:14:33 -0400 Subject: bus: add driver for the Technologic Systems NBUS This driver implements a GPIOs bit-banged bus, called the NBUS by Technologic Systems. It is used to communicate with the peripherals in the FPGA on the TS-4600 SoM. Signed-off-by: Sebastien Bourdelin Acked-by: Linus Walleij Signed-off-by: Arnd Bergmann --- drivers/bus/Kconfig | 8 ++ drivers/bus/Makefile | 1 + drivers/bus/ts-nbus.c | 375 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/ts-nbus.h | 18 +++ 4 files changed, 402 insertions(+) create mode 100644 drivers/bus/ts-nbus.c create mode 100644 include/linux/ts-nbus.h (limited to 'include') diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index ae3d8f3444b9..854348e646b3 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -157,6 +157,14 @@ config TEGRA_GMI Driver for the Tegra Generic Memory Interface bus which can be used to attach devices such as NOR, UART, FPGA and more. +config TS_NBUS + tristate "Technologic Systems NBUS Driver" + depends on SOC_IMX28 + depends on OF_GPIO && PWM + help + Driver for the Technologic Systems NBUS which is used to interface + with the peripherals in the FPGA of the TS-4600 SoM. + config UNIPHIER_SYSTEM_BUS tristate "UniPhier System Bus driver" depends on ARCH_UNIPHIER && OF diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index cc6364bec054..72377f77651c 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_SUNXI_RSB) += sunxi-rsb.o obj-$(CONFIG_SIMPLE_PM_BUS) += simple-pm-bus.o obj-$(CONFIG_TEGRA_ACONNECT) += tegra-aconnect.o obj-$(CONFIG_TEGRA_GMI) += tegra-gmi.o +obj-$(CONFIG_TS_NBUS) += ts-nbus.o obj-$(CONFIG_UNIPHIER_SYSTEM_BUS) += uniphier-system-bus.o obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o diff --git a/drivers/bus/ts-nbus.c b/drivers/bus/ts-nbus.c new file mode 100644 index 000000000000..073fd9011154 --- /dev/null +++ b/drivers/bus/ts-nbus.c @@ -0,0 +1,375 @@ +/* + * NBUS driver for TS-4600 based boards + * + * Copyright (c) 2016 - Savoir-faire Linux + * Author: Sebastien Bourdelin + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + * + * This driver implements a GPIOs bit-banged bus, called the NBUS by Technologic + * Systems. It is used to communicate with the peripherals in the FPGA on the + * TS-4600 SoM. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TS_NBUS_DIRECTION_IN 0 +#define TS_NBUS_DIRECTION_OUT 1 +#define TS_NBUS_WRITE_ADR 0 +#define TS_NBUS_WRITE_VAL 1 + +struct ts_nbus { + struct pwm_device *pwm; + struct gpio_descs *data; + struct gpio_desc *csn; + struct gpio_desc *txrx; + struct gpio_desc *strobe; + struct gpio_desc *ale; + struct gpio_desc *rdy; + struct mutex lock; +}; + +/* + * request all gpios required by the bus. + */ +static int ts_nbus_init_pdata(struct platform_device *pdev, struct ts_nbus + *ts_nbus) +{ + ts_nbus->data = devm_gpiod_get_array(&pdev->dev, "ts,data", + GPIOD_OUT_HIGH); + if (IS_ERR(ts_nbus->data)) { + dev_err(&pdev->dev, "failed to retrieve ts,data-gpio from dts\n"); + return PTR_ERR(ts_nbus->data); + } + + ts_nbus->csn = devm_gpiod_get(&pdev->dev, "ts,csn", GPIOD_OUT_HIGH); + if (IS_ERR(ts_nbus->csn)) { + dev_err(&pdev->dev, "failed to retrieve ts,csn-gpio from dts\n"); + return PTR_ERR(ts_nbus->csn); + } + + ts_nbus->txrx = devm_gpiod_get(&pdev->dev, "ts,txrx", GPIOD_OUT_HIGH); + if (IS_ERR(ts_nbus->txrx)) { + dev_err(&pdev->dev, "failed to retrieve ts,txrx-gpio from dts\n"); + return PTR_ERR(ts_nbus->txrx); + } + + ts_nbus->strobe = devm_gpiod_get(&pdev->dev, "ts,strobe", GPIOD_OUT_HIGH); + if (IS_ERR(ts_nbus->strobe)) { + dev_err(&pdev->dev, "failed to retrieve ts,strobe-gpio from dts\n"); + return PTR_ERR(ts_nbus->strobe); + } + + ts_nbus->ale = devm_gpiod_get(&pdev->dev, "ts,ale", GPIOD_OUT_HIGH); + if (IS_ERR(ts_nbus->ale)) { + dev_err(&pdev->dev, "failed to retrieve ts,ale-gpio from dts\n"); + return PTR_ERR(ts_nbus->ale); + } + + ts_nbus->rdy = devm_gpiod_get(&pdev->dev, "ts,rdy", GPIOD_IN); + if (IS_ERR(ts_nbus->rdy)) { + dev_err(&pdev->dev, "failed to retrieve ts,rdy-gpio from dts\n"); + return PTR_ERR(ts_nbus->rdy); + } + + return 0; +} + +/* + * the data gpios are used for reading and writing values, their directions + * should be adjusted accordingly. + */ +static void ts_nbus_set_direction(struct ts_nbus *ts_nbus, int direction) +{ + int i; + + for (i = 0; i < 8; i++) { + if (direction == TS_NBUS_DIRECTION_IN) + gpiod_direction_input(ts_nbus->data->desc[i]); + else + /* when used as output the default state of the data + * lines are set to high */ + gpiod_direction_output(ts_nbus->data->desc[i], 1); + } +} + +/* + * reset the bus in its initial state. + * The data, csn, strobe and ale lines must be zero'ed to let the FPGA knows a + * new transaction can be process. + */ +static void ts_nbus_reset_bus(struct ts_nbus *ts_nbus) +{ + int i; + int values[8]; + + for (i = 0; i < 8; i++) + values[i] = 0; + + gpiod_set_array_value_cansleep(8, ts_nbus->data->desc, values); + gpiod_set_value_cansleep(ts_nbus->csn, 0); + gpiod_set_value_cansleep(ts_nbus->strobe, 0); + gpiod_set_value_cansleep(ts_nbus->ale, 0); +} + +/* + * let the FPGA knows it can process. + */ +static void ts_nbus_start_transaction(struct ts_nbus *ts_nbus) +{ + gpiod_set_value_cansleep(ts_nbus->strobe, 1); +} + +/* + * read a byte value from the data gpios. + * return 0 on success or negative errno on failure. + */ +static int ts_nbus_read_byte(struct ts_nbus *ts_nbus, u8 *val) +{ + struct gpio_descs *gpios = ts_nbus->data; + int ret, i; + + *val = 0; + for (i = 0; i < 8; i++) { + ret = gpiod_get_value_cansleep(gpios->desc[i]); + if (ret < 0) + return ret; + if (ret) + *val |= BIT(i); + } + + return 0; +} + +/* + * set the data gpios accordingly to the byte value. + */ +static void ts_nbus_write_byte(struct ts_nbus *ts_nbus, u8 byte) +{ + struct gpio_descs *gpios = ts_nbus->data; + int i; + int values[8]; + + for (i = 0; i < 8; i++) + if (byte & BIT(i)) + values[i] = 1; + else + values[i] = 0; + + gpiod_set_array_value_cansleep(8, gpios->desc, values); +} + +/* + * reading the bus consists of resetting the bus, then notifying the FPGA to + * send the data in the data gpios and return the read value. + * return 0 on success or negative errno on failure. + */ +static int ts_nbus_read_bus(struct ts_nbus *ts_nbus, u8 *val) +{ + ts_nbus_reset_bus(ts_nbus); + ts_nbus_start_transaction(ts_nbus); + + return ts_nbus_read_byte(ts_nbus, val); +} + +/* + * writing to the bus consists of resetting the bus, then define the type of + * command (address/value), write the data and notify the FPGA to retrieve the + * value in the data gpios. + */ +static void ts_nbus_write_bus(struct ts_nbus *ts_nbus, int cmd, u8 val) +{ + ts_nbus_reset_bus(ts_nbus); + + if (cmd == TS_NBUS_WRITE_ADR) + gpiod_set_value_cansleep(ts_nbus->ale, 1); + + ts_nbus_write_byte(ts_nbus, val); + ts_nbus_start_transaction(ts_nbus); +} + +/* + * read the value in the FPGA register at the given address. + * return 0 on success or negative errno on failure. + */ +int ts_nbus_read(struct ts_nbus *ts_nbus, u8 adr, u16 *val) +{ + int ret, i; + u8 byte; + + /* bus access must be atomic */ + mutex_lock(&ts_nbus->lock); + + /* set the bus in read mode */ + gpiod_set_value_cansleep(ts_nbus->txrx, 0); + + /* write address */ + ts_nbus_write_bus(ts_nbus, TS_NBUS_WRITE_ADR, adr); + + /* set the data gpios direction as input before reading */ + ts_nbus_set_direction(ts_nbus, TS_NBUS_DIRECTION_IN); + + /* reading value MSB first */ + do { + *val = 0; + byte = 0; + for (i = 1; i >= 0; i--) { + /* read a byte from the bus, leave on error */ + ret = ts_nbus_read_bus(ts_nbus, &byte); + if (ret < 0) + goto err; + + /* append the byte read to the final value */ + *val |= byte << (i * 8); + } + gpiod_set_value_cansleep(ts_nbus->csn, 1); + ret = gpiod_get_value_cansleep(ts_nbus->rdy); + } while (ret); + +err: + /* restore the data gpios direction as output after reading */ + ts_nbus_set_direction(ts_nbus, TS_NBUS_DIRECTION_OUT); + + mutex_unlock(&ts_nbus->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(ts_nbus_read); + +/* + * write the desired value in the FPGA register at the given address. + */ +int ts_nbus_write(struct ts_nbus *ts_nbus, u8 adr, u16 val) +{ + int i; + + /* bus access must be atomic */ + mutex_lock(&ts_nbus->lock); + + /* set the bus in write mode */ + gpiod_set_value_cansleep(ts_nbus->txrx, 1); + + /* write address */ + ts_nbus_write_bus(ts_nbus, TS_NBUS_WRITE_ADR, adr); + + /* writing value MSB first */ + for (i = 1; i >= 0; i--) + ts_nbus_write_bus(ts_nbus, TS_NBUS_WRITE_VAL, (u8)(val >> (i * 8))); + + /* wait for completion */ + gpiod_set_value_cansleep(ts_nbus->csn, 1); + while (gpiod_get_value_cansleep(ts_nbus->rdy) != 0) { + gpiod_set_value_cansleep(ts_nbus->csn, 0); + gpiod_set_value_cansleep(ts_nbus->csn, 1); + } + + mutex_unlock(&ts_nbus->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(ts_nbus_write); + +static int ts_nbus_probe(struct platform_device *pdev) +{ + struct pwm_device *pwm; + struct pwm_args pargs; + struct device *dev = &pdev->dev; + struct ts_nbus *ts_nbus; + int ret; + + ts_nbus = devm_kzalloc(dev, sizeof(*ts_nbus), GFP_KERNEL); + if (!ts_nbus) + return -ENOMEM; + + mutex_init(&ts_nbus->lock); + + ret = ts_nbus_init_pdata(pdev, ts_nbus); + if (ret < 0) + return ret; + + pwm = devm_pwm_get(dev, NULL); + if (IS_ERR(pwm)) { + ret = PTR_ERR(pwm); + if (ret != -EPROBE_DEFER) + dev_err(dev, "unable to request PWM\n"); + return ret; + } + + pwm_get_args(pwm, &pargs); + if (!pargs.period) { + dev_err(&pdev->dev, "invalid PWM period\n"); + return -EINVAL; + } + + /* + * FIXME: pwm_apply_args() should be removed when switching to + * the atomic PWM API. + */ + pwm_apply_args(pwm); + ret = pwm_config(pwm, pargs.period, pargs.period); + if (ret < 0) + return ret; + + /* + * we can now start the FPGA and populate the peripherals. + */ + pwm_enable(pwm); + ts_nbus->pwm = pwm; + + /* + * let the child nodes retrieve this instance of the ts-nbus. + */ + dev_set_drvdata(dev, ts_nbus); + + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (ret < 0) + return ret; + + dev_info(dev, "initialized\n"); + + return 0; +} + +static int ts_nbus_remove(struct platform_device *pdev) +{ + struct ts_nbus *ts_nbus = dev_get_drvdata(&pdev->dev); + + /* shutdown the FPGA */ + mutex_lock(&ts_nbus->lock); + pwm_disable(ts_nbus->pwm); + mutex_unlock(&ts_nbus->lock); + + return 0; +} + +static const struct of_device_id ts_nbus_of_match[] = { + { .compatible = "technologic,ts-nbus", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ts_nbus_of_match); + +static struct platform_driver ts_nbus_driver = { + .probe = ts_nbus_probe, + .remove = ts_nbus_remove, + .driver = { + .name = "ts_nbus", + .of_match_table = ts_nbus_of_match, + }, +}; + +module_platform_driver(ts_nbus_driver); + +MODULE_ALIAS("platform:ts_nbus"); +MODULE_AUTHOR("Sebastien Bourdelin "); +MODULE_DESCRIPTION("Technologic Systems NBUS"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/ts-nbus.h b/include/linux/ts-nbus.h new file mode 100644 index 000000000000..5bd4c822f7cf --- /dev/null +++ b/include/linux/ts-nbus.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2016 - Savoir-faire Linux + * Author: Sebastien Bourdelin + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef _TS_NBUS_H +#define _TS_NBUS_H + +struct ts_nbus; + +extern int ts_nbus_read(struct ts_nbus *ts_nbus, u8 adr, u16 *val); +extern int ts_nbus_write(struct ts_nbus *ts_nbus, u8 adr, u16 val); + +#endif /* _TS_NBUS_H */ -- cgit v1.2.3