From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.5 required=3.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH,MAILING_LIST_MULTI,SIGNED_OFF_BY,SPF_PASS,USER_AGENT_MUTT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id AEE5BC282C0 for ; Fri, 25 Jan 2019 07:54:24 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 2E21A217D7 for ; Fri, 25 Jan 2019 07:54:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728354AbfAYHyW (ORCPT ); Fri, 25 Jan 2019 02:54:22 -0500 Received: from mx0b-001b2d01.pphosted.com ([148.163.158.5]:44360 "EHLO mx0a-001b2d01.pphosted.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726879AbfAYHyW (ORCPT ); Fri, 25 Jan 2019 02:54:22 -0500 Received: from pps.filterd (m0098413.ppops.net [127.0.0.1]) by mx0b-001b2d01.pphosted.com (8.16.0.27/8.16.0.27) with SMTP id x0P7na2K039335 for ; Fri, 25 Jan 2019 02:54:12 -0500 Received: from e06smtp04.uk.ibm.com (e06smtp04.uk.ibm.com [195.75.94.100]) by mx0b-001b2d01.pphosted.com with ESMTP id 2q7w4x3mjt-1 (version=TLSv1.2 cipher=AES256-GCM-SHA384 bits=256 verify=NOT) for ; Fri, 25 Jan 2019 02:54:11 -0500 Received: from localhost by e06smtp04.uk.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Fri, 25 Jan 2019 07:54:09 -0000 Received: from b06cxnps4075.portsmouth.uk.ibm.com (9.149.109.197) by e06smtp04.uk.ibm.com (192.168.101.134) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; (version=TLSv1/SSLv3 cipher=AES256-GCM-SHA384 bits=256/256) Fri, 25 Jan 2019 07:54:07 -0000 Received: from d06av22.portsmouth.uk.ibm.com (d06av22.portsmouth.uk.ibm.com [9.149.105.58]) by b06cxnps4075.portsmouth.uk.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id x0P7s6Kt1049008 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Fri, 25 Jan 2019 07:54:06 GMT Received: from d06av22.portsmouth.uk.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 6FD9B4C04E; Fri, 25 Jan 2019 07:54:06 +0000 (GMT) Received: from d06av22.portsmouth.uk.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id D840B4C044; Fri, 25 Jan 2019 07:54:04 +0000 (GMT) Received: from rapoport-lnx (unknown [9.148.204.19]) by d06av22.portsmouth.uk.ibm.com (Postfix) with ESMTPS; Fri, 25 Jan 2019 07:54:04 +0000 (GMT) Date: Fri, 25 Jan 2019 09:54:03 +0200 From: Mike Rapoport To: Oded Gabbay Cc: gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org, ogabbay@habana.ai Subject: Re: [PATCH 09/15] habanalabs: add sysfs and hwmon support References: <20190123000057.31477-1-oded.gabbay@gmail.com> <20190123000057.31477-10-oded.gabbay@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20190123000057.31477-10-oded.gabbay@gmail.com> User-Agent: Mutt/1.5.24 (2015-08-30) X-TM-AS-GCONF: 00 x-cbid: 19012507-0016-0000-0000-0000024A3C2C X-IBM-AV-DETECTION: SAVI=unused REMOTE=unused XFE=unused x-cbparentid: 19012507-0017-0000-0000-000032A4844C Message-Id: <20190125075401.GE31519@rapoport-lnx> X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:,, definitions=2019-01-25_05:,, signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 priorityscore=1501 malwarescore=0 suspectscore=13 phishscore=0 bulkscore=0 spamscore=0 clxscore=1015 lowpriorityscore=0 mlxscore=0 impostorscore=0 mlxlogscore=999 adultscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.0.1-1810050000 definitions=main-1901250066 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Wed, Jan 23, 2019 at 02:00:51AM +0200, Oded Gabbay wrote: > This patch add the sysfs and hwmon entries that are exposed by the driver. > > Goya has several sensors, from various categories such as temperature, > voltage, current, etc. The driver exposes those sensors in the standard > hwmon mechanism. > > In addition, the driver exposes a couple of interfaces in sysfs, both for > configuration and for providing status of the device or driver. > > The configuration attributes is for Power Management: > - Automatic or manual > - Frequency value when moving to high frequency mode > - Maximum power the device is allowed to consume > > The rest of the attributes are read-only and provide the following > information: > - Versions of the various firmwares running on the device > - Contents of the device's EEPROM > - The device type (currently only Goya is supported) > - PCI address of the device (to allow user-space to connect between > /dev/hlX to PCI address) > - Status of the device (operational, malfunction, in_reset) > - How many processes are open on the device's file > > Signed-off-by: Oded Gabbay > --- > .../ABI/testing/sysfs-driver-habanalabs | 190 ++++++ > drivers/misc/habanalabs/Makefile | 2 +- > drivers/misc/habanalabs/device.c | 146 +++++ > drivers/misc/habanalabs/goya/Makefile | 2 +- > drivers/misc/habanalabs/goya/goya.c | 230 +++++++ > drivers/misc/habanalabs/goya/goyaP.h | 21 + > drivers/misc/habanalabs/goya/goya_hwmgr.c | 306 +++++++++ > drivers/misc/habanalabs/habanalabs.h | 97 +++ > drivers/misc/habanalabs/habanalabs_drv.c | 7 + > drivers/misc/habanalabs/hwmon.c | 449 +++++++++++++ > drivers/misc/habanalabs/sysfs.c | 588 ++++++++++++++++++ > 11 files changed, 2036 insertions(+), 2 deletions(-) > create mode 100644 Documentation/ABI/testing/sysfs-driver-habanalabs > create mode 100644 drivers/misc/habanalabs/goya/goya_hwmgr.c > create mode 100644 drivers/misc/habanalabs/hwmon.c > create mode 100644 drivers/misc/habanalabs/sysfs.c > > diff --git a/Documentation/ABI/testing/sysfs-driver-habanalabs b/Documentation/ABI/testing/sysfs-driver-habanalabs > new file mode 100644 > index 000000000000..19edd4da87c1 > --- /dev/null > +++ b/Documentation/ABI/testing/sysfs-driver-habanalabs > @@ -0,0 +1,190 @@ > +What: /sys/class/habanalabs/hl/armcp_kernel_ver > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Version of the Linux kernel running on the device's CPU > + > +What: /sys/class/habanalabs/hl/armcp_ver > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Version of the application running on the device's CPU > + > +What: /sys/class/habanalabs/hl/cpld_ver > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Version of the Device's CPLD F/W > + > +What: /sys/class/habanalabs/hl/device_type > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Displays the code name of the device according to its type. > + The supported values are: "GOYA" > + > +What: /sys/class/habanalabs/hl/eeprom > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: A binary file attribute that contains the contents of the > + on-board EEPROM > + > +What: /sys/class/habanalabs/hl/fuse_ver > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Displays the device's version from the eFuse > + > +What: /sys/class/habanalabs/hl/hard_reset > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Interface to trigger a hard-reset operation for the device. > + Hard-reset will reset ALL internal components of the device > + except for the PCI interface and the internal PLLs > + > +What: /sys/class/habanalabs/hl/hard_reset_cnt > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Displays how many times the device have undergone a hard-reset > + operation > + > +What: /sys/class/habanalabs/hl/high_pll > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Allows the user to set the maximum clock frequency for MME, TPC > + and IC when the power management profile is set to "automatic". > + > +What: /sys/class/habanalabs/hl/ic_clk > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Allows the user to set the maximum clock frequency of the > + Interconnect fabric. Writes to this parameter affect the device > + only when the power management profile is set to "manual" mode. > + The device IC clock might be set to lower value then the > + maximum. The user should read the ic_clk_curr to see the actual > + frequency value of the IC > + > +What: /sys/class/habanalabs/hl/ic_clk_curr > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Displays the current clock frequency of the Interconnect fabric > + > +What: /sys/class/habanalabs/hl/infineon_ver > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Version of the Device's power supply F/W code > + > +What: /sys/class/habanalabs/hl/max_power > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Allows the user to set the maximum power consumption of the > + device in milliwatts. > + > +What: /sys/class/habanalabs/hl/mme_clk > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Allows the user to set the maximum clock frequency of the > + MME compute engine. Writes to this parameter affect the device > + only when the power management profile is set to "manual" mode. > + The device MME clock might be set to lower value then the > + maximum. The user should read the mme_clk_curr to see the actual > + frequency value of the MME > + > +What: /sys/class/habanalabs/hl/mme_clk_curr > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Displays the current clock frequency of the MME compute engine > + > +What: /sys/class/habanalabs/hl/pci_addr > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Displays the PCI address of the device. This is needed so the > + user would be able to open a device based on its PCI address > + > +What: /sys/class/habanalabs/hl/pm_mng_profile > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Power management profile. Values are "auto", "manual". In "auto" > + mode, the driver will set the maximum clock frequency to a high > + value when a user-space process opens the device's file (unless > + it was already opened by another process). The driver will set > + the max clock frequency to a low value when there are no user > + processes that are opened on the device's file. In "manual" > + mode, the user sets the maximum clock frequency by writing to > + ic_clk, mme_clk and tpc_clk > + > + > +What: /sys/class/habanalabs/hl/preboot_btl_ver > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Version of the device's preboot F/W code > + > +What: /sys/class/habanalabs/hl/soft_reset > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Interface to trigger a soft-reset operation for the device. > + Soft-reset will reset only the compute and DMA engines of the > + device > + > +What: /sys/class/habanalabs/hl/soft_reset_cnt > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Displays how many times the device have undergone a soft-reset > + operation > + > +What: /sys/class/habanalabs/hl/status > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Status of the card: "Operational", "Malfunction", "In reset". > + > +What: /sys/class/habanalabs/hl/thermal_ver > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Version of the Device's thermal daemon > + > +What: /sys/class/habanalabs/hl/tpc_clk > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Allows the user to set the maximum clock frequency of the > + TPC compute engines. Writes to this parameter affect the device > + only when the power management profile is set to "manual" mode. > + The device TPC clock might be set to lower value then the > + maximum. The user should read the tpc_clk_curr to see the actual > + frequency value of the TPC > + > +What: /sys/class/habanalabs/hl/tpc_clk_curr > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Displays the current clock frequency of the TPC compute engines > + > +What: /sys/class/habanalabs/hl/uboot_ver > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Version of the u-boot running on the device's CPU > + > +What: /sys/class/habanalabs/hl/write_open_cnt > +Date: Jan 2019 > +KernelVersion: 5.1 > +Contact: oded.gabbay@gmail.com > +Description: Displays the total number of user processes that are currently > + opened on the device's file > diff --git a/drivers/misc/habanalabs/Makefile b/drivers/misc/habanalabs/Makefile > index c07f3ccb57dc..b5607233d216 100644 > --- a/drivers/misc/habanalabs/Makefile > +++ b/drivers/misc/habanalabs/Makefile > @@ -5,7 +5,7 @@ > obj-m := habanalabs.o > > habanalabs-y := habanalabs_drv.o device.o context.o asid.o habanalabs_ioctl.o \ > - command_buffer.o hw_queue.o irq.o > + command_buffer.o hw_queue.o irq.o sysfs.o hwmon.o > > include $(src)/goya/Makefile > habanalabs-y += $(HL_GOYA_FILES) > diff --git a/drivers/misc/habanalabs/device.c b/drivers/misc/habanalabs/device.c > index 9199e070e79e..ff7b610f18c4 100644 > --- a/drivers/misc/habanalabs/device.c > +++ b/drivers/misc/habanalabs/device.c > @@ -226,6 +226,118 @@ static void device_early_fini(struct hl_device *hdev) > mutex_destroy(&hdev->device_open); > } > > +static void set_freq_to_low_job(struct work_struct *work) > +{ > + struct hl_device *hdev = container_of(work, struct hl_device, > + work_freq.work); > + > + if (atomic_read(&hdev->fd_open_cnt) == 0) > + hl_device_set_frequency(hdev, PLL_LOW); > + > + schedule_delayed_work(&hdev->work_freq, > + usecs_to_jiffies(HL_PLL_LOW_JOB_FREQ_USEC)); > +} > + > +/** > + * device_late_init - do late stuff initialization for the habanalabs device > + * > + * @hdev: pointer to habanalabs device structure > + * > + * Do stuff that either needs the device H/W queues to be active or needs > + * to happen after all the rest of the initialization is finished > + */ > +static int device_late_init(struct hl_device *hdev) > +{ > + int rc; > + > + INIT_DELAYED_WORK(&hdev->work_freq, set_freq_to_low_job); > + hdev->high_pll = hdev->asic_prop.high_pll; > + > + /* force setting to low frequency */ > + atomic_set(&hdev->curr_pll_profile, PLL_LOW); > + > + if (hdev->pm_mng_profile == PM_AUTO) > + hdev->asic_funcs->set_pll_profile(hdev, PLL_LOW); > + else > + hdev->asic_funcs->set_pll_profile(hdev, PLL_LAST); > + > + if (hdev->asic_funcs->late_init) { > + rc = hdev->asic_funcs->late_init(hdev); > + if (rc) { > + dev_err(hdev->dev, > + "failed late initialization for the H/W\n"); > + return rc; > + } > + } > + > + schedule_delayed_work(&hdev->work_freq, > + usecs_to_jiffies(HL_PLL_LOW_JOB_FREQ_USEC)); > + > + hdev->late_init_done = true; > + > + return 0; > +} > + > +/** > + * device_late_fini - finalize all that was done in device_late_init > + * > + * @hdev: pointer to habanalabs device structure > + * > + */ > +static void device_late_fini(struct hl_device *hdev) > +{ > + if (!hdev->late_init_done) > + return; > + > + cancel_delayed_work_sync(&hdev->work_freq); > + > + if (hdev->asic_funcs->late_fini) > + hdev->asic_funcs->late_fini(hdev); > + > + hdev->late_init_done = false; > +} > + > +/** > + * hl_device_set_frequency - set the frequency of the device > + * > + * @hdev: pointer to habanalabs device structure > + * @freq: the new frequency value > + * > + * Change the frequency if needed. > + * We allose to set PLL to low only if there is no user process > + * Returns 0 if no change was done, otherwise returns 1; > + */ > +int hl_device_set_frequency(struct hl_device *hdev, enum hl_pll_frequency freq) > +{ > + enum hl_pll_frequency old_freq = > + (freq == PLL_HIGH) ? PLL_LOW : PLL_HIGH; > + int ret; > + > + if (hdev->pm_mng_profile == PM_MANUAL) > + return 0; > + > + ret = atomic_cmpxchg(&hdev->curr_pll_profile, old_freq, freq); > + if (ret == freq) > + return 0; > + > + /* > + * in case we want to lower frequency, check if device is not > + * opened. We must have a check here to workaround race condition with > + * hl_device_open > + */ > + if ((freq == PLL_LOW) && (atomic_read(&hdev->fd_open_cnt) > 0)) { > + atomic_set(&hdev->curr_pll_profile, PLL_HIGH); > + return 0; > + } > + > + dev_dbg(hdev->dev, "Changing device frequency to %s\n", > + freq == PLL_HIGH ? "high" : "low"); > + > + hdev->asic_funcs->set_pll_profile(hdev, freq); > + > + return 1; > +} > + > /** > * hl_device_suspend - initiate device suspend > * > @@ -386,6 +498,12 @@ int hl_device_init(struct hl_device *hdev, struct class *hclass) > goto release_ctx; > } > > + rc = hl_sysfs_init(hdev); > + if (rc) { > + dev_err(hdev->dev, "failed to initialize sysfs\n"); > + goto free_cb_pool; > + } > + > rc = hdev->asic_funcs->hw_init(hdev); > if (rc) { > dev_err(hdev->dev, "failed to initialize the H/W\n"); > @@ -403,11 +521,33 @@ int hl_device_init(struct hl_device *hdev, struct class *hclass) > goto out_disabled; > } > > + /* After test_queues, KMD can start sending messages to device CPU */ > + > + rc = device_late_init(hdev); > + if (rc) { > + dev_err(hdev->dev, "Failed late initialization\n"); > + rc = 0; Isn't this an error? > + goto out_disabled; > + } > + > + dev_info(hdev->dev, "Found %s device with %lluGB DRAM\n", > + hdev->asic_name, > + hdev->asic_prop.dram_size / 1024 / 1024 / 1024); > + > + rc = hl_hwmon_init(hdev); > + if (rc) { > + dev_err(hdev->dev, "Failed to initialize hwmon\n"); > + rc = 0; Ditto > + goto out_disabled; > + } > + > dev_notice(hdev->dev, > "Successfully added device to habanalabs driver\n"); > > return 0; > > +free_cb_pool: > + hl_cb_pool_fini(hdev); > release_ctx: > if (hl_ctx_put(hdev->kernel_ctx) != 1) > dev_err(hdev->dev, > @@ -457,6 +597,12 @@ void hl_device_fini(struct hl_device *hdev) > /* Mark device as disabled */ > hdev->disabled = true; > > + hl_hwmon_fini(hdev); > + > + device_late_fini(hdev); > + > + hl_sysfs_fini(hdev); > + > /* > * Halt the engines and disable interrupts so we won't get any more > * completions from H/W and we won't have any accesses from the > diff --git a/drivers/misc/habanalabs/goya/Makefile b/drivers/misc/habanalabs/goya/Makefile > index a57096fa41b6..ada8518ec215 100644 > --- a/drivers/misc/habanalabs/goya/Makefile > +++ b/drivers/misc/habanalabs/goya/Makefile > @@ -1,3 +1,3 @@ > subdir-ccflags-y += -I$(src) > > -HL_GOYA_FILES := goya/goya.o goya/goya_security.o > \ No newline at end of file > +HL_GOYA_FILES := goya/goya.o goya/goya_security.o goya/goya_hwmgr.o > \ No newline at end of file > diff --git a/drivers/misc/habanalabs/goya/goya.c b/drivers/misc/habanalabs/goya/goya.c > index 6c04277ae0fa..7899ff762e0b 100644 > --- a/drivers/misc/habanalabs/goya/goya.c > +++ b/drivers/misc/habanalabs/goya/goya.c > @@ -127,6 +127,8 @@ static const char *goya_axi_name[GOYA_MAX_INITIATORS] = { > > #define GOYA_ASYC_EVENT_GROUP_NON_FATAL_SIZE 121 > > +static int goya_armcp_info_get(struct hl_device *hdev); > + > static void goya_get_fixed_properties(struct hl_device *hdev) > { > struct asic_fixed_properties *prop = &hdev->asic_prop; > @@ -174,6 +176,7 @@ static void goya_get_fixed_properties(struct hl_device *hdev) > prop->num_of_events = GOYA_ASYNC_EVENT_ID_SIZE; > prop->cb_pool_cb_cnt = GOYA_CB_POOL_CB_CNT; > prop->cb_pool_cb_size = GOYA_CB_POOL_CB_SIZE; > + prop->max_power_default = MAX_POWER_DEFAULT; > prop->tpc_enabled_mask = TPC_ENABLED_MASK; > > prop->high_pll = PLL_HIGH_DEFAULT; > @@ -558,6 +561,89 @@ int goya_early_fini(struct hl_device *hdev) > return 0; > } > > +/** > + * goya_fetch_psoc_frequency - Fetch PSOC frequency values > + * > + * @hdev: pointer to hl_device structure > + * > + */ > +static void goya_fetch_psoc_frequency(struct hl_device *hdev) > +{ > + struct asic_fixed_properties *prop = &hdev->asic_prop; > + > + prop->psoc_pci_pll_nr = RREG32(mmPSOC_PCI_PLL_NR); > + prop->psoc_pci_pll_nf = RREG32(mmPSOC_PCI_PLL_NF); > + prop->psoc_pci_pll_od = RREG32(mmPSOC_PCI_PLL_OD); > + prop->psoc_pci_pll_div_factor = RREG32(mmPSOC_PCI_PLL_DIV_FACTOR_1); > +} > + > +/** > + * goya_late_init - GOYA late initialization code > + * > + * @hdev: pointer to hl_device structure > + * > + * Get ArmCP info and send message to CPU to enable PCI access > + */ > +static int goya_late_init(struct hl_device *hdev) > +{ > + struct asic_fixed_properties *prop = &hdev->asic_prop; > + struct goya_device *goya = hdev->asic_specific; > + int rc; > + > + rc = goya->armcp_info_get(hdev); > + if (rc) { > + dev_err(hdev->dev, "Failed to get armcp info\n"); > + return rc; > + } > + > + /* Now that we have the DRAM size in ASIC prop, we need to check > + * its size and configure the DMA_IF DDR wrap protection (which is in > + * the MMU block) accordingly. The value is the log2 of the DRAM size > + */ > + WREG32(mmMMU_LOG2_DDR_SIZE, ilog2(prop->dram_size)); > + > + rc = goya_send_pci_access_msg(hdev, ARMCP_PACKET_ENABLE_PCI_ACCESS); > + if (rc) { > + dev_err(hdev->dev, "Failed to enable PCI access from CPU\n"); > + return rc; > + } > + > + WREG32(mmGIC_DISTRIBUTOR__5_GICD_SETSPI_NSR, > + GOYA_ASYNC_EVENT_ID_INTS_REGISTER); > + > + goya_fetch_psoc_frequency(hdev); > + > + return 0; > +} > + > +/** > + * goya_late_fini - GOYA late tear-down code > + * > + * @hdev: pointer to hl_device structure > + * > + * Free sensors allocated structures > + */ > +void goya_late_fini(struct hl_device *hdev) > +{ > + const struct hwmon_channel_info **channel_info_arr; > + int i = 0; > + > + if (!hdev->hl_chip_info.info) > + return; > + > + channel_info_arr = hdev->hl_chip_info.info; > + > + while (channel_info_arr[i]) { > + kfree(channel_info_arr[i]->config); > + kfree(channel_info_arr[i]); > + i++; > + } > + > + kfree(channel_info_arr); > + > + hdev->hl_chip_info.info = NULL; > +} > + > /** > * goya_sw_init - Goya software initialization code > * > @@ -575,9 +661,15 @@ static int goya_sw_init(struct hl_device *hdev) > return -ENOMEM; > > goya->test_cpu_queue = goya_test_cpu_queue; > + goya->armcp_info_get = goya_armcp_info_get; > > /* according to goya_init_iatu */ > goya->ddr_bar_cur_addr = DRAM_PHYS_BASE; > + > + goya->mme_clk = GOYA_PLL_FREQ_LOW; > + goya->tpc_clk = GOYA_PLL_FREQ_LOW; > + goya->ic_clk = GOYA_PLL_FREQ_LOW; > + > hdev->asic_specific = goya; > > /* Create DMA pool for small allocations */ > @@ -4272,6 +4364,87 @@ void *goya_get_events_stat(struct hl_device *hdev, u32 *size) > return goya->events_stat; > } > > +static int goya_armcp_info_get(struct hl_device *hdev) > +{ > + struct goya_device *goya = hdev->asic_specific; > + struct asic_fixed_properties *prop = &hdev->asic_prop; > + struct armcp_packet pkt; > + void *armcp_info_cpu_addr; > + dma_addr_t armcp_info_dma_addr; > + u64 dram_size; > + long result; > + int rc; > + > + if (!(goya->hw_cap_initialized & HW_CAP_CPU_Q)) > + return 0; > + > + armcp_info_cpu_addr = > + hdev->asic_funcs->cpu_accessible_dma_pool_alloc(hdev, > + sizeof(struct armcp_info), &armcp_info_dma_addr); > + if (!armcp_info_cpu_addr) { > + dev_err(hdev->dev, > + "Failed to allocate DMA memory for ArmCP info packet\n"); > + return -ENOMEM; > + } > + > + memset(armcp_info_cpu_addr, 0, sizeof(struct armcp_info)); Do you expect usage of cpu_accessible_dma_pool_alloc() without the need to clear the memory? If not memset(0) can be moved inside that function. > + > + memset(&pkt, 0, sizeof(pkt)); > + > + pkt.opcode = ARMCP_PACKET_INFO_GET; > + pkt.addr = armcp_info_dma_addr + prop->host_phys_base_address; > + pkt.data_max_size = sizeof(struct armcp_info); > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + GOYA_ARMCP_INFO_TIMEOUT, &result); > + > + if (rc) { > + dev_err(hdev->dev, > + "Failed to send armcp info pkt, error %d\n", rc); > + goto out; > + } > + > + memcpy(&prop->armcp_info, armcp_info_cpu_addr, > + sizeof(prop->armcp_info)); > + > + dram_size = prop->armcp_info.dram_size; > + if (dram_size) { > + if ((!is_power_of_2(dram_size)) || > + (dram_size < DRAM_PHYS_DEFAULT_SIZE)) { > + dev_err(hdev->dev, > + "F/W reported invalid DRAM size %llu. Trying to use default size\n", > + dram_size); > + dram_size = DRAM_PHYS_DEFAULT_SIZE; > + } > + > + prop->dram_size = dram_size; > + prop->dram_end_address = prop->dram_base_address + dram_size; > + } > + > + rc = hl_build_hwmon_channel_info(hdev, prop->armcp_info.sensors); > + if (rc) { > + dev_err(hdev->dev, > + "Failed to build hwmon channel info, error %d\n", rc); > + rc = -EFAULT; > + goto out; > + } > + > +out: > + hdev->asic_funcs->cpu_accessible_dma_pool_free(hdev, > + sizeof(struct armcp_info), armcp_info_cpu_addr); > + > + return rc; > +} > + > +static void goya_init_clock_gating(struct hl_device *hdev) > +{ > + > +} > + > +static void goya_disable_clock_gating(struct hl_device *hdev) > +{ > + > +} > > static void goya_hw_queues_lock(struct hl_device *hdev) > { > @@ -4287,9 +4460,60 @@ static void goya_hw_queues_unlock(struct hl_device *hdev) > spin_unlock(&goya->hw_queues_lock); > } > > +int goya_get_eeprom_data(struct hl_device *hdev, void *data, size_t max_size) > +{ > + struct goya_device *goya = hdev->asic_specific; > + struct asic_fixed_properties *prop = &hdev->asic_prop; > + struct armcp_packet pkt; > + void *eeprom_info_cpu_addr; > + dma_addr_t eeprom_info_dma_addr; > + long result; > + int rc; > + > + if (!(goya->hw_cap_initialized & HW_CAP_CPU_Q)) > + return 0; > + > + eeprom_info_cpu_addr = > + hdev->asic_funcs->cpu_accessible_dma_pool_alloc(hdev, > + max_size, &eeprom_info_dma_addr); > + if (!eeprom_info_cpu_addr) { > + dev_err(hdev->dev, > + "Failed to allocate DMA memory for EEPROM info packet\n"); > + return -ENOMEM; > + } > + > + memset(eeprom_info_cpu_addr, 0, max_size); > + > + memset(&pkt, 0, sizeof(pkt)); > + > + pkt.opcode = ARMCP_PACKET_EEPROM_DATA_GET; > + pkt.addr = eeprom_info_dma_addr + prop->host_phys_base_address; > + pkt.data_max_size = max_size; > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + GOYA_ARMCP_EEPROM_TIMEOUT, &result); > + > + if (rc) { > + dev_err(hdev->dev, > + "Failed to send armcp EEPROM pkt, error %d\n", rc); > + goto out; > + } > + > + /* result contains the actual size */ > + memcpy(data, eeprom_info_cpu_addr, min((size_t)result, max_size)); > + > +out: > + hdev->asic_funcs->cpu_accessible_dma_pool_free(hdev, max_size, > + eeprom_info_cpu_addr); > + > + return rc; > +} > + > static const struct hl_asic_funcs goya_funcs = { > .early_init = goya_early_init, > .early_fini = goya_early_fini, > + .late_init = goya_late_init, > + .late_fini = goya_late_fini, > .sw_init = goya_sw_init, > .sw_fini = goya_sw_fini, > .hw_init = goya_hw_init, > @@ -4310,10 +4534,16 @@ static const struct hl_asic_funcs goya_funcs = { > .cpu_accessible_dma_pool_alloc = goya_cpu_accessible_dma_pool_alloc, > .cpu_accessible_dma_pool_free = goya_cpu_accessible_dma_pool_free, > .update_eq_ci = goya_update_eq_ci, > + .add_device_attr = goya_add_device_attr, > + .remove_device_attr = goya_remove_device_attr, > .handle_eqe = goya_handle_eqe, > + .set_pll_profile = goya_set_pll_profile, > .get_events_stat = goya_get_events_stat, > + .enable_clock_gating = goya_init_clock_gating, > + .disable_clock_gating = goya_disable_clock_gating, > .hw_queues_lock = goya_hw_queues_lock, > .hw_queues_unlock = goya_hw_queues_unlock, > + .get_eeprom_data = goya_get_eeprom_data, > .send_cpu_message = goya_send_cpu_message > }; > > diff --git a/drivers/misc/habanalabs/goya/goyaP.h b/drivers/misc/habanalabs/goya/goyaP.h > index c6bfcb6c6905..42e8b1baef2f 100644 > --- a/drivers/misc/habanalabs/goya/goyaP.h > +++ b/drivers/misc/habanalabs/goya/goyaP.h > @@ -48,7 +48,10 @@ > > #define PLL_HIGH_DEFAULT 1575000000 /* 1.575 GHz */ > > +#define MAX_POWER_DEFAULT 200000 /* 200W */ > + > #define GOYA_ARMCP_INFO_TIMEOUT 10000000 /* 10s */ > +#define GOYA_ARMCP_EEPROM_TIMEOUT 10000000 /* 10s */ > > #define DRAM_PHYS_DEFAULT_SIZE 0x100000000ull /* 4GB */ > > @@ -119,9 +122,15 @@ enum goya_fw_component { > > struct goya_device { > int (*test_cpu_queue)(struct hl_device *hdev); > + int (*armcp_info_get)(struct hl_device *hdev); > > /* TODO: remove hw_queues_lock after moving to scheduler code */ > spinlock_t hw_queues_lock; > + > + u64 mme_clk; > + u64 tpc_clk; > + u64 ic_clk; > + > u64 ddr_bar_cur_addr; > u32 events_stat[GOYA_ASYNC_EVENT_ID_SIZE]; > u32 hw_cap_initialized; > @@ -130,6 +139,18 @@ struct goya_device { > int goya_test_cpu_queue(struct hl_device *hdev); > int goya_send_cpu_message(struct hl_device *hdev, u32 *msg, u16 len, > u32 timeout, long *result); > +long goya_get_temperature(struct hl_device *hdev, int sensor_index, u32 attr); > +long goya_get_voltage(struct hl_device *hdev, int sensor_index, u32 attr); > +long goya_get_current(struct hl_device *hdev, int sensor_index, u32 attr); > +long goya_get_fan_speed(struct hl_device *hdev, int sensor_index, u32 attr); > +long goya_get_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr); > +void goya_set_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr, > + long value); > +void goya_set_pll_profile(struct hl_device *hdev, enum hl_pll_frequency freq); > +int goya_add_device_attr(struct hl_device *hdev); > +void goya_remove_device_attr(struct hl_device *hdev); > void goya_init_security(struct hl_device *hdev); > +u64 goya_get_max_power(struct hl_device *hdev); > +void goya_set_max_power(struct hl_device *hdev, u64 value); > > #endif /* GOYAP_H_ */ > diff --git a/drivers/misc/habanalabs/goya/goya_hwmgr.c b/drivers/misc/habanalabs/goya/goya_hwmgr.c > new file mode 100644 > index 000000000000..866d1774b2e4 > --- /dev/null > +++ b/drivers/misc/habanalabs/goya/goya_hwmgr.c > @@ -0,0 +1,306 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +/* > + * Copyright 2016-2018 HabanaLabs, Ltd. > + * All Rights Reserved. > + */ > + > +#include "goyaP.h" > + > +void goya_set_pll_profile(struct hl_device *hdev, enum hl_pll_frequency freq) > +{ > + struct goya_device *goya = hdev->asic_specific; > + > + switch (freq) { > + case PLL_HIGH: > + hl_set_frequency(hdev, MME_PLL, hdev->high_pll); > + hl_set_frequency(hdev, TPC_PLL, hdev->high_pll); > + hl_set_frequency(hdev, IC_PLL, hdev->high_pll); > + break; > + case PLL_LOW: > + hl_set_frequency(hdev, MME_PLL, GOYA_PLL_FREQ_LOW); > + hl_set_frequency(hdev, TPC_PLL, GOYA_PLL_FREQ_LOW); > + hl_set_frequency(hdev, IC_PLL, GOYA_PLL_FREQ_LOW); > + break; > + case PLL_LAST: > + hl_set_frequency(hdev, MME_PLL, goya->mme_clk); > + hl_set_frequency(hdev, TPC_PLL, goya->tpc_clk); > + hl_set_frequency(hdev, IC_PLL, goya->ic_clk); > + break; > + default: > + dev_err(hdev->dev, "unknown frequency setting\n"); > + } > +} > + > +static ssize_t mme_clk_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + long value; > + > + if (hdev->disabled) > + return -ENODEV; > + > + value = hl_get_frequency(hdev, MME_PLL, false); > + > + if (value < 0) > + return value; > + > + return snprintf(buf, PAGE_SIZE, "%lu\n", value); > +} > + > +static ssize_t mme_clk_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + struct goya_device *goya = hdev->asic_specific; > + int rc; > + long value; > + > + if (hdev->disabled) { > + count = -ENODEV; > + goto fail; > + } > + > + if (hdev->pm_mng_profile == PM_AUTO) { > + count = -EPERM; > + goto fail; > + } > + > + rc = kstrtoul(buf, 0, &value); > + > + if (rc) { > + count = -EINVAL; > + goto fail; > + } > + > + hl_set_frequency(hdev, MME_PLL, value); > + goya->mme_clk = value; > + > +fail: > + return count; > +} > + > +static ssize_t tpc_clk_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + long value; > + > + if (hdev->disabled) > + return -ENODEV; > + > + value = hl_get_frequency(hdev, TPC_PLL, false); > + > + if (value < 0) > + return value; > + > + return snprintf(buf, PAGE_SIZE, "%lu\n", value); > +} > + > +static ssize_t tpc_clk_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + struct goya_device *goya = hdev->asic_specific; > + int rc; > + long value; > + > + if (hdev->disabled) { > + count = -ENODEV; > + goto fail; > + } > + > + if (hdev->pm_mng_profile == PM_AUTO) { > + count = -EPERM; > + goto fail; > + } > + > + rc = kstrtoul(buf, 0, &value); > + > + if (rc) { > + count = -EINVAL; > + goto fail; > + } > + > + hl_set_frequency(hdev, TPC_PLL, value); > + goya->tpc_clk = value; > + > +fail: > + return count; > +} > + > +static ssize_t ic_clk_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + long value; > + > + if (hdev->disabled) > + return -ENODEV; > + > + value = hl_get_frequency(hdev, IC_PLL, false); > + > + if (value < 0) > + return value; > + > + return snprintf(buf, PAGE_SIZE, "%lu\n", value); > +} > + > +static ssize_t ic_clk_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + struct goya_device *goya = hdev->asic_specific; > + int rc; > + long value; > + > + if (hdev->disabled) { > + count = -ENODEV; > + goto fail; > + } > + > + if (hdev->pm_mng_profile == PM_AUTO) { > + count = -EPERM; > + goto fail; > + } > + > + rc = kstrtoul(buf, 0, &value); > + > + if (rc) { > + count = -EINVAL; > + goto fail; > + } > + > + hl_set_frequency(hdev, IC_PLL, value); > + goya->ic_clk = value; > + > +fail: > + return count; > +} > + > +static ssize_t mme_clk_curr_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + long value; > + > + if (hdev->disabled) > + return -ENODEV; > + > + value = hl_get_frequency(hdev, MME_PLL, true); > + > + if (value < 0) > + return value; > + > + return snprintf(buf, PAGE_SIZE, "%lu\n", value); > +} > + > +static ssize_t tpc_clk_curr_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + long value; > + > + if (hdev->disabled) > + return -ENODEV; > + > + value = hl_get_frequency(hdev, TPC_PLL, true); > + > + if (value < 0) > + return value; > + > + return snprintf(buf, PAGE_SIZE, "%lu\n", value); > +} > + > +static ssize_t ic_clk_curr_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + long value; > + > + if (hdev->disabled) > + return -ENODEV; > + > + value = hl_get_frequency(hdev, IC_PLL, true); > + > + if (value < 0) > + return value; > + > + return snprintf(buf, PAGE_SIZE, "%lu\n", value); > +} > + > +static DEVICE_ATTR_RW(mme_clk); > +static DEVICE_ATTR_RW(tpc_clk); > +static DEVICE_ATTR_RW(ic_clk); > +static DEVICE_ATTR_RO(mme_clk_curr); > +static DEVICE_ATTR_RO(tpc_clk_curr); > +static DEVICE_ATTR_RO(ic_clk_curr); > + > +int goya_add_device_attr(struct hl_device *hdev) > +{ > + int rc; > + > + rc = device_create_file(hdev->dev, &dev_attr_mme_clk); > + if (rc) { > + dev_err(hdev->dev, "failed to create device file mme_clk\n"); > + return rc; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_tpc_clk); > + if (rc) { > + dev_err(hdev->dev, "failed to create device file tpc_clk\n"); > + goto remove_mme_clk; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_ic_clk); > + if (rc) { > + dev_err(hdev->dev, "failed to create device file ic_clk\n"); > + goto remove_tpc_clk; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_mme_clk_curr); > + if (rc) { > + dev_err(hdev->dev, > + "failed to create device file mme_clk_curr\n"); > + goto remove_ic_clk; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_tpc_clk_curr); > + if (rc) { > + dev_err(hdev->dev, > + "failed to create device file tpc_clk_curr\n"); > + goto remove_mme_clk_curr; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_ic_clk_curr); > + if (rc) { > + dev_err(hdev->dev, > + "failed to create device file ic_clk_curr\n"); > + goto remove_tpc_clk_curr; > + } > + > + return 0; > + > +remove_tpc_clk_curr: > + device_remove_file(hdev->dev, &dev_attr_tpc_clk_curr); > +remove_mme_clk_curr: > + device_remove_file(hdev->dev, &dev_attr_mme_clk_curr); > +remove_ic_clk: > + device_remove_file(hdev->dev, &dev_attr_ic_clk); > +remove_tpc_clk: > + device_remove_file(hdev->dev, &dev_attr_tpc_clk); > +remove_mme_clk: > + device_remove_file(hdev->dev, &dev_attr_mme_clk); > + return rc; > +} > + > +void goya_remove_device_attr(struct hl_device *hdev) > +{ > + device_remove_file(hdev->dev, &dev_attr_ic_clk_curr); > + device_remove_file(hdev->dev, &dev_attr_tpc_clk_curr); > + device_remove_file(hdev->dev, &dev_attr_mme_clk_curr); > + device_remove_file(hdev->dev, &dev_attr_ic_clk); > + device_remove_file(hdev->dev, &dev_attr_tpc_clk); > + device_remove_file(hdev->dev, &dev_attr_mme_clk); > +} > diff --git a/drivers/misc/habanalabs/habanalabs.h b/drivers/misc/habanalabs/habanalabs.h > index 899bf98eb002..49b84b3ff864 100644 > --- a/drivers/misc/habanalabs/habanalabs.h > +++ b/drivers/misc/habanalabs/habanalabs.h > @@ -25,6 +25,8 @@ > > #define HL_DEVICE_TIMEOUT_USEC 1000000 /* 1 s */ > > +#define HL_PLL_LOW_JOB_FREQ_USEC 5000000 /* 5 s */ > + > #define HL_MAX_QUEUES 128 > > struct hl_device; > @@ -60,6 +62,8 @@ struct hw_queue_properties { > /** > * struct asic_fixed_properties - ASIC specific immutable properties. > * @hw_queues_props: H/W queues properties. > + * @armcp_info: received various information from ArmCP regarding the H/W. e.g. > + * available sensors. > * @uboot_ver: F/W U-boot version. > * @preboot_ver: F/W Preboot version. > * @sram_base_address: SRAM physical start address. > @@ -72,6 +76,7 @@ struct hw_queue_properties { > * @dram_pci_bar_size: size of PCI bar towards DRAM. > * @host_phys_base_address: base physical address of host memory for > * transactions that the device generates. > + * @max_power_default: max power of the device after reset > * @va_space_host_start_address: base address of virtual memory range for > * mapping host memory. > * @va_space_host_end_address: end address of virtual memory range for > @@ -84,6 +89,10 @@ struct hw_queue_properties { > * @sram_size: total size of SRAM. > * @max_asid: maximum number of open contexts (ASIDs). > * @num_of_events: number of possible internal H/W IRQs. > + * @psoc_pci_pll_nr: PCI PLL NR value. > + * @psoc_pci_pll_nf: PCI PLL NF value. > + * @psoc_pci_pll_od: PCI PLL OD value. > + * @psoc_pci_pll_div_factor: PCI PLL DIV FACTOR 1 value. > * @completion_queues_count: number of completion queues. > * @high_pll: high PLL frequency used by the device. > * @cb_pool_cb_cnt: number of CBs in the CB pool. > @@ -92,6 +101,7 @@ struct hw_queue_properties { > */ > struct asic_fixed_properties { > struct hw_queue_properties hw_queues_props[HL_MAX_QUEUES]; > + struct armcp_info armcp_info; > char uboot_ver[VERSION_MAX_LEN]; > char preboot_ver[VERSION_MAX_LEN]; > u64 sram_base_address; > @@ -103,6 +113,7 @@ struct asic_fixed_properties { > u64 dram_size; > u64 dram_pci_bar_size; > u64 host_phys_base_address; > + u64 max_power_default; > u64 va_space_host_start_address; > u64 va_space_host_end_address; > u64 va_space_dram_start_address; > @@ -111,6 +122,10 @@ struct asic_fixed_properties { > u32 sram_size; > u32 max_asid; > u32 num_of_events; > + u32 psoc_pci_pll_nr; > + u32 psoc_pci_pll_nf; > + u32 psoc_pci_pll_od; > + u32 psoc_pci_pll_div_factor; > u32 high_pll; > u32 cb_pool_cb_cnt; > u32 cb_pool_cb_size; > @@ -296,13 +311,37 @@ enum hl_asic_type { > }; > > > +/** > + * enum hl_pm_mng_profile - power management profile. > + * @PM_AUTO: internal clock is set by KMD. > + * @PM_MANUAL: internal clock is set by the user. > + * @PM_LAST: last power management type. > + */ > +enum hl_pm_mng_profile { > + PM_AUTO = 1, > + PM_MANUAL, > + PM_LAST > +}; > > +/** > + * enum hl_pll_frequency - PLL frequency. > + * @PLL_HIGH: high frequency. > + * @PLL_LOW: low frequency. > + * @PLL_LAST: last frequency values that were configured by the user. > + */ > +enum hl_pll_frequency { > + PLL_HIGH = 1, > + PLL_LOW, > + PLL_LAST > +}; > > /** > * struct hl_asic_funcs - ASIC specific functions that are can be called from > * common code. > * @early_init: sets up early driver state (pre sw_init), doesn't configure H/W. > * @early_fini: tears down what was done in early_init. > + * @late_init: sets up late driver/hw state (post hw_init) - Optional. > + * @late_fini: tears down what was done in late_init (pre hw_fini) - Optional. > * @sw_init: sets up driver state, does not configure H/W. > * @sw_fini: tears down driver state, does not configure H/W. > * @hw_init: sets up the H/W state. > @@ -326,15 +365,23 @@ enum hl_asic_type { > * @cpu_accessible_dma_pool_alloc: allocate CPU PQ packet from DMA pool. > * @cpu_accessible_dma_pool_free: free CPU PQ packet from DMA pool. > * @update_eq_ci: update event queue CI. > + * @add_device_attr: add ASIC specific device attributes. > + * @remove_device_attr: remove ASIC specific device attributes. > * @handle_eqe: handle event queue entry (IRQ) from ArmCP. > + * @set_pll_profile: change PLL profile (manual/automatic). > * @get_events_stat: retrieve event queue entries histogram. > + * @enable_clock_gating: enable clock gating for reducing power consumption. > + * @disable_clock_gating: disable clock for accessing registers on HBW. > * @hw_queues_lock: acquire H/W queues lock. > * @hw_queues_unlock: release H/W queues lock. > + * @get_eeprom_data: retrieve EEPROM data from F/W. > * @send_cpu_message: send buffer to ArmCP. > */ > struct hl_asic_funcs { > int (*early_init)(struct hl_device *hdev); > int (*early_fini)(struct hl_device *hdev); > + int (*late_init)(struct hl_device *hdev); > + void (*late_fini)(struct hl_device *hdev); > int (*sw_init)(struct hl_device *hdev); > int (*sw_fini)(struct hl_device *hdev); > int (*hw_init)(struct hl_device *hdev); > @@ -363,11 +410,19 @@ struct hl_asic_funcs { > void (*cpu_accessible_dma_pool_free)(struct hl_device *hdev, > size_t size, void *vaddr); > void (*update_eq_ci)(struct hl_device *hdev, u32 val); > + int (*add_device_attr)(struct hl_device *hdev); > + void (*remove_device_attr)(struct hl_device *hdev); > void (*handle_eqe)(struct hl_device *hdev, > struct hl_eq_entry *eq_entry); > + void (*set_pll_profile)(struct hl_device *hdev, > + enum hl_pll_frequency freq); > void* (*get_events_stat)(struct hl_device *hdev, u32 *size); > + void (*enable_clock_gating)(struct hl_device *hdev); > + void (*disable_clock_gating)(struct hl_device *hdev); > void (*hw_queues_lock)(struct hl_device *hdev); > void (*hw_queues_unlock)(struct hl_device *hdev); > + int (*get_eeprom_data)(struct hl_device *hdev, void *data, > + size_t max_size); > int (*send_cpu_message)(struct hl_device *hdev, u32 *msg, > u16 len, u32 timeout, long *result); > }; > @@ -496,6 +551,7 @@ void hl_wreg(struct hl_device *hdev, u32 reg, u32 val); > * @rmmio: configuration area address on SRAM. > * @cdev: related char device. > * @dev: realted kernel basic device structure. > + * @work_freq: delayed work to lower device frequency if possible. > * @asic_name: ASIC specific nmae. > * @asic_type: ASIC specific type. > * @completion_queue: array of hl_cq. > @@ -517,13 +573,23 @@ void hl_wreg(struct hl_device *hdev, u32 reg, u32 val); > * @asic_prop: ASIC specific immutable properties. > * @asic_funcs: ASIC specific functions. > * @asic_specific: ASIC specific information to use only from ASIC files. > + * @hwmon_dev: H/W monitor device. > + * @pm_mng_profile: current power management profile. > + * @hl_chip_info: ASIC's sensors information. > * @cb_pool: list of preallocated CBs. > * @cb_pool_lock: protects the CB pool. > * @user_ctx: current user context executing. > + * @curr_pll_profile: current PLL profile. > * @fd_open_cnt: number of open context executing. > + * @max_power: the max power of the device, as configured by the sysadmin. This > + * value is saved so in case of hard-reset, KMD will restore this > + * value and update the F/W after the re-initialization > * @major: habanalabs KMD major. > + * @high_pll: high PLL profile frequency. > * @id: device minor. > * @disabled: is device disabled. > + * @late_init_done: is late init stage was done during initialization. > + * @hwmon_initialized: is H/W monitor sensors was initialized. > */ > struct hl_device { > struct pci_dev *pdev; > @@ -531,6 +597,7 @@ struct hl_device { > void __iomem *rmmio; > struct cdev cdev; > struct device *dev; > + struct delayed_work work_freq; > char asic_name[16]; > enum hl_asic_type asic_type; > struct hl_cq *completion_queue; > @@ -553,16 +620,25 @@ struct hl_device { > struct asic_fixed_properties asic_prop; > const struct hl_asic_funcs *asic_funcs; > void *asic_specific; > + struct device *hwmon_dev; > + enum hl_pm_mng_profile pm_mng_profile; > + struct hwmon_chip_info hl_chip_info; > > struct list_head cb_pool; > spinlock_t cb_pool_lock; > > /* TODO: The following fields should be moved for multi-context */ > struct hl_ctx *user_ctx; > + > + atomic_t curr_pll_profile; > atomic_t fd_open_cnt; > + u64 max_power; > u32 major; > + u32 high_pll; > u16 id; > u8 disabled; > + u8 late_init_done; > + u8 hwmon_initialized; > > /* Parameters for bring-up */ > u8 cpu_enable; > @@ -647,6 +723,15 @@ int hl_device_suspend(struct hl_device *hdev); > int hl_device_resume(struct hl_device *hdev); > void hl_hpriv_get(struct hl_fpriv *hpriv); > void hl_hpriv_put(struct hl_fpriv *hpriv); > +int hl_device_set_frequency(struct hl_device *hdev, enum hl_pll_frequency freq); > +int hl_build_hwmon_channel_info(struct hl_device *hdev, > + struct armcp_sensor *sensors_arr); > + > +int hl_sysfs_init(struct hl_device *hdev); > +void hl_sysfs_fini(struct hl_device *hdev); > + > +int hl_hwmon_init(struct hl_device *hdev); > +void hl_hwmon_fini(struct hl_device *hdev); > > int hl_cb_create(struct hl_device *hdev, struct hl_cb_mgr *mgr, u32 cb_size, > u64 *handle, int ctx_id); > @@ -663,6 +748,18 @@ int hl_cb_pool_fini(struct hl_device *hdev); > > void goya_set_asic_funcs(struct hl_device *hdev); > > +long hl_get_frequency(struct hl_device *hdev, u32 pll_index, bool curr); > +void hl_set_frequency(struct hl_device *hdev, u32 pll_index, u64 freq); > +long hl_get_temperature(struct hl_device *hdev, int sensor_index, u32 attr); > +long hl_get_voltage(struct hl_device *hdev, int sensor_index, u32 attr); > +long hl_get_current(struct hl_device *hdev, int sensor_index, u32 attr); > +long hl_get_fan_speed(struct hl_device *hdev, int sensor_index, u32 attr); > +long hl_get_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr); > +void hl_set_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr, > + long value); > +u64 hl_get_max_power(struct hl_device *hdev); > +void hl_set_max_power(struct hl_device *hdev, u64 value); > + > /* IOCTLs */ > long hl_ioctl(struct file *filep, unsigned int cmd, unsigned long arg); > int hl_cb_ioctl(struct hl_fpriv *hpriv, void *data); > diff --git a/drivers/misc/habanalabs/habanalabs_drv.c b/drivers/misc/habanalabs/habanalabs_drv.c > index b64f58ad0f5d..47a9ab458b43 100644 > --- a/drivers/misc/habanalabs/habanalabs_drv.c > +++ b/drivers/misc/habanalabs/habanalabs_drv.c > @@ -134,6 +134,13 @@ int hl_device_open(struct inode *inode, struct file *filp) > > hpriv->taskpid = find_get_pid(current->pid); > > + /* > + * Device is IDLE at this point so it is legal to change PLLs. There > + * is no need to check anything because if the PLL is already HIGH, the > + * set function will return without doing anything > + */ > + hl_device_set_frequency(hdev, PLL_HIGH); > + > return 0; > > out_err: > diff --git a/drivers/misc/habanalabs/hwmon.c b/drivers/misc/habanalabs/hwmon.c > new file mode 100644 > index 000000000000..6ca0decb7490 > --- /dev/null > +++ b/drivers/misc/habanalabs/hwmon.c > @@ -0,0 +1,449 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +/* > + * Copyright 2016-2018 HabanaLabs, Ltd. > + * All Rights Reserved. > + */ > + > +#include "habanalabs.h" > + > +#define SENSORS_PKT_TIMEOUT 100000 /* 100ms */ > +#define HWMON_NR_SENSOR_TYPES (hwmon_pwm + 1) > + > +int hl_build_hwmon_channel_info(struct hl_device *hdev, > + struct armcp_sensor *sensors_arr) > +{ > + u32 counts[HWMON_NR_SENSOR_TYPES] = {0}; > + u32 *sensors_by_type[HWMON_NR_SENSOR_TYPES] = {0}; > + u32 sensors_by_type_next_index[HWMON_NR_SENSOR_TYPES] = {0}; > + struct hwmon_channel_info **channels_info; > + u32 num_sensors_for_type, num_active_sensor_types = 0, > + arr_size = 0, *curr_arr; > + enum hwmon_sensor_types type; > + int rc, i, j; > + > + for (i = 0 ; i < ARMCP_MAX_SENSORS ; i++) { > + type = sensors_arr[i].type; > + > + if ((type == 0) && (sensors_arr[i].flags == 0)) > + break; > + > + if (type >= HWMON_NR_SENSOR_TYPES) { > + dev_err(hdev->dev, > + "Got wrong sensor type %d from device\n", type); > + return -EINVAL; > + } > + > + counts[type]++; > + arr_size++; > + } > + > + for (i = 0 ; i < HWMON_NR_SENSOR_TYPES ; i++) { > + if (counts[i] == 0) > + continue; > + > + num_sensors_for_type = counts[i] + 1; > + curr_arr = kcalloc(num_sensors_for_type, sizeof(*curr_arr), > + GFP_KERNEL); > + if (!curr_arr) { > + rc = -ENOMEM; > + goto sensors_type_err; > + } > + > + num_active_sensor_types++; > + sensors_by_type[i] = curr_arr; > + } > + > + for (i = 0 ; i < arr_size ; i++) { > + type = sensors_arr[i].type; > + curr_arr = sensors_by_type[type]; > + curr_arr[sensors_by_type_next_index[type]++] = > + sensors_arr[i].flags; > + } > + > + channels_info = kcalloc(num_active_sensor_types + 1, > + sizeof(*channels_info), GFP_KERNEL); > + if (!channels_info) { > + rc = -ENOMEM; > + goto channels_info_array_err; > + } > + > + for (i = 0 ; i < num_active_sensor_types ; i++) { > + channels_info[i] = kzalloc(sizeof(*channels_info[i]), > + GFP_KERNEL); > + if (!channels_info[i]) { > + rc = -ENOMEM; > + goto channel_info_err; > + } > + } > + > + for (i = 0, j = 0 ; i < HWMON_NR_SENSOR_TYPES ; i++) { > + if (!sensors_by_type[i]) > + continue; > + > + channels_info[j]->type = i; > + channels_info[j]->config = sensors_by_type[i]; > + j++; > + } > + > + hdev->hl_chip_info.info = > + (const struct hwmon_channel_info **)channels_info; > + > + return 0; > + > +channel_info_err: > + for (i = 0 ; i < num_active_sensor_types ; i++) > + if (channels_info[i]) { > + kfree(channels_info[i]->config); > + kfree(channels_info[i]); > + } > + kfree(channels_info); > +channels_info_array_err: > +sensors_type_err: > + for (i = 0 ; i < HWMON_NR_SENSOR_TYPES ; i++) > + kfree(sensors_by_type[i]); > + > + return rc; > +} > + > +static int hl_read(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long *val) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + if (hdev->disabled) > + return -ENODEV; > + > + switch (type) { > + case hwmon_temp: > + switch (attr) { > + case hwmon_temp_input: > + case hwmon_temp_max: > + case hwmon_temp_crit: > + case hwmon_temp_max_hyst: > + case hwmon_temp_crit_hyst: > + break; > + default: > + return -EINVAL; > + } > + > + *val = hl_get_temperature(hdev, channel, attr); > + break; > + case hwmon_in: > + switch (attr) { > + case hwmon_in_input: > + case hwmon_in_min: > + case hwmon_in_max: > + break; > + default: > + return -EINVAL; > + } > + > + *val = hl_get_voltage(hdev, channel, attr); > + break; > + case hwmon_curr: > + switch (attr) { > + case hwmon_curr_input: > + case hwmon_curr_min: > + case hwmon_curr_max: > + break; > + default: > + return -EINVAL; > + } > + > + *val = hl_get_current(hdev, channel, attr); > + break; > + case hwmon_fan: > + switch (attr) { > + case hwmon_fan_input: > + case hwmon_fan_min: > + case hwmon_fan_max: > + break; > + default: > + return -EINVAL; > + } > + *val = hl_get_fan_speed(hdev, channel, attr); > + break; > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + case hwmon_pwm_enable: > + break; > + default: > + return -EINVAL; > + } > + *val = hl_get_pwm_info(hdev, channel, attr); > + break; > + default: > + return -EINVAL; > + } > + return 0; > +} > + > +static int hl_write(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long val) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + if (hdev->disabled) > + return -ENODEV; > + > + switch (type) { > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + case hwmon_pwm_enable: > + break; > + default: > + return -EINVAL; > + } > + hl_set_pwm_info(hdev, channel, attr, val); > + break; > + default: > + return -EINVAL; > + } > + return 0; > +} > + > +static umode_t hl_is_visible(const void *data, enum hwmon_sensor_types type, > + u32 attr, int channel) > +{ > + switch (type) { > + case hwmon_temp: > + switch (attr) { > + case hwmon_temp_input: > + case hwmon_temp_max: > + case hwmon_temp_max_hyst: > + case hwmon_temp_crit: > + case hwmon_temp_crit_hyst: > + return 0444; > + } > + break; > + case hwmon_in: > + switch (attr) { > + case hwmon_in_input: > + case hwmon_in_min: > + case hwmon_in_max: > + return 0444; > + } > + break; > + case hwmon_curr: > + switch (attr) { > + case hwmon_curr_input: > + case hwmon_curr_min: > + case hwmon_curr_max: > + return 0444; > + } > + break; > + case hwmon_fan: > + switch (attr) { > + case hwmon_fan_input: > + case hwmon_fan_min: > + case hwmon_fan_max: > + return 0444; > + } > + break; > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + case hwmon_pwm_enable: > + return 0644; > + } > + break; > + default: > + break; > + } > + return 0; > +} > + > +static const struct hwmon_ops hl_hwmon_ops = { > + .is_visible = hl_is_visible, > + .read = hl_read, > + .write = hl_write > +}; > + > +long hl_get_temperature(struct hl_device *hdev, int sensor_index, u32 attr) > +{ > + struct armcp_packet pkt; > + long result; > + int rc; > + > + memset(&pkt, 0, sizeof(pkt)); > + > + pkt.opcode = ARMCP_PACKET_TEMPERATURE_GET; > + pkt.sensor_index = sensor_index; > + pkt.type = attr; > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + SENSORS_PKT_TIMEOUT, &result); > + > + if (rc) { > + dev_err(hdev->dev, > + "Failed to get temperature from sensor %d, error %d\n", > + sensor_index, rc); > + result = 0; > + } > + > + return result; > +} > + > +long hl_get_voltage(struct hl_device *hdev, int sensor_index, u32 attr) > +{ > + struct armcp_packet pkt; > + long result; > + int rc; > + > + memset(&pkt, 0, sizeof(pkt)); > + > + pkt.opcode = ARMCP_PACKET_VOLTAGE_GET; > + pkt.sensor_index = sensor_index; > + pkt.type = attr; > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + SENSORS_PKT_TIMEOUT, &result); > + > + if (rc) { > + dev_err(hdev->dev, > + "Failed to get voltage from sensor %d, error %d\n", > + sensor_index, rc); > + result = 0; > + } > + > + return result; > +} > + > +long hl_get_current(struct hl_device *hdev, int sensor_index, u32 attr) > +{ > + struct armcp_packet pkt; > + long result; > + int rc; > + > + memset(&pkt, 0, sizeof(pkt)); > + > + pkt.opcode = ARMCP_PACKET_CURRENT_GET; > + pkt.sensor_index = sensor_index; > + pkt.type = attr; > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + SENSORS_PKT_TIMEOUT, &result); > + > + if (rc) { > + dev_err(hdev->dev, > + "Failed to get current from sensor %d, error %d\n", > + sensor_index, rc); > + result = 0; > + } > + > + return result; > +} > + > +long hl_get_fan_speed(struct hl_device *hdev, int sensor_index, u32 attr) > +{ > + struct armcp_packet pkt; > + long result; > + int rc; > + > + memset(&pkt, 0, sizeof(pkt)); > + > + pkt.opcode = ARMCP_PACKET_FAN_SPEED_GET; > + pkt.sensor_index = sensor_index; > + pkt.type = attr; > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + SENSORS_PKT_TIMEOUT, &result); > + > + if (rc) { > + dev_err(hdev->dev, > + "Failed to get fan speed from sensor %d, error %d\n", > + sensor_index, rc); > + result = 0; > + } > + > + return result; > +} > + > +long hl_get_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr) > +{ > + struct armcp_packet pkt; > + long result; > + int rc; > + > + memset(&pkt, 0, sizeof(pkt)); > + > + pkt.opcode = ARMCP_PACKET_PWM_GET; > + pkt.sensor_index = sensor_index; > + pkt.type = attr; > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + SENSORS_PKT_TIMEOUT, &result); > + > + if (rc) { > + dev_err(hdev->dev, > + "Failed to get pwm info from sensor %d, error %d\n", > + sensor_index, rc); > + result = 0; > + } > + > + return result; > +} > + > +void hl_set_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr, > + long value) > +{ > + struct armcp_packet pkt; > + int rc; > + > + memset(&pkt, 0, sizeof(pkt)); > + > + pkt.opcode = ARMCP_PACKET_PWM_SET; > + pkt.sensor_index = sensor_index; > + pkt.type = attr; > + pkt.value = value; > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + SENSORS_PKT_TIMEOUT, NULL); > + > + if (rc) > + dev_err(hdev->dev, > + "Failed to set pwm info to sensor %d, error %d\n", > + sensor_index, rc); > +} > + > +int hl_hwmon_init(struct hl_device *hdev) > +{ > + struct device *dev = hdev->pdev ? &hdev->pdev->dev : hdev->dev; > + int rc; > + > + if ((hdev->hwmon_initialized) || !(hdev->fw_loading)) > + return 0; > + > + if (hdev->hl_chip_info.info) { > + hdev->hl_chip_info.ops = &hl_hwmon_ops; > + > + hdev->hwmon_dev = hwmon_device_register_with_info(dev, > + "habanalabs", hdev, &hdev->hl_chip_info, NULL); > + if (IS_ERR(hdev->hwmon_dev)) { > + rc = PTR_ERR(hdev->hwmon_dev); > + dev_err(hdev->dev, > + "Unable to register hwmon device: %d\n", rc); > + return rc; > + } > + > + dev_info(hdev->dev, "%s: add sensors information\n", > + dev_name(hdev->hwmon_dev)); > + > + hdev->hwmon_initialized = true; > + } else { > + dev_info(hdev->dev, "no available sensors\n"); > + } > + > + return 0; > +} > + > +void hl_hwmon_fini(struct hl_device *hdev) > +{ > + if (!hdev->hwmon_initialized) > + return; > + > + hwmon_device_unregister(hdev->hwmon_dev); > +} > diff --git a/drivers/misc/habanalabs/sysfs.c b/drivers/misc/habanalabs/sysfs.c > new file mode 100644 > index 000000000000..edd5f7159de0 > --- /dev/null > +++ b/drivers/misc/habanalabs/sysfs.c > @@ -0,0 +1,588 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +/* > + * Copyright 2016-2018 HabanaLabs, Ltd. > + * All Rights Reserved. > + */ > + > +#include "habanalabs.h" > +#include "include/habanalabs_device_if.h" > + > +#include > +#include > + > +#define SET_CLK_PKT_TIMEOUT 200000 /* 200ms */ > +#define SET_PWR_PKT_TIMEOUT 400000 /* 400ms */ > + > +long hl_get_frequency(struct hl_device *hdev, u32 pll_index, bool curr) > +{ > + struct armcp_packet pkt; > + long result; > + int rc; > + > + memset(&pkt, 0, sizeof(pkt)); > + > + if (curr) > + pkt.opcode = ARMCP_PACKET_FREQUENCY_CURR_GET; > + else > + pkt.opcode = ARMCP_PACKET_FREQUENCY_GET; > + pkt.pll_index = pll_index; > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + SET_CLK_PKT_TIMEOUT, &result); > + > + if (rc) { > + dev_err(hdev->dev, > + "Failed to get frequency of PLL %d, error %d\n", > + pll_index, rc); > + result = rc; > + } > + > + return result; > +} > + > +void hl_set_frequency(struct hl_device *hdev, u32 pll_index, u64 freq) > +{ > + struct armcp_packet pkt; > + int rc; > + > + memset(&pkt, 0, sizeof(pkt)); > + > + pkt.opcode = ARMCP_PACKET_FREQUENCY_SET; > + pkt.pll_index = pll_index; > + pkt.value = freq; > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + SET_CLK_PKT_TIMEOUT, NULL); > + > + if (rc) > + dev_err(hdev->dev, > + "Failed to set frequency to PLL %d, error %d\n", > + pll_index, rc); > +} > + > +u64 hl_get_max_power(struct hl_device *hdev) > +{ > + struct armcp_packet pkt; > + long result; > + int rc; > + > + memset(&pkt, 0, sizeof(pkt)); > + > + pkt.opcode = ARMCP_PACKET_MAX_POWER_GET; > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + SET_PWR_PKT_TIMEOUT, &result); > + > + if (rc) { > + dev_err(hdev->dev, "Failed to get max power, error %d\n", rc); > + result = rc; > + } > + > + return result; > +} > + > +void hl_set_max_power(struct hl_device *hdev, u64 value) > +{ > + struct armcp_packet pkt; > + int rc; > + > + memset(&pkt, 0, sizeof(pkt)); > + > + pkt.opcode = ARMCP_PACKET_MAX_POWER_SET; > + pkt.value = value; > + > + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), > + SET_PWR_PKT_TIMEOUT, NULL); > + > + if (rc) > + dev_err(hdev->dev, "Failed to set max power, error %d\n", rc); > +} > + > +static ssize_t pm_mng_profile_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + if (hdev->disabled) > + return -ENODEV; > + > + return snprintf(buf, PAGE_SIZE, "%s\n", > + (hdev->pm_mng_profile == PM_AUTO) ? "auto" : > + (hdev->pm_mng_profile == PM_MANUAL) ? "manual" : > + "unknown"); > +} > + > +static ssize_t pm_mng_profile_store(struct device *dev, > + struct device_attribute *attr, const char *buf, size_t count) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + if (hdev->disabled) { > + count = -ENODEV; > + goto out; > + } > + > + mutex_lock(&hdev->device_open); > + > + if (atomic_read(&hdev->fd_open_cnt) > 0) { > + dev_err(hdev->dev, > + "Can't change PM profile while user process is opened on the device\n"); > + count = -EPERM; > + goto unlock_mutex; > + } > + > + if (strncmp("auto", buf, strlen("auto")) == 0) { > + /* Make sure we are in LOW PLL when changing modes */ > + if (hdev->pm_mng_profile == PM_MANUAL) { > + atomic_set(&hdev->curr_pll_profile, PLL_HIGH); > + hl_device_set_frequency(hdev, PLL_LOW); > + hdev->pm_mng_profile = PM_AUTO; > + } > + } else if (strncmp("manual", buf, strlen("manual")) == 0) { > + /* Make sure we are in LOW PLL when changing modes */ > + if (hdev->pm_mng_profile == PM_AUTO) { > + flush_delayed_work(&hdev->work_freq); > + hdev->pm_mng_profile = PM_MANUAL; > + } > + } else { > + dev_err(hdev->dev, "value should be auto or manual\n"); > + count = -EINVAL; > + goto unlock_mutex; > + } > + > +unlock_mutex: > + mutex_unlock(&hdev->device_open); > +out: > + return count; > +} > + > +static ssize_t high_pll_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + if (hdev->disabled) > + return -ENODEV; > + > + return snprintf(buf, PAGE_SIZE, "%u\n", hdev->high_pll); > +} > + > +static ssize_t high_pll_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + long value; > + int rc; > + > + if (hdev->disabled) { > + count = -ENODEV; > + goto out; > + } > + > + rc = kstrtoul(buf, 0, &value); > + > + if (rc) { > + count = -EINVAL; > + goto out; > + } > + > + hdev->high_pll = value; > + > +out: > + return count; > +} > + > +static ssize_t uboot_ver_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + return snprintf(buf, PAGE_SIZE, "%s\n", hdev->asic_prop.uboot_ver); > +} > + > +static ssize_t armcp_kernel_ver_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + return snprintf(buf, PAGE_SIZE, "%s", > + hdev->asic_prop.armcp_info.kernel_version); > +} > + > +static ssize_t armcp_ver_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + return snprintf(buf, PAGE_SIZE, "%s\n", > + hdev->asic_prop.armcp_info.armcp_version); > +} > + > +static ssize_t cpld_ver_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + return snprintf(buf, PAGE_SIZE, "0x%08x\n", > + hdev->asic_prop.armcp_info.cpld_version); > +} > + > +static ssize_t infineon_ver_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + return snprintf(buf, PAGE_SIZE, "0x%04x\n", > + hdev->asic_prop.armcp_info.infineon_version); > +} > + > +static ssize_t fuse_ver_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + return snprintf(buf, PAGE_SIZE, "%s\n", > + hdev->asic_prop.armcp_info.fuse_version); > +} > + > +static ssize_t thermal_ver_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + return snprintf(buf, PAGE_SIZE, "%s", > + hdev->asic_prop.armcp_info.thermal_version); > +} > + > +static ssize_t preboot_btl_ver_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + return snprintf(buf, PAGE_SIZE, "%s\n", hdev->asic_prop.preboot_ver); > +} > + > +static ssize_t device_type_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + char *str; > + > + switch (hdev->asic_type) { > + case ASIC_GOYA: > + str = "GOYA"; > + break; > + default: > + dev_err(hdev->dev, "Unrecognized ASIC type %d\n", > + hdev->asic_type); > + return -EINVAL; > + } > + > + return snprintf(buf, PAGE_SIZE, "%s\n", str); > +} > + > +static ssize_t pci_addr_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + /* Use dummy, fixed address for simulator */ > + if (!hdev->pdev) > + return snprintf(buf, PAGE_SIZE, "0000:%02d:00.0\n", hdev->id); > + > + return snprintf(buf, PAGE_SIZE, "%04x:%02x:%02x.%x\n", > + pci_domain_nr(hdev->pdev->bus), > + hdev->pdev->bus->number, > + PCI_SLOT(hdev->pdev->devfn), > + PCI_FUNC(hdev->pdev->devfn)); > +} > + > +static ssize_t status_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + char *str; > + > + if (hdev->disabled) > + str = "Malfunction"; > + else > + str = "Operational"; > + > + return snprintf(buf, PAGE_SIZE, "%s\n", str); > +} > + > +static ssize_t write_open_cnt_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + > + return snprintf(buf, PAGE_SIZE, "%d\n", hdev->user_ctx ? 1 : 0); > +} > + > +static ssize_t max_power_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + long val; > + > + if (hdev->disabled) > + return -ENODEV; > + > + val = hl_get_max_power(hdev); > + > + return snprintf(buf, PAGE_SIZE, "%lu\n", val); > +} > + > +static ssize_t max_power_store(struct device *dev, > + struct device_attribute *attr, const char *buf, size_t count) > +{ > + struct hl_device *hdev = dev_get_drvdata(dev); > + unsigned long value; > + int rc; > + > + if (hdev->disabled) { > + count = -ENODEV; > + goto out; > + } > + > + rc = kstrtoul(buf, 0, &value); > + > + if (rc) { > + count = -EINVAL; > + goto out; > + } > + > + hdev->max_power = value; > + hl_set_max_power(hdev, value); > + > +out: > + return count; > +} > + > +static ssize_t eeprom_read_handler(struct file *filp, struct kobject *kobj, > + struct bin_attribute *attr, char *buf, loff_t offset, > + size_t max_size) > +{ > + struct device *dev = container_of(kobj, struct device, kobj); > + struct hl_device *hdev = dev_get_drvdata(dev); > + char *data; > + int rc; > + > + if (!max_size) > + return -EINVAL; > + > + data = kzalloc(max_size, GFP_KERNEL); > + if (!data) > + return -ENOMEM; > + > + rc = hdev->asic_funcs->get_eeprom_data(hdev, data, max_size); > + if (rc) > + goto out; > + > + memcpy(buf, data, max_size); > + > +out: > + kfree(data); > + > + return max_size; > +} > + > +static DEVICE_ATTR_RW(pm_mng_profile); > +static DEVICE_ATTR_RW(high_pll); > +static DEVICE_ATTR_RO(uboot_ver); > +static DEVICE_ATTR_RO(armcp_kernel_ver); > +static DEVICE_ATTR_RO(armcp_ver); > +static DEVICE_ATTR_RO(cpld_ver); > +static DEVICE_ATTR_RO(infineon_ver); > +static DEVICE_ATTR_RO(fuse_ver); > +static DEVICE_ATTR_RO(thermal_ver); > +static DEVICE_ATTR_RO(preboot_btl_ver); > +static DEVICE_ATTR_RO(device_type); > +static DEVICE_ATTR_RO(pci_addr); > +static DEVICE_ATTR_RO(status); > +static DEVICE_ATTR_RO(write_open_cnt); > +static DEVICE_ATTR_RW(max_power); > + > +static const struct bin_attribute bin_attr_eeprom = { > + .attr = {.name = "eeprom", .mode = (0444)}, > + .size = PAGE_SIZE, > + .read = eeprom_read_handler > +}; > + > +int hl_sysfs_init(struct hl_device *hdev) > +{ > + int rc; > + > + rc = hdev->asic_funcs->add_device_attr(hdev); > + if (rc) { > + dev_err(hdev->dev, "failed to add device attributes\n"); > + return rc; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_pm_mng_profile); > + if (rc) { > + dev_err(hdev->dev, > + "failed to create device file pm_mng_profile\n"); > + goto remove_device_attr; > + } > + > + hdev->pm_mng_profile = PM_AUTO; > + > + rc = device_create_file(hdev->dev, &dev_attr_high_pll); > + if (rc) { > + dev_err(hdev->dev, > + "failed to create device file pll_profile\n"); > + goto remove_pm_mng_profile; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_uboot_ver); > + if (rc) { > + dev_err(hdev->dev, "failed to create device file uboot_ver\n"); > + goto remove_pll_profile; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_armcp_kernel_ver); > + if (rc) { > + dev_err(hdev->dev, > + "failed to create device file armcp_kernel_ver\n"); > + goto remove_uboot_ver; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_armcp_ver); > + if (rc) { > + dev_err(hdev->dev, "failed to create device file armcp_ver\n"); > + goto remove_armcp_kernel_ver; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_cpld_ver); > + if (rc) { > + dev_err(hdev->dev, "failed to create device file cpld_ver\n"); > + goto remove_armcp_ver; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_infineon_ver); > + if (rc) { > + dev_err(hdev->dev, > + "failed to create device file infineon_ver\n"); > + goto remove_cpld_ver; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_fuse_ver); > + if (rc) { > + dev_err(hdev->dev, "failed to create device file fuse_ver\n"); > + goto remove_infineon_ver; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_thermal_ver); > + if (rc) { > + dev_err(hdev->dev, "failed to create device file thermal_ver\n"); > + goto remove_fuse_ver; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_preboot_btl_ver); > + if (rc) { > + dev_err(hdev->dev, > + "failed to create device file preboot_btl_ver\n"); > + goto remove_thermal_ver; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_device_type); > + if (rc) { > + dev_err(hdev->dev, > + "failed to create device file device_type\n"); > + goto remove_preboot_ver; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_pci_addr); > + if (rc) { > + dev_err(hdev->dev, "failed to create device file pci_addr\n"); > + goto remove_device_type; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_status); > + if (rc) { > + dev_err(hdev->dev, "failed to create device file status\n"); > + goto remove_pci_addr; > + } > + > + rc = device_create_file(hdev->dev, &dev_attr_write_open_cnt); > + if (rc) { > + dev_err(hdev->dev, > + "failed to create device file write_open_count\n"); > + goto remove_status; > + } > + > + hdev->max_power = hdev->asic_prop.max_power_default; > + > + rc = device_create_file(hdev->dev, &dev_attr_max_power); > + if (rc) { > + dev_err(hdev->dev, > + "failed to create device file max_power\n"); > + goto remove_write_open_cnt; > + } > + > + rc = sysfs_create_bin_file(&hdev->dev->kobj, &bin_attr_eeprom); > + if (rc) { > + dev_err(hdev->dev, "failed to create EEPROM sysfs entry\n"); > + goto remove_attr_max_power; > + } > + > + return 0; > + > +remove_attr_max_power: > + device_remove_file(hdev->dev, &dev_attr_max_power); > +remove_write_open_cnt: > + device_remove_file(hdev->dev, &dev_attr_write_open_cnt); > +remove_status: > + device_remove_file(hdev->dev, &dev_attr_status); > +remove_pci_addr: > + device_remove_file(hdev->dev, &dev_attr_pci_addr); > +remove_device_type: > + device_remove_file(hdev->dev, &dev_attr_device_type); > +remove_preboot_ver: > + device_remove_file(hdev->dev, &dev_attr_preboot_btl_ver); > +remove_thermal_ver: > + device_remove_file(hdev->dev, &dev_attr_thermal_ver); > +remove_fuse_ver: > + device_remove_file(hdev->dev, &dev_attr_fuse_ver); > +remove_infineon_ver: > + device_remove_file(hdev->dev, &dev_attr_infineon_ver); > +remove_cpld_ver: > + device_remove_file(hdev->dev, &dev_attr_cpld_ver); > +remove_armcp_ver: > + device_remove_file(hdev->dev, &dev_attr_armcp_ver); > +remove_armcp_kernel_ver: > + device_remove_file(hdev->dev, &dev_attr_armcp_kernel_ver); > +remove_uboot_ver: > + device_remove_file(hdev->dev, &dev_attr_uboot_ver); > +remove_pll_profile: > + device_remove_file(hdev->dev, &dev_attr_high_pll); > +remove_pm_mng_profile: > + device_remove_file(hdev->dev, &dev_attr_pm_mng_profile); > +remove_device_attr: > + hdev->asic_funcs->remove_device_attr(hdev); > + > + return rc; > +} > + > +void hl_sysfs_fini(struct hl_device *hdev) > +{ > + sysfs_remove_bin_file(&hdev->dev->kobj, &bin_attr_eeprom); > + device_remove_file(hdev->dev, &dev_attr_max_power); > + device_remove_file(hdev->dev, &dev_attr_write_open_cnt); > + device_remove_file(hdev->dev, &dev_attr_status); > + device_remove_file(hdev->dev, &dev_attr_pci_addr); > + device_remove_file(hdev->dev, &dev_attr_device_type); > + device_remove_file(hdev->dev, &dev_attr_preboot_btl_ver); > + device_remove_file(hdev->dev, &dev_attr_thermal_ver); > + device_remove_file(hdev->dev, &dev_attr_fuse_ver); > + device_remove_file(hdev->dev, &dev_attr_infineon_ver); > + device_remove_file(hdev->dev, &dev_attr_cpld_ver); > + device_remove_file(hdev->dev, &dev_attr_armcp_ver); > + device_remove_file(hdev->dev, &dev_attr_armcp_kernel_ver); > + device_remove_file(hdev->dev, &dev_attr_uboot_ver); > + device_remove_file(hdev->dev, &dev_attr_high_pll); > + device_remove_file(hdev->dev, &dev_attr_pm_mng_profile); > + hdev->asic_funcs->remove_device_attr(hdev); > +} > -- > 2.17.1 > -- Sincerely yours, Mike.