| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * HiSilicon uncore frequency scaling driver
- *
- * Copyright (c) 2025 HiSilicon Co., Ltd
- */
- #include <linux/acpi.h>
- #include <linux/bits.h>
- #include <linux/cleanup.h>
- #include <linux/devfreq.h>
- #include <linux/devfreq-governor.h>
- #include <linux/device.h>
- #include <linux/dev_printk.h>
- #include <linux/errno.h>
- #include <linux/iopoll.h>
- #include <linux/kernel.h>
- #include <linux/ktime.h>
- #include <linux/mailbox_client.h>
- #include <linux/module.h>
- #include <linux/mod_devicetable.h>
- #include <linux/mutex.h>
- #include <linux/platform_device.h>
- #include <linux/pm_opp.h>
- #include <linux/property.h>
- #include <linux/topology.h>
- #include <linux/units.h>
- #include <acpi/pcc.h>
- struct hisi_uncore_pcc_data {
- u16 status;
- u16 resv;
- u32 data;
- };
- struct hisi_uncore_pcc_shmem {
- struct acpi_pcct_shared_memory head;
- struct hisi_uncore_pcc_data pcc_data;
- };
- enum hisi_uncore_pcc_cmd_type {
- HUCF_PCC_CMD_GET_CAP = 0,
- HUCF_PCC_CMD_GET_FREQ,
- HUCF_PCC_CMD_SET_FREQ,
- HUCF_PCC_CMD_GET_MODE,
- HUCF_PCC_CMD_SET_MODE,
- HUCF_PCC_CMD_GET_PLAT_FREQ_NUM,
- HUCF_PCC_CMD_GET_PLAT_FREQ_BY_IDX,
- HUCF_PCC_CMD_MAX = 256
- };
- static int hisi_platform_gov_usage;
- static DEFINE_MUTEX(hisi_platform_gov_usage_lock);
- enum hisi_uncore_freq_mode {
- HUCF_MODE_PLATFORM = 0,
- HUCF_MODE_OS,
- HUCF_MODE_MAX
- };
- #define HUCF_CAP_PLATFORM_CTRL BIT(0)
- /**
- * struct hisi_uncore_freq - hisi uncore frequency scaling device data
- * @dev: device of this frequency scaling driver
- * @cl: mailbox client object
- * @pchan: PCC mailbox channel
- * @chan_id: PCC channel ID
- * @last_cmd_cmpl_time: timestamp of the last completed PCC command
- * @pcc_lock: PCC channel lock
- * @devfreq: devfreq data of this hisi_uncore_freq device
- * @related_cpus: CPUs whose performance is majorly affected by this
- * uncore frequency domain
- * @cap: capability flag
- */
- struct hisi_uncore_freq {
- struct device *dev;
- struct mbox_client cl;
- struct pcc_mbox_chan *pchan;
- int chan_id;
- ktime_t last_cmd_cmpl_time;
- struct mutex pcc_lock;
- struct devfreq *devfreq;
- struct cpumask related_cpus;
- u32 cap;
- };
- /* PCC channel timeout = PCC nominal latency * NUM */
- #define HUCF_PCC_POLL_TIMEOUT_NUM 1000
- #define HUCF_PCC_POLL_INTERVAL_US 5
- /* Default polling interval in ms for devfreq governors*/
- #define HUCF_DEFAULT_POLLING_MS 100
- static void hisi_uncore_free_pcc_chan(struct hisi_uncore_freq *uncore)
- {
- guard(mutex)(&uncore->pcc_lock);
- pcc_mbox_free_channel(uncore->pchan);
- uncore->pchan = NULL;
- }
- static void devm_hisi_uncore_free_pcc_chan(void *data)
- {
- hisi_uncore_free_pcc_chan(data);
- }
- static int hisi_uncore_request_pcc_chan(struct hisi_uncore_freq *uncore)
- {
- struct device *dev = uncore->dev;
- struct pcc_mbox_chan *pcc_chan;
- uncore->cl = (struct mbox_client) {
- .dev = dev,
- .tx_block = false,
- .knows_txdone = true,
- };
- pcc_chan = pcc_mbox_request_channel(&uncore->cl, uncore->chan_id);
- if (IS_ERR(pcc_chan))
- return dev_err_probe(dev, PTR_ERR(pcc_chan),
- "Failed to request PCC channel %u\n", uncore->chan_id);
- if (!pcc_chan->shmem_base_addr) {
- pcc_mbox_free_channel(pcc_chan);
- return dev_err_probe(dev, -EINVAL,
- "Invalid PCC shared memory address\n");
- }
- if (pcc_chan->shmem_size < sizeof(struct hisi_uncore_pcc_shmem)) {
- pcc_mbox_free_channel(pcc_chan);
- return dev_err_probe(dev, -EINVAL,
- "Invalid PCC shared memory size (%lluB)\n",
- pcc_chan->shmem_size);
- }
- uncore->pchan = pcc_chan;
- return devm_add_action_or_reset(uncore->dev,
- devm_hisi_uncore_free_pcc_chan, uncore);
- }
- static acpi_status hisi_uncore_pcc_reg_scan(struct acpi_resource *res,
- void *ctx)
- {
- struct acpi_resource_generic_register *reg;
- struct hisi_uncore_freq *uncore;
- if (!res || res->type != ACPI_RESOURCE_TYPE_GENERIC_REGISTER)
- return AE_OK;
- reg = &res->data.generic_reg;
- if (reg->space_id != ACPI_ADR_SPACE_PLATFORM_COMM)
- return AE_OK;
- if (!ctx)
- return AE_ERROR;
- uncore = ctx;
- /* PCC subspace ID stored in Access Size */
- uncore->chan_id = reg->access_size;
- return AE_CTRL_TERMINATE;
- }
- static int hisi_uncore_init_pcc_chan(struct hisi_uncore_freq *uncore)
- {
- acpi_handle handle = ACPI_HANDLE(uncore->dev);
- acpi_status status;
- int rc;
- uncore->chan_id = -1;
- status = acpi_walk_resources(handle, METHOD_NAME__CRS,
- hisi_uncore_pcc_reg_scan, uncore);
- if (ACPI_FAILURE(status) || uncore->chan_id < 0)
- return dev_err_probe(uncore->dev, -ENODEV,
- "Failed to get a PCC channel\n");
- rc = devm_mutex_init(uncore->dev, &uncore->pcc_lock);
- if (rc)
- return rc;
- return hisi_uncore_request_pcc_chan(uncore);
- }
- static int hisi_uncore_cmd_send(struct hisi_uncore_freq *uncore,
- u8 cmd, u32 *data)
- {
- struct hisi_uncore_pcc_shmem __iomem *addr;
- struct hisi_uncore_pcc_shmem shmem;
- struct pcc_mbox_chan *pchan;
- unsigned int mrtt;
- s64 time_delta;
- u16 status;
- int rc;
- guard(mutex)(&uncore->pcc_lock);
- pchan = uncore->pchan;
- if (!pchan)
- return -ENODEV;
- addr = (struct hisi_uncore_pcc_shmem __iomem *)pchan->shmem;
- if (!addr)
- return -EINVAL;
- /* Handle the Minimum Request Turnaround Time (MRTT) */
- mrtt = pchan->min_turnaround_time;
- time_delta = ktime_us_delta(ktime_get(), uncore->last_cmd_cmpl_time);
- if (mrtt > time_delta)
- udelay(mrtt - time_delta);
- /* Copy data */
- shmem.head = (struct acpi_pcct_shared_memory) {
- .signature = PCC_SIGNATURE | uncore->chan_id,
- .command = cmd,
- };
- shmem.pcc_data.data = *data;
- memcpy_toio(addr, &shmem, sizeof(shmem));
- /* Ring doorbell */
- rc = mbox_send_message(pchan->mchan, &cmd);
- if (rc < 0) {
- dev_err(uncore->dev, "Failed to send mbox message, %d\n", rc);
- return rc;
- }
- /* Wait status */
- rc = readw_poll_timeout(&addr->head.status, status,
- status & (PCC_STATUS_CMD_COMPLETE |
- PCC_STATUS_ERROR),
- HUCF_PCC_POLL_INTERVAL_US,
- pchan->latency * HUCF_PCC_POLL_TIMEOUT_NUM);
- if (rc) {
- dev_err(uncore->dev, "PCC channel response timeout, cmd=%u\n", cmd);
- } else if (status & PCC_STATUS_ERROR) {
- dev_err(uncore->dev, "PCC cmd error, cmd=%u\n", cmd);
- rc = -EIO;
- }
- uncore->last_cmd_cmpl_time = ktime_get();
- /* Copy data back */
- memcpy_fromio(data, &addr->pcc_data.data, sizeof(*data));
- /* Clear mailbox active req */
- mbox_client_txdone(pchan->mchan, rc);
- return rc;
- }
- static int hisi_uncore_target(struct device *dev, unsigned long *freq,
- u32 flags)
- {
- struct hisi_uncore_freq *uncore = dev_get_drvdata(dev);
- struct dev_pm_opp *opp;
- u32 data;
- if (WARN_ON(!uncore || !uncore->pchan))
- return -ENODEV;
- opp = devfreq_recommended_opp(dev, freq, flags);
- if (IS_ERR(opp)) {
- dev_err(dev, "Failed to get opp for freq %lu hz\n", *freq);
- return PTR_ERR(opp);
- }
- data = (u32)(dev_pm_opp_get_freq(opp) / HZ_PER_MHZ);
- dev_pm_opp_put(opp);
- return hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_SET_FREQ, &data);
- }
- static int hisi_uncore_get_dev_status(struct device *dev,
- struct devfreq_dev_status *stat)
- {
- /* Not used */
- return 0;
- }
- static int hisi_uncore_get_cur_freq(struct device *dev, unsigned long *freq)
- {
- struct hisi_uncore_freq *uncore = dev_get_drvdata(dev);
- u32 data = 0;
- int rc;
- if (WARN_ON(!uncore || !uncore->pchan))
- return -ENODEV;
- rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_FREQ, &data);
- /*
- * Upon a failure, 'data' remains 0 and 'freq' is set to 0 rather than a
- * random value. devfreq shouldn't use 'freq' in that case though.
- */
- *freq = data * HZ_PER_MHZ;
- return rc;
- }
- static void devm_hisi_uncore_remove_opp(void *data)
- {
- struct hisi_uncore_freq *uncore = data;
- dev_pm_opp_remove_all_dynamic(uncore->dev);
- }
- static int hisi_uncore_init_opp(struct hisi_uncore_freq *uncore)
- {
- struct device *dev = uncore->dev;
- unsigned long freq_mhz;
- u32 num, index;
- u32 data = 0;
- int rc;
- rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_PLAT_FREQ_NUM,
- &data);
- if (rc)
- return dev_err_probe(dev, rc, "Failed to get plat freq num\n");
- num = data;
- for (index = 0; index < num; index++) {
- data = index;
- rc = hisi_uncore_cmd_send(uncore,
- HUCF_PCC_CMD_GET_PLAT_FREQ_BY_IDX,
- &data);
- if (rc) {
- dev_pm_opp_remove_all_dynamic(dev);
- return dev_err_probe(dev, rc,
- "Failed to get plat freq at index %u\n", index);
- }
- freq_mhz = data;
- /* Don't care OPP voltage, take 1V as default */
- rc = dev_pm_opp_add(dev, freq_mhz * HZ_PER_MHZ, 1000000);
- if (rc) {
- dev_pm_opp_remove_all_dynamic(dev);
- return dev_err_probe(dev, rc,
- "Add OPP %lu failed\n", freq_mhz);
- }
- }
- return devm_add_action_or_reset(dev, devm_hisi_uncore_remove_opp,
- uncore);
- }
- static int hisi_platform_gov_func(struct devfreq *df, unsigned long *freq)
- {
- /*
- * Platform-controlled mode doesn't care the frequency issued from
- * devfreq, so just pick the max freq.
- */
- *freq = DEVFREQ_MAX_FREQ;
- return 0;
- }
- static int hisi_platform_gov_handler(struct devfreq *df, unsigned int event,
- void *val)
- {
- struct hisi_uncore_freq *uncore = dev_get_drvdata(df->dev.parent);
- int rc = 0;
- u32 data;
- if (WARN_ON(!uncore || !uncore->pchan))
- return -ENODEV;
- switch (event) {
- case DEVFREQ_GOV_START:
- data = HUCF_MODE_PLATFORM;
- rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_SET_MODE, &data);
- if (rc)
- dev_err(uncore->dev, "Failed to set platform mode (%d)\n", rc);
- break;
- case DEVFREQ_GOV_STOP:
- data = HUCF_MODE_OS;
- rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_SET_MODE, &data);
- if (rc)
- dev_err(uncore->dev, "Failed to set os mode (%d)\n", rc);
- break;
- default:
- break;
- }
- return rc;
- }
- /*
- * In the platform-controlled mode, the platform decides the uncore frequency
- * and ignores the frequency issued from the driver.
- * Thus, create a pseudo 'hisi_platform' governor that stops devfreq monitor
- * from working so as to save meaningless overhead.
- */
- static struct devfreq_governor hisi_platform_governor = {
- .name = "hisi_platform",
- /*
- * Set interrupt_driven to skip the devfreq monitor mechanism, though
- * this governor is not interrupt-driven.
- */
- .flags = DEVFREQ_GOV_FLAG_IRQ_DRIVEN,
- .get_target_freq = hisi_platform_gov_func,
- .event_handler = hisi_platform_gov_handler,
- };
- static void hisi_uncore_remove_platform_gov(struct hisi_uncore_freq *uncore)
- {
- u32 data = HUCF_MODE_PLATFORM;
- int rc;
- if (!(uncore->cap & HUCF_CAP_PLATFORM_CTRL))
- return;
- guard(mutex)(&hisi_platform_gov_usage_lock);
- if (--hisi_platform_gov_usage == 0) {
- rc = devfreq_remove_governor(&hisi_platform_governor);
- if (rc)
- dev_err(uncore->dev, "Failed to remove hisi_platform gov (%d)\n", rc);
- }
- /*
- * Set to the platform-controlled mode on exit if supported, so as to
- * have a certain behaviour when the driver is detached.
- */
- rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_SET_MODE, &data);
- if (rc)
- dev_err(uncore->dev, "Failed to set platform mode on exit (%d)\n", rc);
- }
- static void devm_hisi_uncore_remove_platform_gov(void *data)
- {
- hisi_uncore_remove_platform_gov(data);
- }
- static int hisi_uncore_add_platform_gov(struct hisi_uncore_freq *uncore)
- {
- if (!(uncore->cap & HUCF_CAP_PLATFORM_CTRL))
- return 0;
- guard(mutex)(&hisi_platform_gov_usage_lock);
- if (hisi_platform_gov_usage == 0) {
- int rc = devfreq_add_governor(&hisi_platform_governor);
- if (rc)
- return rc;
- }
- hisi_platform_gov_usage++;
- return devm_add_action_or_reset(uncore->dev,
- devm_hisi_uncore_remove_platform_gov,
- uncore);
- }
- /*
- * Returns:
- * 0 if success, uncore->related_cpus is set.
- * -EINVAL if property not found, or property found but without elements in it,
- * or invalid arguments received in any of the subroutine.
- * Other error codes if it goes wrong.
- */
- static int hisi_uncore_mark_related_cpus(struct hisi_uncore_freq *uncore,
- char *property, int (*get_topo_id)(int cpu),
- const struct cpumask *(*get_cpumask)(int cpu))
- {
- unsigned int i, cpu;
- size_t len;
- int rc;
- rc = device_property_count_u32(uncore->dev, property);
- if (rc < 0)
- return rc;
- if (rc == 0)
- return -EINVAL;
- len = rc;
- u32 *num __free(kfree) = kcalloc(len, sizeof(*num), GFP_KERNEL);
- if (!num)
- return -ENOMEM;
- rc = device_property_read_u32_array(uncore->dev, property, num, len);
- if (rc)
- return rc;
- for (i = 0; i < len; i++) {
- for_each_possible_cpu(cpu) {
- if (get_topo_id(cpu) != num[i])
- continue;
- cpumask_or(&uncore->related_cpus,
- &uncore->related_cpus, get_cpumask(cpu));
- break;
- }
- }
- return 0;
- }
- static int get_package_id(int cpu)
- {
- return topology_physical_package_id(cpu);
- }
- static const struct cpumask *get_package_cpumask(int cpu)
- {
- return topology_core_cpumask(cpu);
- }
- static int get_cluster_id(int cpu)
- {
- return topology_cluster_id(cpu);
- }
- static const struct cpumask *get_cluster_cpumask(int cpu)
- {
- return topology_cluster_cpumask(cpu);
- }
- static int hisi_uncore_mark_related_cpus_wrap(struct hisi_uncore_freq *uncore)
- {
- int rc;
- cpumask_clear(&uncore->related_cpus);
- rc = hisi_uncore_mark_related_cpus(uncore, "related-package",
- get_package_id,
- get_package_cpumask);
- /* Success, or firmware probably broken */
- if (!rc || rc != -EINVAL)
- return rc;
- /* Try another property name if rc == -EINVAL */
- return hisi_uncore_mark_related_cpus(uncore, "related-cluster",
- get_cluster_id,
- get_cluster_cpumask);
- }
- static ssize_t related_cpus_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- struct hisi_uncore_freq *uncore = dev_get_drvdata(dev->parent);
- return cpumap_print_to_pagebuf(true, buf, &uncore->related_cpus);
- }
- static DEVICE_ATTR_RO(related_cpus);
- static struct attribute *hisi_uncore_freq_attrs[] = {
- &dev_attr_related_cpus.attr,
- NULL
- };
- ATTRIBUTE_GROUPS(hisi_uncore_freq);
- static int hisi_uncore_devfreq_register(struct hisi_uncore_freq *uncore)
- {
- struct devfreq_dev_profile *profile;
- struct device *dev = uncore->dev;
- unsigned long freq;
- u32 data;
- int rc;
- rc = hisi_uncore_get_cur_freq(dev, &freq);
- if (rc)
- return dev_err_probe(dev, rc, "Failed to get plat init freq\n");
- profile = devm_kzalloc(dev, sizeof(*profile), GFP_KERNEL);
- if (!profile)
- return -ENOMEM;
- *profile = (struct devfreq_dev_profile) {
- .initial_freq = freq,
- .polling_ms = HUCF_DEFAULT_POLLING_MS,
- .timer = DEVFREQ_TIMER_DELAYED,
- .target = hisi_uncore_target,
- .get_dev_status = hisi_uncore_get_dev_status,
- .get_cur_freq = hisi_uncore_get_cur_freq,
- .dev_groups = hisi_uncore_freq_groups,
- };
- rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_MODE, &data);
- if (rc)
- return dev_err_probe(dev, rc, "Failed to get operate mode\n");
- if (data == HUCF_MODE_PLATFORM)
- uncore->devfreq = devm_devfreq_add_device(dev, profile,
- hisi_platform_governor.name, NULL);
- else
- uncore->devfreq = devm_devfreq_add_device(dev, profile,
- DEVFREQ_GOV_PERFORMANCE, NULL);
- if (IS_ERR(uncore->devfreq))
- return dev_err_probe(dev, PTR_ERR(uncore->devfreq),
- "Failed to add devfreq device\n");
- return 0;
- }
- static int hisi_uncore_freq_probe(struct platform_device *pdev)
- {
- struct hisi_uncore_freq *uncore;
- struct device *dev = &pdev->dev;
- u32 cap;
- int rc;
- uncore = devm_kzalloc(dev, sizeof(*uncore), GFP_KERNEL);
- if (!uncore)
- return -ENOMEM;
- uncore->dev = dev;
- platform_set_drvdata(pdev, uncore);
- rc = hisi_uncore_init_pcc_chan(uncore);
- if (rc)
- return dev_err_probe(dev, rc, "Failed to init PCC channel\n");
- rc = hisi_uncore_init_opp(uncore);
- if (rc)
- return dev_err_probe(dev, rc, "Failed to init OPP\n");
- rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_CAP, &cap);
- if (rc)
- return dev_err_probe(dev, rc, "Failed to get capability\n");
- uncore->cap = cap;
- rc = hisi_uncore_add_platform_gov(uncore);
- if (rc)
- return dev_err_probe(dev, rc, "Failed to add hisi_platform governor\n");
- rc = hisi_uncore_mark_related_cpus_wrap(uncore);
- if (rc)
- return dev_err_probe(dev, rc, "Failed to mark related cpus\n");
- rc = hisi_uncore_devfreq_register(uncore);
- if (rc)
- return dev_err_probe(dev, rc, "Failed to register devfreq\n");
- return 0;
- }
- static const struct acpi_device_id hisi_uncore_freq_acpi_match[] = {
- { "HISI04F1", },
- { }
- };
- MODULE_DEVICE_TABLE(acpi, hisi_uncore_freq_acpi_match);
- static struct platform_driver hisi_uncore_freq_drv = {
- .probe = hisi_uncore_freq_probe,
- .driver = {
- .name = "hisi_uncore_freq",
- .acpi_match_table = hisi_uncore_freq_acpi_match,
- },
- };
- module_platform_driver(hisi_uncore_freq_drv);
- MODULE_DESCRIPTION("HiSilicon uncore frequency scaling driver");
- MODULE_AUTHOR("Jie Zhan <zhanjie9@hisilicon.com>");
- MODULE_LICENSE("GPL");
|