18f93662dSSerge Semin // SPDX-License-Identifier: GPL-2.0-only 28f93662dSSerge Semin /* 38f93662dSSerge Semin * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC 48f93662dSSerge Semin * 58f93662dSSerge Semin * Authors: 68f93662dSSerge Semin * Serge Semin <Sergey.Semin@baikalelectronics.ru> 78f93662dSSerge Semin * 88f93662dSSerge Semin * Baikal-T1 APB-bus driver 98f93662dSSerge Semin */ 108f93662dSSerge Semin 118f93662dSSerge Semin #include <linux/kernel.h> 128f93662dSSerge Semin #include <linux/module.h> 138f93662dSSerge Semin #include <linux/types.h> 148f93662dSSerge Semin #include <linux/device.h> 158f93662dSSerge Semin #include <linux/atomic.h> 168f93662dSSerge Semin #include <linux/platform_device.h> 178f93662dSSerge Semin #include <linux/interrupt.h> 181c8ceb16SSerge Semin #include <linux/io.h> 198f93662dSSerge Semin #include <linux/nmi.h> 208f93662dSSerge Semin #include <linux/of.h> 218f93662dSSerge Semin #include <linux/regmap.h> 228f93662dSSerge Semin #include <linux/clk.h> 238f93662dSSerge Semin #include <linux/reset.h> 248f93662dSSerge Semin #include <linux/time64.h> 258f93662dSSerge Semin #include <linux/clk.h> 268f93662dSSerge Semin #include <linux/sysfs.h> 278f93662dSSerge Semin 288f93662dSSerge Semin #define APB_EHB_ISR 0x00 298f93662dSSerge Semin #define APB_EHB_ISR_PENDING BIT(0) 308f93662dSSerge Semin #define APB_EHB_ISR_MASK BIT(1) 318f93662dSSerge Semin #define APB_EHB_ADDR 0x04 328f93662dSSerge Semin #define APB_EHB_TIMEOUT 0x08 338f93662dSSerge Semin 348f93662dSSerge Semin #define APB_EHB_TIMEOUT_MIN 0x000003FFU 358f93662dSSerge Semin #define APB_EHB_TIMEOUT_MAX 0xFFFFFFFFU 368f93662dSSerge Semin 378f93662dSSerge Semin /* 388f93662dSSerge Semin * struct bt1_apb - Baikal-T1 APB EHB private data 398f93662dSSerge Semin * @dev: Pointer to the device structure. 408f93662dSSerge Semin * @regs: APB EHB registers map. 418f93662dSSerge Semin * @res: No-device error injection memory region. 428f93662dSSerge Semin * @irq: Errors IRQ number. 438f93662dSSerge Semin * @rate: APB-bus reference clock rate. 448f93662dSSerge Semin * @pclk: APB-reference clock. 458f93662dSSerge Semin * @prst: APB domain reset line. 468f93662dSSerge Semin * @count: Number of errors detected. 478f93662dSSerge Semin */ 488f93662dSSerge Semin struct bt1_apb { 498f93662dSSerge Semin struct device *dev; 508f93662dSSerge Semin 518f93662dSSerge Semin struct regmap *regs; 528f93662dSSerge Semin void __iomem *res; 538f93662dSSerge Semin int irq; 548f93662dSSerge Semin 558f93662dSSerge Semin unsigned long rate; 568f93662dSSerge Semin struct clk *pclk; 578f93662dSSerge Semin 588f93662dSSerge Semin struct reset_control *prst; 598f93662dSSerge Semin 608f93662dSSerge Semin atomic_t count; 618f93662dSSerge Semin }; 628f93662dSSerge Semin 638f93662dSSerge Semin static const struct regmap_config bt1_apb_regmap_cfg = { 648f93662dSSerge Semin .reg_bits = 32, 658f93662dSSerge Semin .val_bits = 32, 668f93662dSSerge Semin .reg_stride = 4, 678f93662dSSerge Semin .max_register = APB_EHB_TIMEOUT, 688f93662dSSerge Semin .fast_io = true 698f93662dSSerge Semin }; 708f93662dSSerge Semin 718f93662dSSerge Semin static inline unsigned long bt1_apb_n_to_timeout_us(struct bt1_apb *apb, u32 n) 728f93662dSSerge Semin { 738f93662dSSerge Semin u64 timeout = (u64)n * USEC_PER_SEC; 748f93662dSSerge Semin 758f93662dSSerge Semin do_div(timeout, apb->rate); 768f93662dSSerge Semin 778f93662dSSerge Semin return timeout; 788f93662dSSerge Semin 798f93662dSSerge Semin } 808f93662dSSerge Semin 818f93662dSSerge Semin static inline unsigned long bt1_apb_timeout_to_n_us(struct bt1_apb *apb, 828f93662dSSerge Semin unsigned long timeout) 838f93662dSSerge Semin { 848f93662dSSerge Semin u64 n = (u64)timeout * apb->rate; 858f93662dSSerge Semin 868f93662dSSerge Semin do_div(n, USEC_PER_SEC); 878f93662dSSerge Semin 888f93662dSSerge Semin return n; 898f93662dSSerge Semin 908f93662dSSerge Semin } 918f93662dSSerge Semin 928f93662dSSerge Semin static irqreturn_t bt1_apb_isr(int irq, void *data) 938f93662dSSerge Semin { 948f93662dSSerge Semin struct bt1_apb *apb = data; 958f93662dSSerge Semin u32 addr = 0; 968f93662dSSerge Semin 978f93662dSSerge Semin regmap_read(apb->regs, APB_EHB_ADDR, &addr); 988f93662dSSerge Semin 998f93662dSSerge Semin dev_crit_ratelimited(apb->dev, 1008f93662dSSerge Semin "APB-bus fault %d: Slave access timeout at 0x%08x\n", 1018f93662dSSerge Semin atomic_inc_return(&apb->count), 1028f93662dSSerge Semin addr); 1038f93662dSSerge Semin 1048f93662dSSerge Semin /* 1058f93662dSSerge Semin * Print backtrace on each CPU. This might be pointless if the fault 1068f93662dSSerge Semin * has happened on the same CPU as the IRQ handler is executed or 1078f93662dSSerge Semin * the other core proceeded further execution despite the error. 1088f93662dSSerge Semin * But if it's not, by looking at the trace we would get straight to 1098f93662dSSerge Semin * the cause of the problem. 1108f93662dSSerge Semin */ 1118f93662dSSerge Semin trigger_all_cpu_backtrace(); 1128f93662dSSerge Semin 1138f93662dSSerge Semin regmap_update_bits(apb->regs, APB_EHB_ISR, APB_EHB_ISR_PENDING, 0); 1148f93662dSSerge Semin 1158f93662dSSerge Semin return IRQ_HANDLED; 1168f93662dSSerge Semin } 1178f93662dSSerge Semin 1188f93662dSSerge Semin static void bt1_apb_clear_data(void *data) 1198f93662dSSerge Semin { 1208f93662dSSerge Semin struct bt1_apb *apb = data; 1218f93662dSSerge Semin struct platform_device *pdev = to_platform_device(apb->dev); 1228f93662dSSerge Semin 1238f93662dSSerge Semin platform_set_drvdata(pdev, NULL); 1248f93662dSSerge Semin } 1258f93662dSSerge Semin 1268f93662dSSerge Semin static struct bt1_apb *bt1_apb_create_data(struct platform_device *pdev) 1278f93662dSSerge Semin { 1288f93662dSSerge Semin struct device *dev = &pdev->dev; 1298f93662dSSerge Semin struct bt1_apb *apb; 1308f93662dSSerge Semin int ret; 1318f93662dSSerge Semin 1328f93662dSSerge Semin apb = devm_kzalloc(dev, sizeof(*apb), GFP_KERNEL); 1338f93662dSSerge Semin if (!apb) 1348f93662dSSerge Semin return ERR_PTR(-ENOMEM); 1358f93662dSSerge Semin 1368f93662dSSerge Semin ret = devm_add_action(dev, bt1_apb_clear_data, apb); 1378f93662dSSerge Semin if (ret) { 1388f93662dSSerge Semin dev_err(dev, "Can't add APB EHB data clear action\n"); 1398f93662dSSerge Semin return ERR_PTR(ret); 1408f93662dSSerge Semin } 1418f93662dSSerge Semin 1428f93662dSSerge Semin apb->dev = dev; 1438f93662dSSerge Semin atomic_set(&apb->count, 0); 1448f93662dSSerge Semin platform_set_drvdata(pdev, apb); 1458f93662dSSerge Semin 1468f93662dSSerge Semin return apb; 1478f93662dSSerge Semin } 1488f93662dSSerge Semin 1498f93662dSSerge Semin static int bt1_apb_request_regs(struct bt1_apb *apb) 1508f93662dSSerge Semin { 1518f93662dSSerge Semin struct platform_device *pdev = to_platform_device(apb->dev); 1528f93662dSSerge Semin void __iomem *regs; 1538f93662dSSerge Semin 1548f93662dSSerge Semin regs = devm_platform_ioremap_resource_byname(pdev, "ehb"); 1558f93662dSSerge Semin if (IS_ERR(regs)) { 1568f93662dSSerge Semin dev_err(apb->dev, "Couldn't map APB EHB registers\n"); 1578f93662dSSerge Semin return PTR_ERR(regs); 1588f93662dSSerge Semin } 1598f93662dSSerge Semin 1608f93662dSSerge Semin apb->regs = devm_regmap_init_mmio(apb->dev, regs, &bt1_apb_regmap_cfg); 1618f93662dSSerge Semin if (IS_ERR(apb->regs)) { 1628f93662dSSerge Semin dev_err(apb->dev, "Couldn't create APB EHB regmap\n"); 1638f93662dSSerge Semin return PTR_ERR(apb->regs); 1648f93662dSSerge Semin } 1658f93662dSSerge Semin 1668f93662dSSerge Semin apb->res = devm_platform_ioremap_resource_byname(pdev, "nodev"); 16775341b3dSSerge Semin if (IS_ERR(apb->res)) 1688f93662dSSerge Semin dev_err(apb->dev, "Couldn't map reserved region\n"); 1698f93662dSSerge Semin 17075341b3dSSerge Semin return PTR_ERR_OR_ZERO(apb->res); 1718f93662dSSerge Semin } 1728f93662dSSerge Semin 1738f93662dSSerge Semin static int bt1_apb_request_rst(struct bt1_apb *apb) 1748f93662dSSerge Semin { 1758f93662dSSerge Semin int ret; 1768f93662dSSerge Semin 1778f93662dSSerge Semin apb->prst = devm_reset_control_get_optional_exclusive(apb->dev, "prst"); 1788f93662dSSerge Semin if (IS_ERR(apb->prst)) { 1798f93662dSSerge Semin dev_warn(apb->dev, "Couldn't get reset control line\n"); 1808f93662dSSerge Semin return PTR_ERR(apb->prst); 1818f93662dSSerge Semin } 1828f93662dSSerge Semin 1838f93662dSSerge Semin ret = reset_control_deassert(apb->prst); 1848f93662dSSerge Semin if (ret) 1858f93662dSSerge Semin dev_err(apb->dev, "Failed to deassert the reset line\n"); 1868f93662dSSerge Semin 1878f93662dSSerge Semin return ret; 1888f93662dSSerge Semin } 1898f93662dSSerge Semin 1908f93662dSSerge Semin static void bt1_apb_disable_clk(void *data) 1918f93662dSSerge Semin { 1928f93662dSSerge Semin struct bt1_apb *apb = data; 1938f93662dSSerge Semin 1948f93662dSSerge Semin clk_disable_unprepare(apb->pclk); 1958f93662dSSerge Semin } 1968f93662dSSerge Semin 1978f93662dSSerge Semin static int bt1_apb_request_clk(struct bt1_apb *apb) 1988f93662dSSerge Semin { 1998f93662dSSerge Semin int ret; 2008f93662dSSerge Semin 2018f93662dSSerge Semin apb->pclk = devm_clk_get(apb->dev, "pclk"); 2028f93662dSSerge Semin if (IS_ERR(apb->pclk)) { 2038f93662dSSerge Semin dev_err(apb->dev, "Couldn't get APB clock descriptor\n"); 2048f93662dSSerge Semin return PTR_ERR(apb->pclk); 2058f93662dSSerge Semin } 2068f93662dSSerge Semin 2078f93662dSSerge Semin ret = clk_prepare_enable(apb->pclk); 2088f93662dSSerge Semin if (ret) { 2098f93662dSSerge Semin dev_err(apb->dev, "Couldn't enable the APB clock\n"); 2108f93662dSSerge Semin return ret; 2118f93662dSSerge Semin } 2128f93662dSSerge Semin 2138f93662dSSerge Semin ret = devm_add_action_or_reset(apb->dev, bt1_apb_disable_clk, apb); 2148f93662dSSerge Semin if (ret) { 2158f93662dSSerge Semin dev_err(apb->dev, "Can't add APB EHB clocks disable action\n"); 2168f93662dSSerge Semin return ret; 2178f93662dSSerge Semin } 2188f93662dSSerge Semin 2198f93662dSSerge Semin apb->rate = clk_get_rate(apb->pclk); 2208f93662dSSerge Semin if (!apb->rate) { 2218f93662dSSerge Semin dev_err(apb->dev, "Invalid clock rate\n"); 2228f93662dSSerge Semin return -EINVAL; 2238f93662dSSerge Semin } 2248f93662dSSerge Semin 2258f93662dSSerge Semin return 0; 2268f93662dSSerge Semin } 2278f93662dSSerge Semin 2288f93662dSSerge Semin static void bt1_apb_clear_irq(void *data) 2298f93662dSSerge Semin { 2308f93662dSSerge Semin struct bt1_apb *apb = data; 2318f93662dSSerge Semin 2328f93662dSSerge Semin regmap_update_bits(apb->regs, APB_EHB_ISR, APB_EHB_ISR_MASK, 0); 2338f93662dSSerge Semin } 2348f93662dSSerge Semin 2358f93662dSSerge Semin static int bt1_apb_request_irq(struct bt1_apb *apb) 2368f93662dSSerge Semin { 2378f93662dSSerge Semin struct platform_device *pdev = to_platform_device(apb->dev); 2388f93662dSSerge Semin int ret; 2398f93662dSSerge Semin 2408f93662dSSerge Semin apb->irq = platform_get_irq(pdev, 0); 2418f93662dSSerge Semin if (apb->irq < 0) 2428f93662dSSerge Semin return apb->irq; 2438f93662dSSerge Semin 2448f93662dSSerge Semin ret = devm_request_irq(apb->dev, apb->irq, bt1_apb_isr, IRQF_SHARED, 2458f93662dSSerge Semin "bt1-apb", apb); 2468f93662dSSerge Semin if (ret) { 2478f93662dSSerge Semin dev_err(apb->dev, "Couldn't request APB EHB IRQ\n"); 2488f93662dSSerge Semin return ret; 2498f93662dSSerge Semin } 2508f93662dSSerge Semin 2518f93662dSSerge Semin ret = devm_add_action(apb->dev, bt1_apb_clear_irq, apb); 2528f93662dSSerge Semin if (ret) { 2538f93662dSSerge Semin dev_err(apb->dev, "Can't add APB EHB IRQs clear action\n"); 2548f93662dSSerge Semin return ret; 2558f93662dSSerge Semin } 2568f93662dSSerge Semin 2578f93662dSSerge Semin /* Unmask IRQ and clear it' pending flag. */ 2588f93662dSSerge Semin regmap_update_bits(apb->regs, APB_EHB_ISR, 2598f93662dSSerge Semin APB_EHB_ISR_PENDING | APB_EHB_ISR_MASK, 2608f93662dSSerge Semin APB_EHB_ISR_MASK); 2618f93662dSSerge Semin 2628f93662dSSerge Semin return 0; 2638f93662dSSerge Semin } 2648f93662dSSerge Semin 2658f93662dSSerge Semin static ssize_t count_show(struct device *dev, struct device_attribute *attr, 2668f93662dSSerge Semin char *buf) 2678f93662dSSerge Semin { 2688f93662dSSerge Semin struct bt1_apb *apb = dev_get_drvdata(dev); 2698f93662dSSerge Semin 2708f93662dSSerge Semin return scnprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&apb->count)); 2718f93662dSSerge Semin } 2728f93662dSSerge Semin static DEVICE_ATTR_RO(count); 2738f93662dSSerge Semin 2748f93662dSSerge Semin static ssize_t timeout_show(struct device *dev, struct device_attribute *attr, 2758f93662dSSerge Semin char *buf) 2768f93662dSSerge Semin { 2778f93662dSSerge Semin struct bt1_apb *apb = dev_get_drvdata(dev); 2788f93662dSSerge Semin unsigned long timeout; 2798f93662dSSerge Semin int ret; 2808f93662dSSerge Semin u32 n; 2818f93662dSSerge Semin 2828f93662dSSerge Semin ret = regmap_read(apb->regs, APB_EHB_TIMEOUT, &n); 2838f93662dSSerge Semin if (ret) 2848f93662dSSerge Semin return ret; 2858f93662dSSerge Semin 2868f93662dSSerge Semin timeout = bt1_apb_n_to_timeout_us(apb, n); 2878f93662dSSerge Semin 2888f93662dSSerge Semin return scnprintf(buf, PAGE_SIZE, "%lu\n", timeout); 2898f93662dSSerge Semin } 2908f93662dSSerge Semin 2918f93662dSSerge Semin static ssize_t timeout_store(struct device *dev, 2928f93662dSSerge Semin struct device_attribute *attr, 2938f93662dSSerge Semin const char *buf, size_t count) 2948f93662dSSerge Semin { 2958f93662dSSerge Semin struct bt1_apb *apb = dev_get_drvdata(dev); 2968f93662dSSerge Semin unsigned long timeout; 2978f93662dSSerge Semin int ret; 2988f93662dSSerge Semin u32 n; 2998f93662dSSerge Semin 3008f93662dSSerge Semin if (kstrtoul(buf, 0, &timeout) < 0) 3018f93662dSSerge Semin return -EINVAL; 3028f93662dSSerge Semin 3038f93662dSSerge Semin n = bt1_apb_timeout_to_n_us(apb, timeout); 3048f93662dSSerge Semin n = clamp(n, APB_EHB_TIMEOUT_MIN, APB_EHB_TIMEOUT_MAX); 3058f93662dSSerge Semin 3068f93662dSSerge Semin ret = regmap_write(apb->regs, APB_EHB_TIMEOUT, n); 3078f93662dSSerge Semin 3088f93662dSSerge Semin return ret ?: count; 3098f93662dSSerge Semin } 3108f93662dSSerge Semin static DEVICE_ATTR_RW(timeout); 3118f93662dSSerge Semin 312b19dc1b7SSerge Semin static ssize_t inject_error_show(struct device *dev, 313b19dc1b7SSerge Semin struct device_attribute *attr, char *buf) 3148f93662dSSerge Semin { 3158f93662dSSerge Semin return scnprintf(buf, PAGE_SIZE, "Error injection: nodev irq\n"); 3168f93662dSSerge Semin } 3178f93662dSSerge Semin 3188f93662dSSerge Semin static ssize_t inject_error_store(struct device *dev, 3198f93662dSSerge Semin struct device_attribute *attr, 3208f93662dSSerge Semin const char *data, size_t count) 3218f93662dSSerge Semin { 3228f93662dSSerge Semin struct bt1_apb *apb = dev_get_drvdata(dev); 3238f93662dSSerge Semin 3248f93662dSSerge Semin /* 3258f93662dSSerge Semin * Either dummy read from the unmapped address in the APB IO area 3268f93662dSSerge Semin * or manually set the IRQ status. 3278f93662dSSerge Semin */ 328*b7cb430dSSerge Semin if (sysfs_streq(data, "nodev")) 3298f93662dSSerge Semin readl(apb->res); 330*b7cb430dSSerge Semin else if (sysfs_streq(data, "irq")) 3318f93662dSSerge Semin regmap_update_bits(apb->regs, APB_EHB_ISR, APB_EHB_ISR_PENDING, 3328f93662dSSerge Semin APB_EHB_ISR_PENDING); 3338f93662dSSerge Semin else 3348f93662dSSerge Semin return -EINVAL; 3358f93662dSSerge Semin 3368f93662dSSerge Semin return count; 3378f93662dSSerge Semin } 3388f93662dSSerge Semin static DEVICE_ATTR_RW(inject_error); 3398f93662dSSerge Semin 3408f93662dSSerge Semin static struct attribute *bt1_apb_sysfs_attrs[] = { 3418f93662dSSerge Semin &dev_attr_count.attr, 3428f93662dSSerge Semin &dev_attr_timeout.attr, 3438f93662dSSerge Semin &dev_attr_inject_error.attr, 3448f93662dSSerge Semin NULL 3458f93662dSSerge Semin }; 3468f93662dSSerge Semin ATTRIBUTE_GROUPS(bt1_apb_sysfs); 3478f93662dSSerge Semin 3488f93662dSSerge Semin static void bt1_apb_remove_sysfs(void *data) 3498f93662dSSerge Semin { 3508f93662dSSerge Semin struct bt1_apb *apb = data; 3518f93662dSSerge Semin 3528f93662dSSerge Semin device_remove_groups(apb->dev, bt1_apb_sysfs_groups); 3538f93662dSSerge Semin } 3548f93662dSSerge Semin 3558f93662dSSerge Semin static int bt1_apb_init_sysfs(struct bt1_apb *apb) 3568f93662dSSerge Semin { 3578f93662dSSerge Semin int ret; 3588f93662dSSerge Semin 3598f93662dSSerge Semin ret = device_add_groups(apb->dev, bt1_apb_sysfs_groups); 3608f93662dSSerge Semin if (ret) { 3618f93662dSSerge Semin dev_err(apb->dev, "Failed to create EHB APB sysfs nodes\n"); 3628f93662dSSerge Semin return ret; 3638f93662dSSerge Semin } 3648f93662dSSerge Semin 3658f93662dSSerge Semin ret = devm_add_action_or_reset(apb->dev, bt1_apb_remove_sysfs, apb); 3668f93662dSSerge Semin if (ret) 3678f93662dSSerge Semin dev_err(apb->dev, "Can't add APB EHB sysfs remove action\n"); 3688f93662dSSerge Semin 3698f93662dSSerge Semin return ret; 3708f93662dSSerge Semin } 3718f93662dSSerge Semin 3728f93662dSSerge Semin static int bt1_apb_probe(struct platform_device *pdev) 3738f93662dSSerge Semin { 3748f93662dSSerge Semin struct bt1_apb *apb; 3758f93662dSSerge Semin int ret; 3768f93662dSSerge Semin 3778f93662dSSerge Semin apb = bt1_apb_create_data(pdev); 3788f93662dSSerge Semin if (IS_ERR(apb)) 3798f93662dSSerge Semin return PTR_ERR(apb); 3808f93662dSSerge Semin 3818f93662dSSerge Semin ret = bt1_apb_request_regs(apb); 3828f93662dSSerge Semin if (ret) 3838f93662dSSerge Semin return ret; 3848f93662dSSerge Semin 3858f93662dSSerge Semin ret = bt1_apb_request_rst(apb); 3868f93662dSSerge Semin if (ret) 3878f93662dSSerge Semin return ret; 3888f93662dSSerge Semin 3898f93662dSSerge Semin ret = bt1_apb_request_clk(apb); 3908f93662dSSerge Semin if (ret) 3918f93662dSSerge Semin return ret; 3928f93662dSSerge Semin 3938f93662dSSerge Semin ret = bt1_apb_request_irq(apb); 3948f93662dSSerge Semin if (ret) 3958f93662dSSerge Semin return ret; 3968f93662dSSerge Semin 3978f93662dSSerge Semin ret = bt1_apb_init_sysfs(apb); 3988f93662dSSerge Semin if (ret) 3998f93662dSSerge Semin return ret; 4008f93662dSSerge Semin 4018f93662dSSerge Semin return 0; 4028f93662dSSerge Semin } 4038f93662dSSerge Semin 4048f93662dSSerge Semin static const struct of_device_id bt1_apb_of_match[] = { 4058f93662dSSerge Semin { .compatible = "baikal,bt1-apb" }, 4068f93662dSSerge Semin { } 4078f93662dSSerge Semin }; 4088f93662dSSerge Semin MODULE_DEVICE_TABLE(of, bt1_apb_of_match); 4098f93662dSSerge Semin 4108f93662dSSerge Semin static struct platform_driver bt1_apb_driver = { 4118f93662dSSerge Semin .probe = bt1_apb_probe, 4128f93662dSSerge Semin .driver = { 4138f93662dSSerge Semin .name = "bt1-apb", 4148f93662dSSerge Semin .of_match_table = bt1_apb_of_match 4158f93662dSSerge Semin } 4168f93662dSSerge Semin }; 4178f93662dSSerge Semin module_platform_driver(bt1_apb_driver); 4188f93662dSSerge Semin 4198f93662dSSerge Semin MODULE_AUTHOR("Serge Semin <Sergey.Semin@baikalelectronics.ru>"); 4208f93662dSSerge Semin MODULE_DESCRIPTION("Baikal-T1 APB-bus driver"); 4218f93662dSSerge Semin MODULE_LICENSE("GPL v2"); 422