// SPDX-License-Identifier: GPL-2.0 
 | 
  
 | 
/* 
 | 
 * Copyright 2016-2019 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"); 
 | 
    } 
 | 
} 
 | 
  
 | 
int goya_get_clk_rate(struct hl_device *hdev, u32 *cur_clk, u32 *max_clk) 
 | 
{ 
 | 
    long value; 
 | 
  
 | 
    if (hl_device_disabled_or_in_reset(hdev)) 
 | 
        return -ENODEV; 
 | 
  
 | 
    value = hl_get_frequency(hdev, MME_PLL, false); 
 | 
  
 | 
    if (value < 0) { 
 | 
        dev_err(hdev->dev, "Failed to retrieve device max clock %ld\n", 
 | 
            value); 
 | 
        return value; 
 | 
    } 
 | 
  
 | 
    *max_clk = (value / 1000 / 1000); 
 | 
  
 | 
    value = hl_get_frequency(hdev, MME_PLL, true); 
 | 
  
 | 
    if (value < 0) { 
 | 
        dev_err(hdev->dev, 
 | 
            "Failed to retrieve device current clock %ld\n", 
 | 
            value); 
 | 
        return value; 
 | 
    } 
 | 
  
 | 
    *cur_clk = (value / 1000 / 1000); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
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 (hl_device_disabled_or_in_reset(hdev)) 
 | 
        return -ENODEV; 
 | 
  
 | 
    value = hl_get_frequency(hdev, MME_PLL, false); 
 | 
  
 | 
    if (value < 0) 
 | 
        return value; 
 | 
  
 | 
    return sprintf(buf, "%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 (hl_device_disabled_or_in_reset(hdev)) { 
 | 
        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 (hl_device_disabled_or_in_reset(hdev)) 
 | 
        return -ENODEV; 
 | 
  
 | 
    value = hl_get_frequency(hdev, TPC_PLL, false); 
 | 
  
 | 
    if (value < 0) 
 | 
        return value; 
 | 
  
 | 
    return sprintf(buf, "%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 (hl_device_disabled_or_in_reset(hdev)) { 
 | 
        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 (hl_device_disabled_or_in_reset(hdev)) 
 | 
        return -ENODEV; 
 | 
  
 | 
    value = hl_get_frequency(hdev, IC_PLL, false); 
 | 
  
 | 
    if (value < 0) 
 | 
        return value; 
 | 
  
 | 
    return sprintf(buf, "%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 (hl_device_disabled_or_in_reset(hdev)) { 
 | 
        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 (hl_device_disabled_or_in_reset(hdev)) 
 | 
        return -ENODEV; 
 | 
  
 | 
    value = hl_get_frequency(hdev, MME_PLL, true); 
 | 
  
 | 
    if (value < 0) 
 | 
        return value; 
 | 
  
 | 
    return sprintf(buf, "%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 (hl_device_disabled_or_in_reset(hdev)) 
 | 
        return -ENODEV; 
 | 
  
 | 
    value = hl_get_frequency(hdev, TPC_PLL, true); 
 | 
  
 | 
    if (value < 0) 
 | 
        return value; 
 | 
  
 | 
    return sprintf(buf, "%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 (hl_device_disabled_or_in_reset(hdev)) 
 | 
        return -ENODEV; 
 | 
  
 | 
    value = hl_get_frequency(hdev, IC_PLL, true); 
 | 
  
 | 
    if (value < 0) 
 | 
        return value; 
 | 
  
 | 
    return sprintf(buf, "%lu\n", value); 
 | 
} 
 | 
  
 | 
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 (hl_device_disabled_or_in_reset(hdev)) 
 | 
        return -ENODEV; 
 | 
  
 | 
    return sprintf(buf, "%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 (hl_device_disabled_or_in_reset(hdev)) { 
 | 
        count = -ENODEV; 
 | 
        goto out; 
 | 
    } 
 | 
  
 | 
    mutex_lock(&hdev->fpriv_list_lock); 
 | 
  
 | 
    if (hdev->compute_ctx) { 
 | 
        dev_err(hdev->dev, 
 | 
            "Can't change PM profile while compute context 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) { 
 | 
            hdev->curr_pll_profile = PLL_HIGH; 
 | 
            hdev->pm_mng_profile = PM_AUTO; 
 | 
            hl_device_set_frequency(hdev, PLL_LOW); 
 | 
        } 
 | 
    } else if (strncmp("manual", buf, strlen("manual")) == 0) { 
 | 
        if (hdev->pm_mng_profile == PM_AUTO) { 
 | 
            /* Must release the lock because the work thread also 
 | 
             * takes this lock. But before we release it, set 
 | 
             * the mode to manual so nothing will change if a user 
 | 
             * suddenly opens the device 
 | 
             */ 
 | 
            hdev->pm_mng_profile = PM_MANUAL; 
 | 
  
 | 
            mutex_unlock(&hdev->fpriv_list_lock); 
 | 
  
 | 
            /* Flush the current work so we can return to the user 
 | 
             * knowing that he is the only one changing frequencies 
 | 
             */ 
 | 
            flush_delayed_work(&hdev->work_freq); 
 | 
  
 | 
            return count; 
 | 
        } 
 | 
    } else { 
 | 
        dev_err(hdev->dev, "value should be auto or manual\n"); 
 | 
        count = -EINVAL; 
 | 
    } 
 | 
  
 | 
unlock_mutex: 
 | 
    mutex_unlock(&hdev->fpriv_list_lock); 
 | 
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 (hl_device_disabled_or_in_reset(hdev)) 
 | 
        return -ENODEV; 
 | 
  
 | 
    return sprintf(buf, "%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 (hl_device_disabled_or_in_reset(hdev)) { 
 | 
        count = -ENODEV; 
 | 
        goto out; 
 | 
    } 
 | 
  
 | 
    rc = kstrtoul(buf, 0, &value); 
 | 
  
 | 
    if (rc) { 
 | 
        count = -EINVAL; 
 | 
        goto out; 
 | 
    } 
 | 
  
 | 
    hdev->high_pll = value; 
 | 
  
 | 
out: 
 | 
    return count; 
 | 
} 
 | 
  
 | 
static DEVICE_ATTR_RW(high_pll); 
 | 
static DEVICE_ATTR_RW(ic_clk); 
 | 
static DEVICE_ATTR_RO(ic_clk_curr); 
 | 
static DEVICE_ATTR_RW(mme_clk); 
 | 
static DEVICE_ATTR_RO(mme_clk_curr); 
 | 
static DEVICE_ATTR_RW(pm_mng_profile); 
 | 
static DEVICE_ATTR_RW(tpc_clk); 
 | 
static DEVICE_ATTR_RO(tpc_clk_curr); 
 | 
  
 | 
static struct attribute *goya_dev_attrs[] = { 
 | 
    &dev_attr_high_pll.attr, 
 | 
    &dev_attr_ic_clk.attr, 
 | 
    &dev_attr_ic_clk_curr.attr, 
 | 
    &dev_attr_mme_clk.attr, 
 | 
    &dev_attr_mme_clk_curr.attr, 
 | 
    &dev_attr_pm_mng_profile.attr, 
 | 
    &dev_attr_tpc_clk.attr, 
 | 
    &dev_attr_tpc_clk_curr.attr, 
 | 
    NULL, 
 | 
}; 
 | 
  
 | 
void goya_add_device_attr(struct hl_device *hdev, 
 | 
            struct attribute_group *dev_attr_grp) 
 | 
{ 
 | 
    dev_attr_grp->attrs = goya_dev_attrs; 
 | 
} 
 |