Re: [PATCH 09/15] habanalabs: add sysfs and hwmon support

Oded Gabbay
2019-01-28T11:25:21Z

On Fri, Jan 25, 2019 at 9:54 AM Mike Rapoport <rppt@linux.ibm.com> wrote:
>
> 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 <oded.gabbay@gmail.com>
> > ---
> > .../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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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<n>/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?
nope, same explanation as previous patches
>
> > + 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
>
ditto for the answer :)

> > + 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.
yes, if we allocate a pkt from it then we just memcpy over the entire
pkt and no need to memset it.
>
> > +
> > + 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 <linux/hwmon-sysfs.h>
> > +#include <linux/hwmon.h>
> > +
> > +#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.
>