Message ID | 20210218064019.29189-12-lizhih@xilinx.com (mailing list archive) |
---|---|
State | Superseded, archived |
Headers | show |
Series | XRT Alveo driver overview | expand |
On 2/17/21 10:40 PM, Lizhi Hou wrote: > Add UCS driver. UCS is a hardware function discovered by walking xclbin What does UCS stand for ? add to commit log > metadata. A platform device node will be created for it. > UCS enables/disables the dynamic region clocks. > > Signed-off-by: Sonal Santan <sonal.santan@xilinx.com> > Signed-off-by: Max Zhen <max.zhen@xilinx.com> > Signed-off-by: Lizhi Hou <lizhih@xilinx.com> > --- > drivers/fpga/xrt/include/xleaf/ucs.h | 24 +++ > drivers/fpga/xrt/lib/xleaf/ucs.c | 235 +++++++++++++++++++++++++++ > 2 files changed, 259 insertions(+) > create mode 100644 drivers/fpga/xrt/include/xleaf/ucs.h > create mode 100644 drivers/fpga/xrt/lib/xleaf/ucs.c > > diff --git a/drivers/fpga/xrt/include/xleaf/ucs.h b/drivers/fpga/xrt/include/xleaf/ucs.h > new file mode 100644 > index 000000000000..a5ef0e100e12 > --- /dev/null > +++ b/drivers/fpga/xrt/include/xleaf/ucs.h This header is only used by ucs.c, so is it needed ? could the enum be defined in ucs.c ? > @@ -0,0 +1,24 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Header file for XRT UCS Leaf Driver > + * > + * Copyright (C) 2020-2021 Xilinx, Inc. > + * > + * Authors: > + * Lizhi Hou <Lizhi.Hou@xilinx.com> > + */ > + > +#ifndef _XRT_UCS_H_ > +#define _XRT_UCS_H_ > + > +#include "xleaf.h" > + > +/* > + * UCS driver IOCTL calls. > + */ > +enum xrt_ucs_ioctl_cmd { > + XRT_UCS_CHECK = XRT_XLEAF_CUSTOM_BASE, /* See comments in xleaf.h */ > + XRT_UCS_ENABLE, no disable ? > +}; > + > +#endif /* _XRT_UCS_H_ */ > diff --git a/drivers/fpga/xrt/lib/xleaf/ucs.c b/drivers/fpga/xrt/lib/xleaf/ucs.c > new file mode 100644 > index 000000000000..ae762c8fddbb > --- /dev/null > +++ b/drivers/fpga/xrt/lib/xleaf/ucs.c > @@ -0,0 +1,235 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Xilinx Alveo FPGA UCS Driver > + * > + * Copyright (C) 2020-2021 Xilinx, Inc. > + * > + * Authors: > + * Lizhi Hou<Lizhi.Hou@xilinx.com> > + */ > + > +#include <linux/mod_devicetable.h> > +#include <linux/platform_device.h> > +#include <linux/delay.h> > +#include <linux/device.h> > +#include <linux/io.h> > +#include "metadata.h" > +#include "xleaf.h" > +#include "xleaf/ucs.h" > +#include "xleaf/clock.h" > + > +#define UCS_ERR(ucs, fmt, arg...) \ > + xrt_err((ucs)->pdev, fmt "\n", ##arg) > +#define UCS_WARN(ucs, fmt, arg...) \ > + xrt_warn((ucs)->pdev, fmt "\n", ##arg) > +#define UCS_INFO(ucs, fmt, arg...) \ > + xrt_info((ucs)->pdev, fmt "\n", ##arg) > +#define UCS_DBG(ucs, fmt, arg...) \ > + xrt_dbg((ucs)->pdev, fmt "\n", ##arg) > + > +#define XRT_UCS "xrt_ucs" > + > +#define CHANNEL1_OFFSET 0 > +#define CHANNEL2_OFFSET 8 > + > +#define CLK_MAX_VALUE 6400 > + > +struct ucs_control_status_ch1 { > + unsigned int shutdown_clocks_latched:1; > + unsigned int reserved1:15; > + unsigned int clock_throttling_average:14; > + unsigned int reserved2:2; > +}; Likely needs to be packed and/or the unsigned int changed to u32 > + > +struct xrt_ucs { > + struct platform_device *pdev; > + void __iomem *ucs_base; > + struct mutex ucs_lock; /* ucs dev lock */ > +}; > + > +static inline u32 reg_rd(struct xrt_ucs *ucs, u32 offset) > +{ > + return ioread32(ucs->ucs_base + offset); > +} > + > +static inline void reg_wr(struct xrt_ucs *ucs, u32 val, u32 offset) > +{ > + iowrite32(val, ucs->ucs_base + offset); > +} > + > +static void xrt_ucs_event_cb(struct platform_device *pdev, void *arg) > +{ > + struct platform_device *leaf; > + struct xrt_event *evt = (struct xrt_event *)arg; > + enum xrt_events e = evt->xe_evt; > + enum xrt_subdev_id id = evt->xe_subdev.xevt_subdev_id; > + int instance = evt->xe_subdev.xevt_subdev_instance; > + > + switch (e) { > + case XRT_EVENT_POST_CREATION: > + break; > + default: > + xrt_dbg(pdev, "ignored event %d", e); > + return; > + } this switch is a noop, remove > + > + if (id != XRT_SUBDEV_CLOCK) > + return; > + > + leaf = xleaf_get_leaf_by_id(pdev, XRT_SUBDEV_CLOCK, instance); > + if (!leaf) { > + xrt_err(pdev, "does not get clock subdev"); > + return; > + } > + > + xleaf_ioctl(leaf, XRT_CLOCK_VERIFY, NULL); > + xleaf_put_leaf(pdev, leaf); > +} > + > +static void ucs_check(struct xrt_ucs *ucs, bool *latched) > +{ checking but not returning status, change to returning int. this function is called but xrt_ucs_leaf_ioctl which does return status. > + struct ucs_control_status_ch1 *ucs_status_ch1; > + u32 status; > + > + mutex_lock(&ucs->ucs_lock); > + status = reg_rd(ucs, CHANNEL1_OFFSET); > + ucs_status_ch1 = (struct ucs_control_status_ch1 *)&status; > + if (ucs_status_ch1->shutdown_clocks_latched) { > + UCS_ERR(ucs, > + "Critical temperature or power event, kernel clocks have been stopped."); > + UCS_ERR(ucs, > + "run 'xbutil valiate -q' to continue. See AR 73398 for more details."); This error message does not seem like it would be useful, please review. > + /* explicitly indicate reset should be latched */ > + *latched = true; > + } else if (ucs_status_ch1->clock_throttling_average > > + CLK_MAX_VALUE) { > + UCS_ERR(ucs, "kernel clocks %d exceeds expected maximum value %d.", > + ucs_status_ch1->clock_throttling_average, > + CLK_MAX_VALUE); > + } else if (ucs_status_ch1->clock_throttling_average) { > + UCS_ERR(ucs, "kernel clocks throttled at %d%%.", > + (ucs_status_ch1->clock_throttling_average / > + (CLK_MAX_VALUE / 100))); > + } > + mutex_unlock(&ucs->ucs_lock); > +} > + > +static void ucs_enable(struct xrt_ucs *ucs) > +{ > + reg_wr(ucs, 1, CHANNEL2_OFFSET); lock ? > +} > + > +static int > +xrt_ucs_leaf_ioctl(struct platform_device *pdev, u32 cmd, void *arg) > +{ > + struct xrt_ucs *ucs; > + int ret = 0; > + > + ucs = platform_get_drvdata(pdev); > + > + switch (cmd) { > + case XRT_XLEAF_EVENT: > + xrt_ucs_event_cb(pdev, arg); > + break; > + case XRT_UCS_CHECK: { brace not needed here > + ucs_check(ucs, (bool *)arg); > + break; > + } > + case XRT_UCS_ENABLE: > + ucs_enable(ucs); > + break; > + default: > + xrt_err(pdev, "unsupported cmd %d", cmd); > + return -EINVAL; > + } > + > + return ret; > +} > + > +static int ucs_remove(struct platform_device *pdev) > +{ > + struct xrt_ucs *ucs; > + > + ucs = platform_get_drvdata(pdev); > + if (!ucs) { is this possible ? Tom > + xrt_err(pdev, "driver data is NULL"); > + return -EINVAL; > + } > + > + if (ucs->ucs_base) > + iounmap(ucs->ucs_base); > + > + platform_set_drvdata(pdev, NULL); > + devm_kfree(&pdev->dev, ucs); > + > + return 0; > +} > + > +static int ucs_probe(struct platform_device *pdev) > +{ > + struct xrt_ucs *ucs = NULL; > + struct resource *res; > + int ret; > + > + ucs = devm_kzalloc(&pdev->dev, sizeof(*ucs), GFP_KERNEL); > + if (!ucs) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, ucs); > + ucs->pdev = pdev; > + mutex_init(&ucs->ucs_lock); > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + ucs->ucs_base = ioremap(res->start, res->end - res->start + 1); > + if (!ucs->ucs_base) { > + UCS_ERR(ucs, "map base %pR failed", res); > + ret = -EFAULT; > + goto failed; > + } > + ucs_enable(ucs); > + > + return 0; > + > +failed: > + ucs_remove(pdev); > + return ret; > +} > + > +static struct xrt_subdev_endpoints xrt_ucs_endpoints[] = { > + { > + .xse_names = (struct xrt_subdev_ep_names[]) { > + { .ep_name = XRT_MD_NODE_UCS_CONTROL_STATUS }, > + { NULL }, > + }, > + .xse_min_ep = 1, > + }, > + { 0 }, > +}; > + > +static struct xrt_subdev_drvdata xrt_ucs_data = { > + .xsd_dev_ops = { > + .xsd_ioctl = xrt_ucs_leaf_ioctl, > + }, > +}; > + > +static const struct platform_device_id xrt_ucs_table[] = { > + { XRT_UCS, (kernel_ulong_t)&xrt_ucs_data }, > + { }, > +}; > + > +static struct platform_driver xrt_ucs_driver = { > + .driver = { > + .name = XRT_UCS, > + }, > + .probe = ucs_probe, > + .remove = ucs_remove, > + .id_table = xrt_ucs_table, > +}; > + > +void ucs_leaf_init_fini(bool init) > +{ > + if (init) > + xleaf_register_driver(XRT_SUBDEV_UCS, &xrt_ucs_driver, xrt_ucs_endpoints); > + else > + xleaf_unregister_driver(XRT_SUBDEV_UCS); > +}
Hi Tom, On 03/02/2021 08:09 AM, Tom Rix wrote: > On 2/17/21 10:40 PM, Lizhi Hou wrote: >> Add UCS driver. UCS is a hardware function discovered by walking xclbin > What does UCS stand for ? add to commit log UCS stands for User Clock Subsystem. I will add it to log. >> metadata. A platform device node will be created for it. >> UCS enables/disables the dynamic region clocks. >> >> Signed-off-by: Sonal Santan <sonal.santan@xilinx.com> >> Signed-off-by: Max Zhen <max.zhen@xilinx.com> >> Signed-off-by: Lizhi Hou <lizhih@xilinx.com> >> --- >> drivers/fpga/xrt/include/xleaf/ucs.h | 24 +++ >> drivers/fpga/xrt/lib/xleaf/ucs.c | 235 +++++++++++++++++++++++++++ >> 2 files changed, 259 insertions(+) >> create mode 100644 drivers/fpga/xrt/include/xleaf/ucs.h >> create mode 100644 drivers/fpga/xrt/lib/xleaf/ucs.c >> >> diff --git a/drivers/fpga/xrt/include/xleaf/ucs.h b/drivers/fpga/xrt/include/xleaf/ucs.h >> new file mode 100644 >> index 000000000000..a5ef0e100e12 >> --- /dev/null >> +++ b/drivers/fpga/xrt/include/xleaf/ucs.h > This header is only used by ucs.c, so is it needed ? > > could the enum be defined in ucs.c ? It will be used in the future. I will remove it. > >> @@ -0,0 +1,24 @@ >> +/* SPDX-License-Identifier: GPL-2.0 */ >> +/* >> + * Header file for XRT UCS Leaf Driver >> + * >> + * Copyright (C) 2020-2021 Xilinx, Inc. >> + * >> + * Authors: >> + * Lizhi Hou <Lizhi.Hou@xilinx.com> >> + */ >> + >> +#ifndef _XRT_UCS_H_ >> +#define _XRT_UCS_H_ >> + >> +#include "xleaf.h" >> + >> +/* >> + * UCS driver IOCTL calls. >> + */ >> +enum xrt_ucs_ioctl_cmd { >> + XRT_UCS_CHECK = XRT_XLEAF_CUSTOM_BASE, /* See comments in xleaf.h */ >> + XRT_UCS_ENABLE, > no disable ? It does not need to disable because reset the fpga bridge will disable it. I will remove ucs.h because it is not used in this patchset. >> +}; >> + >> +#endif /* _XRT_UCS_H_ */ >> diff --git a/drivers/fpga/xrt/lib/xleaf/ucs.c b/drivers/fpga/xrt/lib/xleaf/ucs.c >> new file mode 100644 >> index 000000000000..ae762c8fddbb >> --- /dev/null >> +++ b/drivers/fpga/xrt/lib/xleaf/ucs.c >> @@ -0,0 +1,235 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +/* >> + * Xilinx Alveo FPGA UCS Driver >> + * >> + * Copyright (C) 2020-2021 Xilinx, Inc. >> + * >> + * Authors: >> + * Lizhi Hou<Lizhi.Hou@xilinx.com> >> + */ >> + >> +#include <linux/mod_devicetable.h> >> +#include <linux/platform_device.h> >> +#include <linux/delay.h> >> +#include <linux/device.h> >> +#include <linux/io.h> >> +#include "metadata.h" >> +#include "xleaf.h" >> +#include "xleaf/ucs.h" >> +#include "xleaf/clock.h" >> + >> +#define UCS_ERR(ucs, fmt, arg...) \ >> + xrt_err((ucs)->pdev, fmt "\n", ##arg) >> +#define UCS_WARN(ucs, fmt, arg...) \ >> + xrt_warn((ucs)->pdev, fmt "\n", ##arg) >> +#define UCS_INFO(ucs, fmt, arg...) \ >> + xrt_info((ucs)->pdev, fmt "\n", ##arg) >> +#define UCS_DBG(ucs, fmt, arg...) \ >> + xrt_dbg((ucs)->pdev, fmt "\n", ##arg) >> + >> +#define XRT_UCS "xrt_ucs" >> + >> +#define CHANNEL1_OFFSET 0 >> +#define CHANNEL2_OFFSET 8 >> + >> +#define CLK_MAX_VALUE 6400 >> + >> +struct ucs_control_status_ch1 { >> + unsigned int shutdown_clocks_latched:1; >> + unsigned int reserved1:15; >> + unsigned int clock_throttling_average:14; >> + unsigned int reserved2:2; >> +}; > Likely needs to be packed and/or the unsigned int changed to u32 Will remove this structure because it is not used in this patch set. >> + >> +struct xrt_ucs { >> + struct platform_device *pdev; >> + void __iomem *ucs_base; >> + struct mutex ucs_lock; /* ucs dev lock */ >> +}; >> + >> +static inline u32 reg_rd(struct xrt_ucs *ucs, u32 offset) >> +{ >> + return ioread32(ucs->ucs_base + offset); >> +} >> + >> +static inline void reg_wr(struct xrt_ucs *ucs, u32 val, u32 offset) >> +{ >> + iowrite32(val, ucs->ucs_base + offset); >> +} >> + >> +static void xrt_ucs_event_cb(struct platform_device *pdev, void *arg) >> +{ >> + struct platform_device *leaf; >> + struct xrt_event *evt = (struct xrt_event *)arg; >> + enum xrt_events e = evt->xe_evt; >> + enum xrt_subdev_id id = evt->xe_subdev.xevt_subdev_id; >> + int instance = evt->xe_subdev.xevt_subdev_instance; >> + >> + switch (e) { >> + case XRT_EVENT_POST_CREATION: >> + break; >> + default: >> + xrt_dbg(pdev, "ignored event %d", e); >> + return; >> + } > this switch is a noop, remove Will change to if (e != XRT_EVENT_POST_CREATION) return -EINVAL >> + >> + if (id != XRT_SUBDEV_CLOCK) >> + return; >> + >> + leaf = xleaf_get_leaf_by_id(pdev, XRT_SUBDEV_CLOCK, instance); >> + if (!leaf) { >> + xrt_err(pdev, "does not get clock subdev"); >> + return; >> + } >> + >> + xleaf_ioctl(leaf, XRT_CLOCK_VERIFY, NULL); >> + xleaf_put_leaf(pdev, leaf); >> +} >> + >> +static void ucs_check(struct xrt_ucs *ucs, bool *latched) >> +{ > checking but not returning status, change to returning int. > > this function is called but xrt_ucs_leaf_ioctl which does return status. Will remove ucs_check() because it is not used in this patch set. > >> + struct ucs_control_status_ch1 *ucs_status_ch1; >> + u32 status; >> + >> + mutex_lock(&ucs->ucs_lock); >> + status = reg_rd(ucs, CHANNEL1_OFFSET); >> + ucs_status_ch1 = (struct ucs_control_status_ch1 *)&status; >> + if (ucs_status_ch1->shutdown_clocks_latched) { >> + UCS_ERR(ucs, >> + "Critical temperature or power event, kernel clocks have been stopped."); >> + UCS_ERR(ucs, >> + "run 'xbutil valiate -q' to continue. See AR 73398 for more details."); > This error message does not seem like it would be useful, please review. >> + /* explicitly indicate reset should be latched */ >> + *latched = true; >> + } else if (ucs_status_ch1->clock_throttling_average > >> + CLK_MAX_VALUE) { >> + UCS_ERR(ucs, "kernel clocks %d exceeds expected maximum value %d.", >> + ucs_status_ch1->clock_throttling_average, >> + CLK_MAX_VALUE); >> + } else if (ucs_status_ch1->clock_throttling_average) { >> + UCS_ERR(ucs, "kernel clocks throttled at %d%%.", >> + (ucs_status_ch1->clock_throttling_average / >> + (CLK_MAX_VALUE / 100))); >> + } >> + mutex_unlock(&ucs->ucs_lock); >> +} >> + >> +static void ucs_enable(struct xrt_ucs *ucs) >> +{ >> + reg_wr(ucs, 1, CHANNEL2_OFFSET); > lock ? Yes. will add it. >> +} >> + >> +static int >> +xrt_ucs_leaf_ioctl(struct platform_device *pdev, u32 cmd, void *arg) >> +{ >> + struct xrt_ucs *ucs; >> + int ret = 0; >> + >> + ucs = platform_get_drvdata(pdev); >> + >> + switch (cmd) { >> + case XRT_XLEAF_EVENT: >> + xrt_ucs_event_cb(pdev, arg); >> + break; >> + case XRT_UCS_CHECK: { > brace not needed here will remove. >> + ucs_check(ucs, (bool *)arg); >> + break; >> + } >> + case XRT_UCS_ENABLE: >> + ucs_enable(ucs); >> + break; >> + default: >> + xrt_err(pdev, "unsupported cmd %d", cmd); >> + return -EINVAL; >> + } >> + >> + return ret; >> +} >> + >> +static int ucs_remove(struct platform_device *pdev) >> +{ >> + struct xrt_ucs *ucs; >> + >> + ucs = platform_get_drvdata(pdev); >> + if (!ucs) { > is this possible ? Will remove. Thanks, Lizhi > > Tom > >> + xrt_err(pdev, "driver data is NULL"); >> + return -EINVAL; >> + } >> + >> + if (ucs->ucs_base) >> + iounmap(ucs->ucs_base); >> + >> + platform_set_drvdata(pdev, NULL); >> + devm_kfree(&pdev->dev, ucs); >> + >> + return 0; >> +} >> + >> +static int ucs_probe(struct platform_device *pdev) >> +{ >> + struct xrt_ucs *ucs = NULL; >> + struct resource *res; >> + int ret; >> + >> + ucs = devm_kzalloc(&pdev->dev, sizeof(*ucs), GFP_KERNEL); >> + if (!ucs) >> + return -ENOMEM; >> + >> + platform_set_drvdata(pdev, ucs); >> + ucs->pdev = pdev; >> + mutex_init(&ucs->ucs_lock); >> + >> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); >> + ucs->ucs_base = ioremap(res->start, res->end - res->start + 1); >> + if (!ucs->ucs_base) { >> + UCS_ERR(ucs, "map base %pR failed", res); >> + ret = -EFAULT; >> + goto failed; >> + } >> + ucs_enable(ucs); >> + >> + return 0; >> + >> +failed: >> + ucs_remove(pdev); >> + return ret; >> +} >> + >> +static struct xrt_subdev_endpoints xrt_ucs_endpoints[] = { >> + { >> + .xse_names = (struct xrt_subdev_ep_names[]) { >> + { .ep_name = XRT_MD_NODE_UCS_CONTROL_STATUS }, >> + { NULL }, >> + }, >> + .xse_min_ep = 1, >> + }, >> + { 0 }, >> +}; >> + >> +static struct xrt_subdev_drvdata xrt_ucs_data = { >> + .xsd_dev_ops = { >> + .xsd_ioctl = xrt_ucs_leaf_ioctl, >> + }, >> +}; >> + >> +static const struct platform_device_id xrt_ucs_table[] = { >> + { XRT_UCS, (kernel_ulong_t)&xrt_ucs_data }, >> + { }, >> +}; >> + >> +static struct platform_driver xrt_ucs_driver = { >> + .driver = { >> + .name = XRT_UCS, >> + }, >> + .probe = ucs_probe, >> + .remove = ucs_remove, >> + .id_table = xrt_ucs_table, >> +}; >> + >> +void ucs_leaf_init_fini(bool init) >> +{ >> + if (init) >> + xleaf_register_driver(XRT_SUBDEV_UCS, &xrt_ucs_driver, xrt_ucs_endpoints); >> + else >> + xleaf_unregister_driver(XRT_SUBDEV_UCS); >> +}
diff --git a/drivers/fpga/xrt/include/xleaf/ucs.h b/drivers/fpga/xrt/include/xleaf/ucs.h new file mode 100644 index 000000000000..a5ef0e100e12 --- /dev/null +++ b/drivers/fpga/xrt/include/xleaf/ucs.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file for XRT UCS Leaf Driver + * + * Copyright (C) 2020-2021 Xilinx, Inc. + * + * Authors: + * Lizhi Hou <Lizhi.Hou@xilinx.com> + */ + +#ifndef _XRT_UCS_H_ +#define _XRT_UCS_H_ + +#include "xleaf.h" + +/* + * UCS driver IOCTL calls. + */ +enum xrt_ucs_ioctl_cmd { + XRT_UCS_CHECK = XRT_XLEAF_CUSTOM_BASE, /* See comments in xleaf.h */ + XRT_UCS_ENABLE, +}; + +#endif /* _XRT_UCS_H_ */ diff --git a/drivers/fpga/xrt/lib/xleaf/ucs.c b/drivers/fpga/xrt/lib/xleaf/ucs.c new file mode 100644 index 000000000000..ae762c8fddbb --- /dev/null +++ b/drivers/fpga/xrt/lib/xleaf/ucs.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Alveo FPGA UCS Driver + * + * Copyright (C) 2020-2021 Xilinx, Inc. + * + * Authors: + * Lizhi Hou<Lizhi.Hou@xilinx.com> + */ + +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include "metadata.h" +#include "xleaf.h" +#include "xleaf/ucs.h" +#include "xleaf/clock.h" + +#define UCS_ERR(ucs, fmt, arg...) \ + xrt_err((ucs)->pdev, fmt "\n", ##arg) +#define UCS_WARN(ucs, fmt, arg...) \ + xrt_warn((ucs)->pdev, fmt "\n", ##arg) +#define UCS_INFO(ucs, fmt, arg...) \ + xrt_info((ucs)->pdev, fmt "\n", ##arg) +#define UCS_DBG(ucs, fmt, arg...) \ + xrt_dbg((ucs)->pdev, fmt "\n", ##arg) + +#define XRT_UCS "xrt_ucs" + +#define CHANNEL1_OFFSET 0 +#define CHANNEL2_OFFSET 8 + +#define CLK_MAX_VALUE 6400 + +struct ucs_control_status_ch1 { + unsigned int shutdown_clocks_latched:1; + unsigned int reserved1:15; + unsigned int clock_throttling_average:14; + unsigned int reserved2:2; +}; + +struct xrt_ucs { + struct platform_device *pdev; + void __iomem *ucs_base; + struct mutex ucs_lock; /* ucs dev lock */ +}; + +static inline u32 reg_rd(struct xrt_ucs *ucs, u32 offset) +{ + return ioread32(ucs->ucs_base + offset); +} + +static inline void reg_wr(struct xrt_ucs *ucs, u32 val, u32 offset) +{ + iowrite32(val, ucs->ucs_base + offset); +} + +static void xrt_ucs_event_cb(struct platform_device *pdev, void *arg) +{ + struct platform_device *leaf; + struct xrt_event *evt = (struct xrt_event *)arg; + enum xrt_events e = evt->xe_evt; + enum xrt_subdev_id id = evt->xe_subdev.xevt_subdev_id; + int instance = evt->xe_subdev.xevt_subdev_instance; + + switch (e) { + case XRT_EVENT_POST_CREATION: + break; + default: + xrt_dbg(pdev, "ignored event %d", e); + return; + } + + if (id != XRT_SUBDEV_CLOCK) + return; + + leaf = xleaf_get_leaf_by_id(pdev, XRT_SUBDEV_CLOCK, instance); + if (!leaf) { + xrt_err(pdev, "does not get clock subdev"); + return; + } + + xleaf_ioctl(leaf, XRT_CLOCK_VERIFY, NULL); + xleaf_put_leaf(pdev, leaf); +} + +static void ucs_check(struct xrt_ucs *ucs, bool *latched) +{ + struct ucs_control_status_ch1 *ucs_status_ch1; + u32 status; + + mutex_lock(&ucs->ucs_lock); + status = reg_rd(ucs, CHANNEL1_OFFSET); + ucs_status_ch1 = (struct ucs_control_status_ch1 *)&status; + if (ucs_status_ch1->shutdown_clocks_latched) { + UCS_ERR(ucs, + "Critical temperature or power event, kernel clocks have been stopped."); + UCS_ERR(ucs, + "run 'xbutil valiate -q' to continue. See AR 73398 for more details."); + /* explicitly indicate reset should be latched */ + *latched = true; + } else if (ucs_status_ch1->clock_throttling_average > + CLK_MAX_VALUE) { + UCS_ERR(ucs, "kernel clocks %d exceeds expected maximum value %d.", + ucs_status_ch1->clock_throttling_average, + CLK_MAX_VALUE); + } else if (ucs_status_ch1->clock_throttling_average) { + UCS_ERR(ucs, "kernel clocks throttled at %d%%.", + (ucs_status_ch1->clock_throttling_average / + (CLK_MAX_VALUE / 100))); + } + mutex_unlock(&ucs->ucs_lock); +} + +static void ucs_enable(struct xrt_ucs *ucs) +{ + reg_wr(ucs, 1, CHANNEL2_OFFSET); +} + +static int +xrt_ucs_leaf_ioctl(struct platform_device *pdev, u32 cmd, void *arg) +{ + struct xrt_ucs *ucs; + int ret = 0; + + ucs = platform_get_drvdata(pdev); + + switch (cmd) { + case XRT_XLEAF_EVENT: + xrt_ucs_event_cb(pdev, arg); + break; + case XRT_UCS_CHECK: { + ucs_check(ucs, (bool *)arg); + break; + } + case XRT_UCS_ENABLE: + ucs_enable(ucs); + break; + default: + xrt_err(pdev, "unsupported cmd %d", cmd); + return -EINVAL; + } + + return ret; +} + +static int ucs_remove(struct platform_device *pdev) +{ + struct xrt_ucs *ucs; + + ucs = platform_get_drvdata(pdev); + if (!ucs) { + xrt_err(pdev, "driver data is NULL"); + return -EINVAL; + } + + if (ucs->ucs_base) + iounmap(ucs->ucs_base); + + platform_set_drvdata(pdev, NULL); + devm_kfree(&pdev->dev, ucs); + + return 0; +} + +static int ucs_probe(struct platform_device *pdev) +{ + struct xrt_ucs *ucs = NULL; + struct resource *res; + int ret; + + ucs = devm_kzalloc(&pdev->dev, sizeof(*ucs), GFP_KERNEL); + if (!ucs) + return -ENOMEM; + + platform_set_drvdata(pdev, ucs); + ucs->pdev = pdev; + mutex_init(&ucs->ucs_lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ucs->ucs_base = ioremap(res->start, res->end - res->start + 1); + if (!ucs->ucs_base) { + UCS_ERR(ucs, "map base %pR failed", res); + ret = -EFAULT; + goto failed; + } + ucs_enable(ucs); + + return 0; + +failed: + ucs_remove(pdev); + return ret; +} + +static struct xrt_subdev_endpoints xrt_ucs_endpoints[] = { + { + .xse_names = (struct xrt_subdev_ep_names[]) { + { .ep_name = XRT_MD_NODE_UCS_CONTROL_STATUS }, + { NULL }, + }, + .xse_min_ep = 1, + }, + { 0 }, +}; + +static struct xrt_subdev_drvdata xrt_ucs_data = { + .xsd_dev_ops = { + .xsd_ioctl = xrt_ucs_leaf_ioctl, + }, +}; + +static const struct platform_device_id xrt_ucs_table[] = { + { XRT_UCS, (kernel_ulong_t)&xrt_ucs_data }, + { }, +}; + +static struct platform_driver xrt_ucs_driver = { + .driver = { + .name = XRT_UCS, + }, + .probe = ucs_probe, + .remove = ucs_remove, + .id_table = xrt_ucs_table, +}; + +void ucs_leaf_init_fini(bool init) +{ + if (init) + xleaf_register_driver(XRT_SUBDEV_UCS, &xrt_ucs_driver, xrt_ucs_endpoints); + else + xleaf_unregister_driver(XRT_SUBDEV_UCS); +}