Message ID | 20200526125928.17096-6-Sergey.Semin@baikalelectronics.ru (mailing list archive) |
---|---|
State | Mainlined |
Headers | show |
Series | bus/memory: Add Baikal-T1 SoC APB/AXI/L2 drivers | expand |
Hi Serge, I love your patch! Yet something to improve: [auto build test ERROR on robh/for-next] [also build test ERROR on char-misc/char-misc-testing staging/staging-testing linus/master v5.7-rc7 next-20200526] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system. BTW, we also suggest to use '--base' option to specify the base tree in git format-patch, please see https://stackoverflow.com/a/37406982] url: https://github.com/0day-ci/linux/commits/Serge-Semin/bus-memory-Add-Baikal-T1-SoC-APB-AXI-L2-drivers/20200526-210837 base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next config: i386-allyesconfig (attached as .config) compiler: gcc-9 (Debian 9.3.0-13) 9.3.0 reproduce (this is a W=1 build): # save the attached .config to linux build tree make W=1 ARCH=i386 If you fix the issue, kindly add following tag as appropriate Reported-by: kbuild test robot <lkp@intel.com> All errors (new ones prefixed by >>, old ones prefixed by <<): >> drivers/bus/bt1-apb.c:404:34: error: array type has incomplete element type 'struct of_device_id' 404 | static const struct of_device_id bt1_apb_of_match[] = { | ^~~~~~~~~~~~~~~~ >> drivers/bus/bt1-apb.c:405:4: error: field name not in record or union initializer 405 | { .compatible = "baikal,bt1-apb" }, | ^ drivers/bus/bt1-apb.c:405:4: note: (near initialization for 'bt1_apb_of_match') drivers/bus/bt1-apb.c:404:34: warning: 'bt1_apb_of_match' defined but not used [-Wunused-variable] 404 | static const struct of_device_id bt1_apb_of_match[] = { | ^~~~~~~~~~~~~~~~ vim +404 drivers/bus/bt1-apb.c 403 > 404 static const struct of_device_id bt1_apb_of_match[] = { > 405 { .compatible = "baikal,bt1-apb" }, 406 { } 407 }; 408 MODULE_DEVICE_TABLE(of, bt1_apb_of_match); 409 --- 0-DAY CI Kernel Test Service, Intel Corporation https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
Hi Serge, I love your patch! Yet something to improve: [auto build test ERROR on robh/for-next] [also build test ERROR on char-misc/char-misc-testing staging/staging-testing linus/master v5.7-rc7 next-20200526] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system. BTW, we also suggest to use '--base' option to specify the base tree in git format-patch, please see https://stackoverflow.com/a/37406982] url: https://github.com/0day-ci/linux/commits/Serge-Semin/bus-memory-Add-Baikal-T1-SoC-APB-AXI-L2-drivers/20200526-210837 base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next config: sparc-allyesconfig (attached as .config) compiler: sparc64-linux-gcc (GCC) 9.3.0 reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # save the attached .config to linux build tree COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross ARCH=sparc If you fix the issue, kindly add following tag as appropriate Reported-by: kbuild test robot <lkp@intel.com> All error/warnings (new ones prefixed by >>, old ones prefixed by <<): drivers/bus/bt1-apb.c: In function 'inject_error_store': drivers/bus/bt1-apb.c:329:3: error: implicit declaration of function 'readl' [-Werror=implicit-function-declaration] 329 | readl(apb->res); | ^~~~~ In file included from include/linux/kobject.h:20, from include/linux/module.h:20, from drivers/bus/bt1-apb.c:12: drivers/bus/bt1-apb.c: At top level: >> drivers/bus/bt1-apb.c:338:23: error: initialization of 'ssize_t (*)(struct device *, struct device_attribute *, char *)' {aka 'long int (*)(struct device *, struct device_attribute *, char *)'} from incompatible pointer type 'int (*)(struct device *, struct device_attribute *, char *)' [-Werror=incompatible-pointer-types] 338 | static DEVICE_ATTR_RW(inject_error); | ^~~~~~~~~~~~ include/linux/sysfs.h:104:10: note: in definition of macro '__ATTR' 104 | .show = _show, | ^~~~~ include/linux/device.h:130:45: note: in expansion of macro '__ATTR_RW' 130 | struct device_attribute dev_attr_##_name = __ATTR_RW(_name) | ^~~~~~~~~ >> drivers/bus/bt1-apb.c:338:8: note: in expansion of macro 'DEVICE_ATTR_RW' 338 | static DEVICE_ATTR_RW(inject_error); | ^~~~~~~~~~~~~~ drivers/bus/bt1-apb.c:338:23: note: (near initialization for 'dev_attr_inject_error.show') 338 | static DEVICE_ATTR_RW(inject_error); | ^~~~~~~~~~~~ include/linux/sysfs.h:104:10: note: in definition of macro '__ATTR' 104 | .show = _show, | ^~~~~ include/linux/device.h:130:45: note: in expansion of macro '__ATTR_RW' 130 | struct device_attribute dev_attr_##_name = __ATTR_RW(_name) | ^~~~~~~~~ >> drivers/bus/bt1-apb.c:338:8: note: in expansion of macro 'DEVICE_ATTR_RW' 338 | static DEVICE_ATTR_RW(inject_error); | ^~~~~~~~~~~~~~ >> drivers/bus/bt1-apb.c:338:23: error: initialization of 'ssize_t (*)(struct device *, struct device_attribute *, const char *, size_t)' {aka 'long int (*)(struct device *, struct device_attribute *, const char *, long unsigned int)'} from incompatible pointer type 'int (*)(struct device *, struct device_attribute *, const char *, size_t)' {aka 'int (*)(struct device *, struct device_attribute *, const char *, long unsigned int)'} [-Werror=incompatible-pointer-types] 338 | static DEVICE_ATTR_RW(inject_error); | ^~~~~~~~~~~~ include/linux/sysfs.h:105:11: note: in definition of macro '__ATTR' 105 | .store = _store, | ^~~~~~ include/linux/device.h:130:45: note: in expansion of macro '__ATTR_RW' 130 | struct device_attribute dev_attr_##_name = __ATTR_RW(_name) | ^~~~~~~~~ >> drivers/bus/bt1-apb.c:338:8: note: in expansion of macro 'DEVICE_ATTR_RW' 338 | static DEVICE_ATTR_RW(inject_error); | ^~~~~~~~~~~~~~ drivers/bus/bt1-apb.c:338:23: note: (near initialization for 'dev_attr_inject_error.store') 338 | static DEVICE_ATTR_RW(inject_error); | ^~~~~~~~~~~~ include/linux/sysfs.h:105:11: note: in definition of macro '__ATTR' 105 | .store = _store, | ^~~~~~ include/linux/device.h:130:45: note: in expansion of macro '__ATTR_RW' 130 | struct device_attribute dev_attr_##_name = __ATTR_RW(_name) | ^~~~~~~~~ >> drivers/bus/bt1-apb.c:338:8: note: in expansion of macro 'DEVICE_ATTR_RW' 338 | static DEVICE_ATTR_RW(inject_error); | ^~~~~~~~~~~~~~ cc1: some warnings being treated as errors vim +338 drivers/bus/bt1-apb.c 317 318 static int inject_error_store(struct device *dev, 319 struct device_attribute *attr, 320 const char *data, size_t count) 321 { 322 struct bt1_apb *apb = dev_get_drvdata(dev); 323 324 /* 325 * Either dummy read from the unmapped address in the APB IO area 326 * or manually set the IRQ status. 327 */ 328 if (!strncmp(data, "nodev", 5)) 329 readl(apb->res); 330 else if (!strncmp(data, "irq", 3)) 331 regmap_update_bits(apb->regs, APB_EHB_ISR, APB_EHB_ISR_PENDING, 332 APB_EHB_ISR_PENDING); 333 else 334 return -EINVAL; 335 336 return count; 337 } > 338 static DEVICE_ATTR_RW(inject_error); 339 --- 0-DAY CI Kernel Test Service, Intel Corporation https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
On Tue, May 26, 2020 at 2:59 PM Serge Semin <Sergey.Semin@baikalelectronics.ru> wrote: > > Baikal-T1 AXI-APB bridge is used to access the SoC subsystem CSRs. > IO requests are routed to this bus by means of the DW AMBA 3 AXI > Interconnect. In case if an attempted APB transaction stays with no > response for a pre-defined time an interrupt occurs and the bus gets > freed for a next operation. This driver provides the interrupt handler > to detect the erroneous address, prints an error message about the > address fault, updates an errors counter. The counter and the APB-bus > operations timeout can be accessed via corresponding sysfs nodes. > A dedicated sysfs-node can be also used to artificially cause the > bus errors described above. > > Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru> > Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru> > Cc: Paul Burton <paulburton@kernel.org> > Cc: Olof Johansson <olof@lixom.net> > Cc: Rob Herring <robh+dt@kernel.org> > Cc: linux-mips@vger.kernel.org > Cc: soc@kernel.org > Cc: devicetree@vger.kernel.org > > --- Applied with this fixup: --- a/drivers/bus/bt1-apb.c +++ b/drivers/bus/bt1-apb.c @@ -16,6 +16,7 @@ #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/nmi.h> +#include <linux/of.h> #include <linux/regmap.h> #include <linux/clk.h> #include <linux/reset.h> @@ -309,13 +310,13 @@ static ssize_t timeout_store(struct device *dev, } static DEVICE_ATTR_RW(timeout); -static int inject_error_show(struct device *dev, struct device_attribute *attr, +static ssize_t inject_error_show(struct device *dev, struct device_attribute *attr, char *buf) { return scnprintf(buf, PAGE_SIZE, "Error injection: nodev irq\n"); } -static int inject_error_store(struct device *dev, +static ssize_t inject_error_store(struct device *dev, struct device_attribute *attr, const char *data, size_t count) {
On Thu, May 28, 2020 at 02:17:17PM +0200, Arnd Bergmann wrote: > On Tue, May 26, 2020 at 2:59 PM Serge Semin > <Sergey.Semin@baikalelectronics.ru> wrote: > > > > Baikal-T1 AXI-APB bridge is used to access the SoC subsystem CSRs. > > IO requests are routed to this bus by means of the DW AMBA 3 AXI > > Interconnect. In case if an attempted APB transaction stays with no > > response for a pre-defined time an interrupt occurs and the bus gets > > freed for a next operation. This driver provides the interrupt handler > > to detect the erroneous address, prints an error message about the > > address fault, updates an errors counter. The counter and the APB-bus > > operations timeout can be accessed via corresponding sysfs nodes. > > A dedicated sysfs-node can be also used to artificially cause the > > bus errors described above. > > > > Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru> > > Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru> > > Cc: Paul Burton <paulburton@kernel.org> > > Cc: Olof Johansson <olof@lixom.net> > > Cc: Rob Herring <robh+dt@kernel.org> > > Cc: linux-mips@vger.kernel.org > > Cc: soc@kernel.org > > Cc: devicetree@vger.kernel.org > > > > --- > > Applied with this fixup: I'm afraid linux/io.h is also needed here.( -Sergey > > --- a/drivers/bus/bt1-apb.c > +++ b/drivers/bus/bt1-apb.c > @@ -16,6 +16,7 @@ > #include <linux/platform_device.h> > #include <linux/interrupt.h> > #include <linux/nmi.h> > +#include <linux/of.h> > #include <linux/regmap.h> > #include <linux/clk.h> > #include <linux/reset.h> > @@ -309,13 +310,13 @@ static ssize_t timeout_store(struct device *dev, > } > static DEVICE_ATTR_RW(timeout); > > -static int inject_error_show(struct device *dev, struct device_attribute *attr, > +static ssize_t inject_error_show(struct device *dev, struct > device_attribute *attr, > char *buf) > { > return scnprintf(buf, PAGE_SIZE, "Error injection: nodev irq\n"); > } > > -static int inject_error_store(struct device *dev, > +static ssize_t inject_error_store(struct device *dev, > struct device_attribute *attr, > const char *data, size_t count) > {
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 1080dd26e268..030f0e59f193 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -29,6 +29,21 @@ config BRCMSTB_GISB_ARB arbiter. This driver provides timeout and target abort error handling and internal bus master decoding. +config BT1_APB + tristate "Baikal-T1 APB-bus driver" + depends on MIPS_BAIKAL_T1 || COMPILE_TEST + select REGMAP_MMIO + help + Baikal-T1 AXI-APB bridge is used to access the SoC subsystem CSRs. + IO requests are routed to this bus by means of the DW AMBA 3 AXI + Interconnect. In case of any APB protocol collisions, slave device + not responding on timeout an IRQ is raised with an erroneous address + reported to the APB terminator (APB Errors Handler Block). This + driver provides the interrupt handler to detect the erroneous + address, prints an error message about the address fault, updates an + errors counter. The counter and the APB-bus operations timeout can be + accessed via corresponding sysfs nodes. + config BT1_AXI tristate "Baikal-T1 AXI-bus driver" depends on MIPS_BAIKAL_T1 || COMPILE_TEST diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index eaa25d171ebd..08ccbfaf7705 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_MOXTET) += moxtet.o # DPAA2 fsl-mc bus obj-$(CONFIG_FSL_MC_BUS) += fsl-mc/ +obj-$(CONFIG_BT1_APB) += bt1-apb.o obj-$(CONFIG_BT1_AXI) += bt1-axi.o obj-$(CONFIG_IMX_WEIM) += imx-weim.o obj-$(CONFIG_MIPS_CDMM) += mips_cdmm.o diff --git a/drivers/bus/bt1-apb.c b/drivers/bus/bt1-apb.c new file mode 100644 index 000000000000..295cfa83aa23 --- /dev/null +++ b/drivers/bus/bt1-apb.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Authors: + * Serge Semin <Sergey.Semin@baikalelectronics.ru> + * + * Baikal-T1 APB-bus driver + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/atomic.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/nmi.h> +#include <linux/regmap.h> +#include <linux/clk.h> +#include <linux/reset.h> +#include <linux/time64.h> +#include <linux/clk.h> +#include <linux/sysfs.h> + +#define APB_EHB_ISR 0x00 +#define APB_EHB_ISR_PENDING BIT(0) +#define APB_EHB_ISR_MASK BIT(1) +#define APB_EHB_ADDR 0x04 +#define APB_EHB_TIMEOUT 0x08 + +#define APB_EHB_TIMEOUT_MIN 0x000003FFU +#define APB_EHB_TIMEOUT_MAX 0xFFFFFFFFU + +/* + * struct bt1_apb - Baikal-T1 APB EHB private data + * @dev: Pointer to the device structure. + * @regs: APB EHB registers map. + * @res: No-device error injection memory region. + * @irq: Errors IRQ number. + * @rate: APB-bus reference clock rate. + * @pclk: APB-reference clock. + * @prst: APB domain reset line. + * @count: Number of errors detected. + */ +struct bt1_apb { + struct device *dev; + + struct regmap *regs; + void __iomem *res; + int irq; + + unsigned long rate; + struct clk *pclk; + + struct reset_control *prst; + + atomic_t count; +}; + +static const struct regmap_config bt1_apb_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = APB_EHB_TIMEOUT, + .fast_io = true +}; + +static inline unsigned long bt1_apb_n_to_timeout_us(struct bt1_apb *apb, u32 n) +{ + u64 timeout = (u64)n * USEC_PER_SEC; + + do_div(timeout, apb->rate); + + return timeout; + +} + +static inline unsigned long bt1_apb_timeout_to_n_us(struct bt1_apb *apb, + unsigned long timeout) +{ + u64 n = (u64)timeout * apb->rate; + + do_div(n, USEC_PER_SEC); + + return n; + +} + +static irqreturn_t bt1_apb_isr(int irq, void *data) +{ + struct bt1_apb *apb = data; + u32 addr = 0; + + regmap_read(apb->regs, APB_EHB_ADDR, &addr); + + dev_crit_ratelimited(apb->dev, + "APB-bus fault %d: Slave access timeout at 0x%08x\n", + atomic_inc_return(&apb->count), + addr); + + /* + * Print backtrace on each CPU. This might be pointless if the fault + * has happened on the same CPU as the IRQ handler is executed or + * the other core proceeded further execution despite the error. + * But if it's not, by looking at the trace we would get straight to + * the cause of the problem. + */ + trigger_all_cpu_backtrace(); + + regmap_update_bits(apb->regs, APB_EHB_ISR, APB_EHB_ISR_PENDING, 0); + + return IRQ_HANDLED; +} + +static void bt1_apb_clear_data(void *data) +{ + struct bt1_apb *apb = data; + struct platform_device *pdev = to_platform_device(apb->dev); + + platform_set_drvdata(pdev, NULL); +} + +static struct bt1_apb *bt1_apb_create_data(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bt1_apb *apb; + int ret; + + apb = devm_kzalloc(dev, sizeof(*apb), GFP_KERNEL); + if (!apb) + return ERR_PTR(-ENOMEM); + + ret = devm_add_action(dev, bt1_apb_clear_data, apb); + if (ret) { + dev_err(dev, "Can't add APB EHB data clear action\n"); + return ERR_PTR(ret); + } + + apb->dev = dev; + atomic_set(&apb->count, 0); + platform_set_drvdata(pdev, apb); + + return apb; +} + +static int bt1_apb_request_regs(struct bt1_apb *apb) +{ + struct platform_device *pdev = to_platform_device(apb->dev); + void __iomem *regs; + + regs = devm_platform_ioremap_resource_byname(pdev, "ehb"); + if (IS_ERR(regs)) { + dev_err(apb->dev, "Couldn't map APB EHB registers\n"); + return PTR_ERR(regs); + } + + apb->regs = devm_regmap_init_mmio(apb->dev, regs, &bt1_apb_regmap_cfg); + if (IS_ERR(apb->regs)) { + dev_err(apb->dev, "Couldn't create APB EHB regmap\n"); + return PTR_ERR(apb->regs); + } + + apb->res = devm_platform_ioremap_resource_byname(pdev, "nodev"); + if (IS_ERR(apb->res)) { + dev_err(apb->dev, "Couldn't map reserved region\n"); + return PTR_ERR(apb->res); + } + + return 0; +} + +static int bt1_apb_request_rst(struct bt1_apb *apb) +{ + int ret; + + apb->prst = devm_reset_control_get_optional_exclusive(apb->dev, "prst"); + if (IS_ERR(apb->prst)) { + dev_warn(apb->dev, "Couldn't get reset control line\n"); + return PTR_ERR(apb->prst); + } + + ret = reset_control_deassert(apb->prst); + if (ret) + dev_err(apb->dev, "Failed to deassert the reset line\n"); + + return ret; +} + +static void bt1_apb_disable_clk(void *data) +{ + struct bt1_apb *apb = data; + + clk_disable_unprepare(apb->pclk); +} + +static int bt1_apb_request_clk(struct bt1_apb *apb) +{ + int ret; + + apb->pclk = devm_clk_get(apb->dev, "pclk"); + if (IS_ERR(apb->pclk)) { + dev_err(apb->dev, "Couldn't get APB clock descriptor\n"); + return PTR_ERR(apb->pclk); + } + + ret = clk_prepare_enable(apb->pclk); + if (ret) { + dev_err(apb->dev, "Couldn't enable the APB clock\n"); + return ret; + } + + ret = devm_add_action_or_reset(apb->dev, bt1_apb_disable_clk, apb); + if (ret) { + dev_err(apb->dev, "Can't add APB EHB clocks disable action\n"); + return ret; + } + + apb->rate = clk_get_rate(apb->pclk); + if (!apb->rate) { + dev_err(apb->dev, "Invalid clock rate\n"); + return -EINVAL; + } + + return 0; +} + +static void bt1_apb_clear_irq(void *data) +{ + struct bt1_apb *apb = data; + + regmap_update_bits(apb->regs, APB_EHB_ISR, APB_EHB_ISR_MASK, 0); +} + +static int bt1_apb_request_irq(struct bt1_apb *apb) +{ + struct platform_device *pdev = to_platform_device(apb->dev); + int ret; + + apb->irq = platform_get_irq(pdev, 0); + if (apb->irq < 0) + return apb->irq; + + ret = devm_request_irq(apb->dev, apb->irq, bt1_apb_isr, IRQF_SHARED, + "bt1-apb", apb); + if (ret) { + dev_err(apb->dev, "Couldn't request APB EHB IRQ\n"); + return ret; + } + + ret = devm_add_action(apb->dev, bt1_apb_clear_irq, apb); + if (ret) { + dev_err(apb->dev, "Can't add APB EHB IRQs clear action\n"); + return ret; + } + + /* Unmask IRQ and clear it' pending flag. */ + regmap_update_bits(apb->regs, APB_EHB_ISR, + APB_EHB_ISR_PENDING | APB_EHB_ISR_MASK, + APB_EHB_ISR_MASK); + + return 0; +} + +static ssize_t count_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct bt1_apb *apb = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&apb->count)); +} +static DEVICE_ATTR_RO(count); + +static ssize_t timeout_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct bt1_apb *apb = dev_get_drvdata(dev); + unsigned long timeout; + int ret; + u32 n; + + ret = regmap_read(apb->regs, APB_EHB_TIMEOUT, &n); + if (ret) + return ret; + + timeout = bt1_apb_n_to_timeout_us(apb, n); + + return scnprintf(buf, PAGE_SIZE, "%lu\n", timeout); +} + +static ssize_t timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bt1_apb *apb = dev_get_drvdata(dev); + unsigned long timeout; + int ret; + u32 n; + + if (kstrtoul(buf, 0, &timeout) < 0) + return -EINVAL; + + n = bt1_apb_timeout_to_n_us(apb, timeout); + n = clamp(n, APB_EHB_TIMEOUT_MIN, APB_EHB_TIMEOUT_MAX); + + ret = regmap_write(apb->regs, APB_EHB_TIMEOUT, n); + + return ret ?: count; +} +static DEVICE_ATTR_RW(timeout); + +static int inject_error_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "Error injection: nodev irq\n"); +} + +static int inject_error_store(struct device *dev, + struct device_attribute *attr, + const char *data, size_t count) +{ + struct bt1_apb *apb = dev_get_drvdata(dev); + + /* + * Either dummy read from the unmapped address in the APB IO area + * or manually set the IRQ status. + */ + if (!strncmp(data, "nodev", 5)) + readl(apb->res); + else if (!strncmp(data, "irq", 3)) + regmap_update_bits(apb->regs, APB_EHB_ISR, APB_EHB_ISR_PENDING, + APB_EHB_ISR_PENDING); + else + return -EINVAL; + + return count; +} +static DEVICE_ATTR_RW(inject_error); + +static struct attribute *bt1_apb_sysfs_attrs[] = { + &dev_attr_count.attr, + &dev_attr_timeout.attr, + &dev_attr_inject_error.attr, + NULL +}; +ATTRIBUTE_GROUPS(bt1_apb_sysfs); + +static void bt1_apb_remove_sysfs(void *data) +{ + struct bt1_apb *apb = data; + + device_remove_groups(apb->dev, bt1_apb_sysfs_groups); +} + +static int bt1_apb_init_sysfs(struct bt1_apb *apb) +{ + int ret; + + ret = device_add_groups(apb->dev, bt1_apb_sysfs_groups); + if (ret) { + dev_err(apb->dev, "Failed to create EHB APB sysfs nodes\n"); + return ret; + } + + ret = devm_add_action_or_reset(apb->dev, bt1_apb_remove_sysfs, apb); + if (ret) + dev_err(apb->dev, "Can't add APB EHB sysfs remove action\n"); + + return ret; +} + +static int bt1_apb_probe(struct platform_device *pdev) +{ + struct bt1_apb *apb; + int ret; + + apb = bt1_apb_create_data(pdev); + if (IS_ERR(apb)) + return PTR_ERR(apb); + + ret = bt1_apb_request_regs(apb); + if (ret) + return ret; + + ret = bt1_apb_request_rst(apb); + if (ret) + return ret; + + ret = bt1_apb_request_clk(apb); + if (ret) + return ret; + + ret = bt1_apb_request_irq(apb); + if (ret) + return ret; + + ret = bt1_apb_init_sysfs(apb); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id bt1_apb_of_match[] = { + { .compatible = "baikal,bt1-apb" }, + { } +}; +MODULE_DEVICE_TABLE(of, bt1_apb_of_match); + +static struct platform_driver bt1_apb_driver = { + .probe = bt1_apb_probe, + .driver = { + .name = "bt1-apb", + .of_match_table = bt1_apb_of_match + } +}; +module_platform_driver(bt1_apb_driver); + +MODULE_AUTHOR("Serge Semin <Sergey.Semin@baikalelectronics.ru>"); +MODULE_DESCRIPTION("Baikal-T1 APB-bus driver"); +MODULE_LICENSE("GPL v2");
Baikal-T1 AXI-APB bridge is used to access the SoC subsystem CSRs. IO requests are routed to this bus by means of the DW AMBA 3 AXI Interconnect. In case if an attempted APB transaction stays with no response for a pre-defined time an interrupt occurs and the bus gets freed for a next operation. This driver provides the interrupt handler to detect the erroneous address, prints an error message about the address fault, updates an errors counter. The counter and the APB-bus operations timeout can be accessed via corresponding sysfs nodes. A dedicated sysfs-node can be also used to artificially cause the bus errors described above. Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru> Cc: Paul Burton <paulburton@kernel.org> Cc: Olof Johansson <olof@lixom.net> Cc: Rob Herring <robh+dt@kernel.org> Cc: linux-mips@vger.kernel.org Cc: soc@kernel.org Cc: devicetree@vger.kernel.org --- Changelog v2: - Fix commit message and Kconfig help text spelling. - Move driver from soc to the bus subsystem. - Convert a simple EHB driver to the Baikal-T1 AXI-bus one. - Convert registers MMIO to the regmap. - Add reset line support. - Remove probe-status info string printout. - Since the driver depends on the OF config we can remove of_match_ptr() macro utilization. - Don't print error-message if no platform IRQ found. Just return an error. Changelog v3: - Discard CONFIG_OF dependency since there is none at compile-time. --- drivers/bus/Kconfig | 15 ++ drivers/bus/Makefile | 1 + drivers/bus/bt1-apb.c | 421 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 437 insertions(+) create mode 100644 drivers/bus/bt1-apb.c