diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0106-enable-AST2600-I3C.patch')
-rw-r--r-- | meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0106-enable-AST2600-I3C.patch | 910 |
1 files changed, 910 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0106-enable-AST2600-I3C.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0106-enable-AST2600-I3C.patch new file mode 100644 index 000000000..4cc88cbeb --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0106-enable-AST2600-I3C.patch @@ -0,0 +1,910 @@ +From c3be31a18ef1755c86c169b9d0e54bdbbea99d8a Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Wed, 6 May 2020 18:11:29 -0700 +Subject: [PATCH] enable AST2600 I3C + +This commit ports I3C related changes from Aspeed SDK v00.05.05. +It also includes Vitor's I3C cdev implementation which isn't +upstreamed yet so it should be refined later. + +Signed-off-by: Ryan Chen <ryan_chen@aspeedtech.com> +Signed-off-by: Vitor Soares <soares@synopsys.com> +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + arch/arm/boot/dts/aspeed-g6.dtsi | 19 +- + drivers/clk/clk-ast2600.c | 30 ++- + drivers/i3c/Kconfig | 15 ++ + drivers/i3c/Makefile | 1 + + drivers/i3c/i3cdev.c | 428 ++++++++++++++++++++++++++++++ + drivers/i3c/internals.h | 2 + + drivers/i3c/master.c | 39 ++- + drivers/i3c/master/Kconfig | 5 + + drivers/i3c/master/Makefile | 1 + + drivers/i3c/master/aspeed-i3c-global.c | 77 ++++++ + include/dt-bindings/clock/ast2600-clock.h | 3 +- + include/uapi/linux/i3c/i3cdev.h | 38 +++ + 12 files changed, 634 insertions(+), 24 deletions(-) + create mode 100644 drivers/i3c/i3cdev.c + create mode 100644 drivers/i3c/master/aspeed-i3c-global.c + create mode 100644 include/uapi/linux/i3c/i3cdev.h + +diff --git a/arch/arm/boot/dts/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed-g6.dtsi +index 91f431e419d9..33e1b0ef24f0 100644 +--- a/arch/arm/boot/dts/aspeed-g6.dtsi ++++ b/arch/arm/boot/dts/aspeed-g6.dtsi +@@ -1091,13 +1091,20 @@ + }; + + &i3c { ++ i3cglobal: i3cg@0 { ++ reg = <0x0 0x1000>; ++ compatible = "aspeed,ast2600-i3c-global"; ++ resets = <&syscon ASPEED_RESET_I3C_DMA>; ++ status = "disabled"; ++ }; ++ + i3c0: i3c0@2000 { + #address-cells = <1>; + #size-cells = <0>; + #interrupt-cells = <1>; + reg = <0x2000 0x1000>; + compatible = "snps,dw-i3c-master-1.00a"; +- clocks = <&syscon ASPEED_CLK_APB2>; ++ clocks = <&syscon ASPEED_CLK_GATE_I3C0CLK>; + resets = <&syscon ASPEED_RESET_I3C0>; + bus-frequency = <100000>; + interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>; +@@ -1110,7 +1117,7 @@ + #interrupt-cells = <1>; + reg = <0x3000 0x1000>; + compatible = "snps,dw-i3c-master-1.00a"; +- clocks = <&syscon ASPEED_CLK_APB2>; ++ clocks = <&syscon ASPEED_CLK_GATE_I3C1CLK>; + resets = <&syscon ASPEED_RESET_I3C1>; + bus-frequency = <100000>; + interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>; +@@ -1123,7 +1130,7 @@ + #interrupt-cells = <1>; + reg = <0x4000 0x1000>; + compatible = "snps,dw-i3c-master-1.00a"; +- clocks = <&syscon ASPEED_CLK_APB2>; ++ clocks = <&syscon ASPEED_CLK_GATE_I3C2CLK>; + resets = <&syscon ASPEED_RESET_I3C2>; + bus-frequency = <100000>; + interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>; +@@ -1138,7 +1145,7 @@ + #interrupt-cells = <1>; + reg = <0x5000 0x1000>; + compatible = "snps,dw-i3c-master-1.00a"; +- clocks = <&syscon ASPEED_CLK_APB2>; ++ clocks = <&syscon ASPEED_CLK_GATE_I3C3CLK>; + resets = <&syscon ASPEED_RESET_I3C3>; + bus-frequency = <100000>; + interrupts = <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH>; +@@ -1153,7 +1160,7 @@ + #interrupt-cells = <1>; + reg = <0x6000 0x1000>; + compatible = "snps,dw-i3c-master-1.00a"; +- clocks = <&syscon ASPEED_CLK_APB2>; ++ clocks = <&syscon ASPEED_CLK_GATE_I3C4CLK>; + resets = <&syscon ASPEED_RESET_I3C4>; + bus-frequency = <100000>; + interrupts = <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>; +@@ -1168,7 +1175,7 @@ + #interrupt-cells = <1>; + reg = <0x7000 0x1000>; + compatible = "snps,dw-i3c-master-1.00a"; +- clocks = <&syscon ASPEED_CLK_APB2>; ++ clocks = <&syscon ASPEED_CLK_GATE_I3C5CLK>; + resets = <&syscon ASPEED_RESET_I3C5>; + bus-frequency = <100000>; + interrupts = <GIC_SPI 107 IRQ_TYPE_LEVEL_HIGH>; +diff --git a/drivers/clk/clk-ast2600.c b/drivers/clk/clk-ast2600.c +index e07326544fdc..a30dcbb1a3fd 100644 +--- a/drivers/clk/clk-ast2600.c ++++ b/drivers/clk/clk-ast2600.c +@@ -112,14 +112,14 @@ static const struct aspeed_gate_data aspeed_g6_gates[] = { + [ASPEED_CLK_GATE_LHCCLK] = { 37, -1, "lhclk-gate", "lhclk", 0 }, /* LPC master/LPC+ */ + /* Reserved 38 RSA: no longer used */ + /* Reserved 39 */ +- [ASPEED_CLK_GATE_I3C0CLK] = { 40, 40, "i3c0clk-gate", NULL, 0 }, /* I3C0 */ +- [ASPEED_CLK_GATE_I3C1CLK] = { 41, 41, "i3c1clk-gate", NULL, 0 }, /* I3C1 */ +- [ASPEED_CLK_GATE_I3C2CLK] = { 42, 42, "i3c2clk-gate", NULL, 0 }, /* I3C2 */ +- [ASPEED_CLK_GATE_I3C3CLK] = { 43, 43, "i3c3clk-gate", NULL, 0 }, /* I3C3 */ +- [ASPEED_CLK_GATE_I3C4CLK] = { 44, 44, "i3c4clk-gate", NULL, 0 }, /* I3C4 */ +- [ASPEED_CLK_GATE_I3C5CLK] = { 45, 45, "i3c5clk-gate", NULL, 0 }, /* I3C5 */ +- [ASPEED_CLK_GATE_I3C6CLK] = { 46, 46, "i3c6clk-gate", NULL, 0 }, /* I3C6 */ +- [ASPEED_CLK_GATE_I3C7CLK] = { 47, 47, "i3c7clk-gate", NULL, 0 }, /* I3C7 */ ++ [ASPEED_CLK_GATE_I3C0CLK] = { 40, 40, "i3c0clk-gate", "i3cclk", 0 }, /* I3C0 */ ++ [ASPEED_CLK_GATE_I3C1CLK] = { 41, 41, "i3c1clk-gate", "i3cclk", 0 }, /* I3C1 */ ++ [ASPEED_CLK_GATE_I3C2CLK] = { 42, 42, "i3c2clk-gate", "i3cclk", 0 }, /* I3C2 */ ++ [ASPEED_CLK_GATE_I3C3CLK] = { 43, 43, "i3c3clk-gate", "i3cclk", 0 }, /* I3C3 */ ++ [ASPEED_CLK_GATE_I3C4CLK] = { 44, 44, "i3c4clk-gate", "i3cclk", 0 }, /* I3C4 */ ++ [ASPEED_CLK_GATE_I3C5CLK] = { 45, 45, "i3c5clk-gate", "i3cclk", 0 }, /* I3C5 */ ++ [ASPEED_CLK_GATE_I3C6CLK] = { 46, 46, "i3c6clk-gate", "i3cclk", 0 }, /* I3C6 */ ++ [ASPEED_CLK_GATE_I3C7CLK] = { 47, 47, "i3c7clk-gate", "i3cclk", 0 }, /* I3C7 */ + [ASPEED_CLK_GATE_UART1CLK] = { 48, -1, "uart1clk-gate", "uart", 0 }, /* UART1 */ + [ASPEED_CLK_GATE_UART2CLK] = { 49, -1, "uart2clk-gate", "uart", 0 }, /* UART2 */ + [ASPEED_CLK_GATE_UART3CLK] = { 50, -1, "uart3clk-gate", "uart", 0 }, /* UART3 */ +@@ -749,6 +749,20 @@ static void __init aspeed_g6_cc(struct regmap *map) + /* USB 2.0 port1 phy 40MHz clock */ + hw = clk_hw_register_fixed_rate(NULL, "usb-phy-40m", NULL, 0, 40000000); + aspeed_g6_clk_data->hws[ASPEED_CLK_USBPHY_40M] = hw; ++ ++ /* i3c clock source */ ++ regmap_read(map, ASPEED_G6_CLK_SELECTION5, &val); ++ if(val & BIT(31)) { ++ val = (val >> 28) & 0x7; ++ if(val) ++ div = val + 1; ++ else ++ div = val + 2; ++ hw = clk_hw_register_fixed_factor(NULL, "i3cclk", "apll", 0, 1, div); ++ } else { ++ hw = clk_hw_register_fixed_factor(NULL, "i3cclk", "ahb", 0, 1, 1); ++ } ++ aspeed_g6_clk_data->hws[ASPEED_CLK_I3C] = hw; + }; + + static void __init aspeed_g6_cc_init(struct device_node *np) +diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig +index 30a441506f61..01642768ab5f 100644 +--- a/drivers/i3c/Kconfig ++++ b/drivers/i3c/Kconfig +@@ -20,5 +20,20 @@ menuconfig I3C + will be called i3c. + + if I3C ++ ++config I3CDEV ++ tristate "I3C device interface" ++ depends on I3C ++ help ++ Say Y here to use i3c-* device files, usually found in the /dev ++ directory on your system. They make it possible to have user-space ++ programs use the I3C devices. ++ ++ This support is also available as a module. If so, the module ++ will be called i3cdev. ++ ++ Note that this application programming interface is EXPERIMENTAL ++ and hence SUBJECT TO CHANGE WITHOUT NOTICE while it stabilizes. ++ + source "drivers/i3c/master/Kconfig" + endif # I3C +diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile +index 11982efbc6d9..606d422841b2 100644 +--- a/drivers/i3c/Makefile ++++ b/drivers/i3c/Makefile +@@ -1,4 +1,5 @@ + # SPDX-License-Identifier: GPL-2.0 + i3c-y := device.o master.o + obj-$(CONFIG_I3C) += i3c.o ++obj-$(CONFIG_I3CDEV) += i3cdev.o + obj-$(CONFIG_I3C) += master/ +diff --git a/drivers/i3c/i3cdev.c b/drivers/i3c/i3cdev.c +new file mode 100644 +index 000000000000..07f5641a902d +--- /dev/null ++++ b/drivers/i3c/i3cdev.c +@@ -0,0 +1,428 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2019 Synopsys, Inc. and/or its affiliates. ++ * ++ * Author: Vitor Soares <soares@synopsys.com> ++ */ ++ ++#include <linux/cdev.h> ++#include <linux/compat.h> ++#include <linux/device.h> ++#include <linux/fs.h> ++#include <linux/init.h> ++#include <linux/jiffies.h> ++#include <linux/kernel.h> ++#include <linux/list.h> ++#include <linux/module.h> ++#include <linux/notifier.h> ++#include <linux/slab.h> ++#include <linux/uaccess.h> ++ ++#include <linux/i3c/i3cdev.h> ++ ++#include "internals.h" ++ ++struct i3cdev_data { ++ struct list_head list; ++ struct i3c_device *i3c; ++ struct cdev cdev; ++ struct device *dev; ++ int id; ++}; ++ ++static DEFINE_IDA(i3cdev_ida); ++static dev_t i3cdev_number; ++#define I3C_MINORS 16 /* 16 I3C devices supported for now */ ++ ++static LIST_HEAD(i3cdev_list); ++static DEFINE_SPINLOCK(i3cdev_list_lock); ++ ++static struct i3cdev_data *i3cdev_get_by_i3c(struct i3c_device *i3c) ++{ ++ struct i3cdev_data *i3cdev; ++ ++ spin_lock(&i3cdev_list_lock); ++ list_for_each_entry(i3cdev, &i3cdev_list, list) { ++ if (i3cdev->i3c == i3c) ++ goto found; ++ } ++ ++ i3cdev = NULL; ++ ++found: ++ spin_unlock(&i3cdev_list_lock); ++ return i3cdev; ++} ++ ++static struct i3cdev_data *get_free_i3cdev(struct i3c_device *i3c) ++{ ++ struct i3cdev_data *i3cdev; ++ int id; ++ ++ id = ida_simple_get(&i3cdev_ida, 0, I3C_MINORS, GFP_KERNEL); ++ if (id < 0) { ++ pr_err("i3cdev: no minor number available!\n"); ++ return ERR_PTR(id); ++ } ++ ++ i3cdev = kzalloc(sizeof(*i3cdev), GFP_KERNEL); ++ if (!i3cdev) { ++ ida_simple_remove(&i3cdev_ida, id); ++ return ERR_PTR(-ENOMEM); ++ } ++ ++ i3cdev->i3c = i3c; ++ i3cdev->id = id; ++ ++ spin_lock(&i3cdev_list_lock); ++ list_add_tail(&i3cdev->list, &i3cdev_list); ++ spin_unlock(&i3cdev_list_lock); ++ ++ return i3cdev; ++} ++ ++static void put_i3cdev(struct i3cdev_data *i3cdev) ++{ ++ spin_lock(&i3cdev_list_lock); ++ list_del(&i3cdev->list); ++ spin_unlock(&i3cdev_list_lock); ++ kfree(i3cdev); ++} ++ ++static ssize_t ++i3cdev_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos) ++{ ++ struct i3c_device *i3c = file->private_data; ++ struct i3c_priv_xfer xfers = { ++ .rnw = true, ++ .len = count, ++ }; ++ char *tmp; ++ int ret; ++ ++ tmp = kzalloc(count, GFP_KERNEL); ++ if (!tmp) ++ return -ENOMEM; ++ ++ xfers.data.in = tmp; ++ ++ dev_dbg(&i3c->dev, "Reading %zu bytes.\n", count); ++ ++ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1); ++ if (!ret) ++ ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret; ++ ++ kfree(tmp); ++ return ret; ++} ++ ++static ssize_t ++i3cdev_write(struct file *file, const char __user *buf, size_t count, ++ loff_t *f_pos) ++{ ++ struct i3c_device *i3c = file->private_data; ++ struct i3c_priv_xfer xfers = { ++ .rnw = false, ++ .len = count, ++ }; ++ char *tmp; ++ int ret; ++ ++ tmp = memdup_user(buf, count); ++ if (IS_ERR(tmp)) ++ return PTR_ERR(tmp); ++ ++ xfers.data.out = tmp; ++ ++ dev_dbg(&i3c->dev, "Writing %zu bytes.\n", count); ++ ++ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1); ++ kfree(tmp); ++ return (!ret) ? count : ret; ++} ++ ++static int ++i3cdev_do_priv_xfer(struct i3c_device *dev, struct i3c_ioc_priv_xfer *xfers, ++ unsigned int nxfers) ++{ ++ struct i3c_priv_xfer *k_xfers; ++ u8 **data_ptrs; ++ int i, ret = 0; ++ ++ k_xfers = kcalloc(nxfers, sizeof(*k_xfers), GFP_KERNEL); ++ if (!k_xfers) ++ return -ENOMEM; ++ ++ data_ptrs = kcalloc(nxfers, sizeof(*data_ptrs), GFP_KERNEL); ++ if (!data_ptrs) { ++ ret = -ENOMEM; ++ goto err_free_k_xfer; ++ } ++ ++ for (i = 0; i < nxfers; i++) { ++ data_ptrs[i] = memdup_user((const u8 __user *) ++ (uintptr_t)xfers[i].data, ++ xfers[i].len); ++ if (IS_ERR(data_ptrs[i])) { ++ ret = PTR_ERR(data_ptrs[i]); ++ break; ++ } ++ ++ k_xfers[i].len = xfers[i].len; ++ if (xfers[i].rnw) { ++ k_xfers[i].rnw = true; ++ k_xfers[i].data.in = data_ptrs[i]; ++ } else { ++ k_xfers[i].rnw = false; ++ k_xfers[i].data.out = data_ptrs[i]; ++ } ++ } ++ ++ if (ret < 0) { ++ i--; ++ goto err_free_mem; ++ } ++ ++ ret = i3c_device_do_priv_xfers(dev, k_xfers, nxfers); ++ if (ret) ++ goto err_free_mem; ++ ++ for (i = 0; i < nxfers; i++) { ++ if (xfers[i].rnw) { ++ if (copy_to_user((void __user *)(uintptr_t)xfers[i].data, ++ data_ptrs[i], xfers[i].len)) ++ ret = -EFAULT; ++ } ++ } ++ ++err_free_mem: ++ for (; i >= 0; i--) ++ kfree(data_ptrs[i]); ++ kfree(data_ptrs); ++err_free_k_xfer: ++ kfree(k_xfers); ++ return ret; ++} ++ ++static struct i3c_ioc_priv_xfer * ++i3cdev_get_ioc_priv_xfer(unsigned int cmd, struct i3c_ioc_priv_xfer *u_xfers, ++ unsigned int *nxfers) ++{ ++ u32 tmp = _IOC_SIZE(cmd); ++ ++ if ((tmp % sizeof(struct i3c_ioc_priv_xfer)) != 0) ++ return ERR_PTR(-EINVAL); ++ ++ *nxfers = tmp / sizeof(struct i3c_ioc_priv_xfer); ++ if (*nxfers == 0) ++ return NULL; ++ ++ return memdup_user(u_xfers, tmp); ++} ++ ++static int ++i3cdev_ioc_priv_xfer(struct i3c_device *i3c, unsigned int cmd, ++ struct i3c_ioc_priv_xfer *u_xfers) ++{ ++ struct i3c_ioc_priv_xfer *k_xfers; ++ unsigned int nxfers; ++ int ret; ++ ++ k_xfers = i3cdev_get_ioc_priv_xfer(cmd, u_xfers, &nxfers); ++ if (IS_ERR_OR_NULL(k_xfers)) ++ return PTR_ERR(k_xfers); ++ ++ ret = i3cdev_do_priv_xfer(i3c, k_xfers, nxfers); ++ ++ kfree(k_xfers); ++ ++ return ret; ++} ++ ++static long ++i3cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ struct i3c_device *i3c = file->private_data; ++ ++ dev_dbg(&i3c->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n", cmd, arg); ++ ++ if (_IOC_TYPE(cmd) != I3C_DEV_IOC_MAGIC) ++ return -ENOTTY; ++ ++ /* Check command number and direction */ ++ if (_IOC_NR(cmd) == _IOC_NR(I3C_IOC_PRIV_XFER(0)) && ++ _IOC_DIR(cmd) == (_IOC_READ | _IOC_WRITE)) ++ return i3cdev_ioc_priv_xfer(i3c, cmd, ++ (struct i3c_ioc_priv_xfer __user *)arg); ++ ++ return 0; ++} ++ ++static int i3cdev_open(struct inode *inode, struct file *file) ++{ ++ struct i3cdev_data *i3cdev = container_of(inode->i_cdev, ++ struct i3cdev_data, ++ cdev); ++ ++ file->private_data = i3cdev->i3c; ++ ++ return 0; ++} ++ ++static int i3cdev_release(struct inode *inode, struct file *file) ++{ ++ file->private_data = NULL; ++ ++ return 0; ++} ++ ++static const struct file_operations i3cdev_fops = { ++ .owner = THIS_MODULE, ++ .read = i3cdev_read, ++ .write = i3cdev_write, ++ .unlocked_ioctl = i3cdev_ioctl, ++ .open = i3cdev_open, ++ .release = i3cdev_release, ++}; ++ ++/* ------------------------------------------------------------------------- */ ++ ++static struct class *i3cdev_class; ++ ++static int i3cdev_attach(struct device *dev, void *dummy) ++{ ++ struct i3cdev_data *i3cdev; ++ struct i3c_device *i3c; ++ int res; ++ ++ if (dev->type == &i3c_masterdev_type || dev->driver) ++ return 0; ++ ++ i3c = dev_to_i3cdev(dev); ++ ++ /* Get a device */ ++ i3cdev = get_free_i3cdev(i3c); ++ if (IS_ERR(i3cdev)) ++ return PTR_ERR(i3cdev); ++ ++ cdev_init(&i3cdev->cdev, &i3cdev_fops); ++ i3cdev->cdev.owner = THIS_MODULE; ++ res = cdev_add(&i3cdev->cdev, ++ MKDEV(MAJOR(i3cdev_number), i3cdev->id), 1); ++ if (res) ++ goto error_cdev; ++ ++ /* register this i3c device with the driver core */ ++ i3cdev->dev = device_create(i3cdev_class, &i3c->dev, ++ MKDEV(MAJOR(i3cdev_number), i3cdev->id), ++ NULL, "i3c-%s", dev_name(&i3c->dev)); ++ if (IS_ERR(i3cdev->dev)) { ++ res = PTR_ERR(i3cdev->dev); ++ goto error; ++ } ++ pr_debug("i3cdev: I3C device [%s] registered as minor %d\n", ++ dev_name(&i3c->dev), i3cdev->id); ++ return 0; ++ ++error: ++ cdev_del(&i3cdev->cdev); ++error_cdev: ++ put_i3cdev(i3cdev); ++ return res; ++} ++ ++static int i3cdev_detach(struct device *dev, void *dummy) ++{ ++ struct i3cdev_data *i3cdev; ++ struct i3c_device *i3c; ++ ++ if (dev->type == &i3c_masterdev_type) ++ return 0; ++ ++ i3c = dev_to_i3cdev(dev); ++ ++ i3cdev = i3cdev_get_by_i3c(i3c); ++ if (!i3cdev) ++ return 0; ++ ++ cdev_del(&i3cdev->cdev); ++ device_destroy(i3cdev_class, MKDEV(MAJOR(i3cdev_number), i3cdev->id)); ++ ida_simple_remove(&i3cdev_ida, i3cdev->id); ++ put_i3cdev(i3cdev); ++ ++ pr_debug("i3cdev: device [%s] unregistered\n", dev_name(&i3c->dev)); ++ ++ return 0; ++} ++ ++static int i3cdev_notifier_call(struct notifier_block *nb, ++ unsigned long action, ++ void *data) ++{ ++ struct device *dev = data; ++ ++ switch (action) { ++ case BUS_NOTIFY_ADD_DEVICE: ++ case BUS_NOTIFY_UNBOUND_DRIVER: ++ return i3cdev_attach(dev, NULL); ++ case BUS_NOTIFY_DEL_DEVICE: ++ case BUS_NOTIFY_BOUND_DRIVER: ++ return i3cdev_detach(dev, NULL); ++ } ++ ++ return 0; ++} ++ ++static struct notifier_block i3c_notifier = { ++ .notifier_call = i3cdev_notifier_call, ++}; ++ ++static int __init i3cdev_init(void) ++{ ++ int res; ++ ++ /* Dynamically request unused major number */ ++ res = alloc_chrdev_region(&i3cdev_number, 0, I3C_MINORS, "i3c"); ++ if (res) ++ goto out; ++ ++ /* Create a classe to populate sysfs entries*/ ++ i3cdev_class = class_create(THIS_MODULE, "i3cdev"); ++ if (IS_ERR(i3cdev_class)) { ++ res = PTR_ERR(i3cdev_class); ++ goto out_unreg_chrdev; ++ } ++ ++ /* Keep track of busses which have devices to add or remove later */ ++ res = bus_register_notifier(&i3c_bus_type, &i3c_notifier); ++ if (res) ++ goto out_unreg_class; ++ ++ /* Bind to already existing device without driver right away */ ++ i3c_for_each_dev(NULL, i3cdev_attach); ++ ++ return 0; ++ ++out_unreg_class: ++ class_destroy(i3cdev_class); ++out_unreg_chrdev: ++ unregister_chrdev_region(i3cdev_number, I3C_MINORS); ++out: ++ pr_err("%s: Driver Initialisation failed\n", __FILE__); ++ return res; ++} ++ ++static void __exit i3cdev_exit(void) ++{ ++ bus_unregister_notifier(&i3c_bus_type, &i3c_notifier); ++ i3c_for_each_dev(NULL, i3cdev_detach); ++ class_destroy(i3cdev_class); ++ unregister_chrdev_region(i3cdev_number, I3C_MINORS); ++} ++ ++MODULE_AUTHOR("Vitor Soares <soares@synopsys.com>"); ++MODULE_DESCRIPTION("I3C /dev entries driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(i3cdev_init); ++module_exit(i3cdev_exit); +diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h +index 86b7b44cfca2..a6deedf5ce06 100644 +--- a/drivers/i3c/internals.h ++++ b/drivers/i3c/internals.h +@@ -11,6 +11,7 @@ + #include <linux/i3c/master.h> + + extern struct bus_type i3c_bus_type; ++extern const struct device_type i3c_masterdev_type; + + void i3c_bus_normaluse_lock(struct i3c_bus *bus); + void i3c_bus_normaluse_unlock(struct i3c_bus *bus); +@@ -23,4 +24,5 @@ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev); + int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, + const struct i3c_ibi_setup *req); + void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev); ++int i3c_for_each_dev(void *data, int (*fn)(struct device *, void *)); + #endif /* I3C_INTERNAL_H */ +diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c +index 5c051dba32a5..53910fb6702e 100644 +--- a/drivers/i3c/master.c ++++ b/drivers/i3c/master.c +@@ -321,6 +321,7 @@ struct bus_type i3c_bus_type = { + .probe = i3c_device_probe, + .remove = i3c_device_remove, + }; ++EXPORT_SYMBOL_GPL(i3c_bus_type); + + static enum i3c_addr_slot_status + i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr) +@@ -523,11 +524,12 @@ static void i3c_masterdev_release(struct device *dev) + of_node_put(dev->of_node); + } + +-static const struct device_type i3c_masterdev_type = { ++const struct device_type i3c_masterdev_type = { + .groups = i3c_masterdev_groups, + }; ++EXPORT_SYMBOL_GPL(i3c_masterdev_type); + +-int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode, ++static int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode, + unsigned long max_i2c_scl_rate) + { + struct i3c_master_controller *master = i3c_bus_to_i3c_master(i3cbus); +@@ -1410,19 +1412,19 @@ static void i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev) + master->ops->detach_i2c_dev(dev); + } + +-static void i3c_master_pre_assign_dyn_addr(struct i3c_dev_desc *dev) ++static int i3c_master_pre_assign_dyn_addr(struct i3c_dev_desc *dev) + { + struct i3c_master_controller *master = i3c_dev_get_master(dev); + int ret; + + if (!dev->boardinfo || !dev->boardinfo->init_dyn_addr || + !dev->boardinfo->static_addr) +- return; ++ return -1; + + ret = i3c_master_setdasa_locked(master, dev->info.static_addr, + dev->boardinfo->init_dyn_addr); + if (ret) +- return; ++ return ret; + + dev->info.dyn_addr = dev->boardinfo->init_dyn_addr; + ret = i3c_master_reattach_i3c_dev(dev, 0); +@@ -1433,10 +1435,11 @@ static void i3c_master_pre_assign_dyn_addr(struct i3c_dev_desc *dev) + if (ret) + goto err_rstdaa; + +- return; ++ return 0; + + err_rstdaa: + i3c_master_rstdaa_locked(master, dev->boardinfo->init_dyn_addr); ++ return ret; + } + + static void +@@ -1631,7 +1634,7 @@ static int i3c_master_bus_init(struct i3c_master_controller *master) + enum i3c_addr_slot_status status; + struct i2c_dev_boardinfo *i2cboardinfo; + struct i3c_dev_boardinfo *i3cboardinfo; +- struct i3c_dev_desc *i3cdev; ++ struct i3c_dev_desc *i3cdev, *i3ctmp; + struct i2c_dev_desc *i2cdev; + int ret; + +@@ -1730,8 +1733,14 @@ static int i3c_master_bus_init(struct i3c_master_controller *master) + * Pre-assign dynamic address and retrieve device information if + * needed. + */ +- i3c_bus_for_each_i3cdev(&master->bus, i3cdev) +- i3c_master_pre_assign_dyn_addr(i3cdev); ++ list_for_each_entry_safe(i3cdev, i3ctmp, &master->bus.devs.i3c, ++ common.node) { ++ ret = i3c_master_pre_assign_dyn_addr(i3cdev); ++ if (ret) { ++ i3c_master_detach_i3c_dev(i3cdev); ++ i3c_master_free_i3c_dev(i3cdev); ++ } ++ } + + ret = i3c_master_do_daa(master); + if (ret) +@@ -2638,6 +2647,18 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev) + dev->ibi = NULL; + } + ++int i3c_for_each_dev(void *data, int (*fn)(struct device *, void *)) ++{ ++ int res; ++ ++ mutex_lock(&i3c_core_lock); ++ res = bus_for_each_dev(&i3c_bus_type, NULL, data, fn); ++ mutex_unlock(&i3c_core_lock); ++ ++ return res; ++} ++EXPORT_SYMBOL_GPL(i3c_for_each_dev); ++ + static int __init i3c_init(void) + { + return bus_register(&i3c_bus_type); +diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig +index 4e80a1fcbf91..693f9aba2b17 100644 +--- a/drivers/i3c/master/Kconfig ++++ b/drivers/i3c/master/Kconfig +@@ -21,3 +21,8 @@ config DW_I3C_MASTER + + This driver can also be built as a module. If so, the module + will be called dw-i3c-master. ++ ++config ASPEED_I3C_GLOBAL ++ tristate "ASPEED I3C global driver" ++ depends on I3C ++ depends on MACH_ASPEED_G6 +diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile +index 7eea9e086144..b5ec8e8dd622 100644 +--- a/drivers/i3c/master/Makefile ++++ b/drivers/i3c/master/Makefile +@@ -1,3 +1,4 @@ + # SPDX-License-Identifier: GPL-2.0-only ++obj-$(CONFIG_ASPEED_I3C_GLOBAL) += aspeed-i3c-global.o + obj-$(CONFIG_CDNS_I3C_MASTER) += i3c-master-cdns.o + obj-$(CONFIG_DW_I3C_MASTER) += dw-i3c-master.o +diff --git a/drivers/i3c/master/aspeed-i3c-global.c b/drivers/i3c/master/aspeed-i3c-global.c +new file mode 100644 +index 000000000000..8db6c1397a6c +--- /dev/null ++++ b/drivers/i3c/master/aspeed-i3c-global.c +@@ -0,0 +1,77 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2019 ASPEED Technology Inc. ++ ++#include <linux/clk.h> ++#include <linux/irq.h> ++#include <linux/irqchip.h> ++#include <linux/irqchip/chained_irq.h> ++#include <linux/irqdomain.h> ++#include <linux/module.h> ++#include <linux/of_platform.h> ++#include <linux/platform_device.h> ++#include <linux/of_address.h> ++#include <linux/of_irq.h> ++#include <linux/io.h> ++#include <linux/reset.h> ++#include <linux/delay.h> ++ ++#define ASPEED_I3CG_CTRL(x) (0x10 + (x*0x10)) ++#define ASPEED_I3CG_SET(x) (0x14 + (x*0x10)) ++ ++struct aspeed_i3c_global { ++ void __iomem *base; ++ struct reset_control *rst; ++}; ++ ++static int aspeed_i3c_global_probe(struct platform_device *pdev) ++{ ++ struct aspeed_i3c_global *i3c_global; ++ struct device_node *node = pdev->dev.of_node; ++ int i = 0; ++ ++ i3c_global = kzalloc(sizeof(*i3c_global), GFP_KERNEL); ++ if (!i3c_global) ++ return -ENOMEM; ++ ++ i3c_global->base = of_iomap(node, 0); ++ if (!i3c_global->base) { ++ return -ENOMEM; ++ } ++ ++ i3c_global->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); ++ ++ if (IS_ERR(i3c_global->rst)) { ++ if (PTR_ERR(i3c_global->rst) != -EPROBE_DEFER) ++ dev_err(&pdev->dev, "missing or invalid reset controller device tree entry\n"); ++ return PTR_ERR(i3c_global->rst); ++ } ++ ++ reset_control_assert(i3c_global->rst); ++ udelay(3); ++ reset_control_deassert(i3c_global->rst); ++ ++ /* init */ ++ for(i = 0; i < 5; i++) ++ writel(0x000474c4, i3c_global->base + ASPEED_I3CG_SET(i)); ++ ++ return 0; ++} ++ ++static const struct of_device_id aspeed_i3c_of_match[] = { ++ { .compatible = "aspeed,ast2600-i3c-global", }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, aspeed_i3c_of_match); ++ ++static struct platform_driver aspeed_i3c_driver = { ++ .probe = aspeed_i3c_global_probe, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_i3c_of_match, ++ }, ++}; ++module_platform_driver(aspeed_i3c_driver); ++ ++MODULE_AUTHOR("Ryan Chen"); ++MODULE_DESCRIPTION("ASPEED I3C Global Driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/include/dt-bindings/clock/ast2600-clock.h b/include/dt-bindings/clock/ast2600-clock.h +index 26f84584a821..6cc47373cc97 100644 +--- a/include/dt-bindings/clock/ast2600-clock.h ++++ b/include/dt-bindings/clock/ast2600-clock.h +@@ -88,7 +88,8 @@ + #define ASPEED_CLK_MAC3RCLK 69 + #define ASPEED_CLK_MAC4RCLK 70 + #define ASPEED_CLK_UART5 71 +-#define ASPEED_CLK_MAX 72 ++#define ASPEED_CLK_I3C 72 ++#define ASPEED_CLK_MAX 73 + + /* Only list resets here that are not part of a gate */ + #define ASPEED_RESET_ADC 55 +diff --git a/include/uapi/linux/i3c/i3cdev.h b/include/uapi/linux/i3c/i3cdev.h +new file mode 100644 +index 000000000000..0897313f5516 +--- /dev/null ++++ b/include/uapi/linux/i3c/i3cdev.h +@@ -0,0 +1,38 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++/* ++ * Copyright (c) 2019 Synopsys, Inc. and/or its affiliates. ++ * ++ * Author: Vitor Soares <vitor.soares@synopsys.com> ++ */ ++ ++#ifndef _UAPI_I3C_DEV_H_ ++#define _UAPI_I3C_DEV_H_ ++ ++#include <linux/types.h> ++#include <linux/ioctl.h> ++ ++/* IOCTL commands */ ++#define I3C_DEV_IOC_MAGIC 0x07 ++ ++/** ++ * struct i3c_ioc_priv_xfer - I3C SDR ioctl private transfer ++ * @data: Holds pointer to userspace buffer with transmit data. ++ * @len: Length of data buffer buffers, in bytes. ++ * @rnw: encodes the transfer direction. true for a read, false for a write ++ */ ++struct i3c_ioc_priv_xfer { ++ __u64 data; ++ __u16 len; ++ __u8 rnw; ++ __u8 pad[5]; ++}; ++ ++ ++#define I3C_PRIV_XFER_SIZE(N) \ ++ ((((sizeof(struct i3c_ioc_priv_xfer)) * (N)) < (1 << _IOC_SIZEBITS)) \ ++ ? ((sizeof(struct i3c_ioc_priv_xfer)) * (N)) : 0) ++ ++#define I3C_IOC_PRIV_XFER(N) \ ++ _IOC(_IOC_READ|_IOC_WRITE, I3C_DEV_IOC_MAGIC, 30, I3C_PRIV_XFER_SIZE(N)) ++ ++#endif +-- +2.7.4 + |