--- /dev/null
+/*
+ * Copyright (c) 2018 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * HAFM-TB(AFM with HIU TB) support
+ * Auther : PARK CHOONGHOON (choong.park@samsung.com)
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/cpumask.h>
+#include <linux/regmap.h>
+#include <linux/of_irq.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+
+#include "exynos-hiu.h"
+#include "../../cpufreq/exynos-ff.h"
+#include "../../cpufreq/exynos-acme.h"
+
+static struct exynos_hiu_data *data;
+
+static void hiu_stats_create_table(struct cpufreq_policy *policy);
+
+#define POLL_PERIOD 100
+
+/****************************************************************/
+/* HIU HELPER FUNCTION */
+/****************************************************************/
+static unsigned int hiu_get_freq_level(unsigned int freq)
+{
+ int level;
+ struct hiu_stats *stats = data->stats;
+
+ if (unlikely(!stats))
+ return 0;
+
+ for (level = 0; level < stats->last_level; level++)
+ if (stats->freq_table[level] == freq)
+ return level + data->level_offset;
+
+ return -EINVAL;
+}
+
+static unsigned int hiu_get_power_budget(unsigned int freq)
+{
+ return data->sw_pbl;
+}
+
+static void hiu_update_reg(int offset, int mask, int shift, unsigned int val)
+{
+ unsigned int reg_val;
+
+ reg_val = __raw_readl(data->base + offset);
+ reg_val &= ~(mask << shift);
+ reg_val |= val << shift;
+ __raw_writel(reg_val, data->base + offset);
+}
+
+static unsigned int hiu_read_reg(int offset, int mask, int shift)
+{
+ unsigned int reg_val;
+
+ reg_val = __raw_readl(data->base + offset);
+ return (reg_val >> shift) & mask;
+}
+
+static unsigned int hiu_get_act_dvfs(void)
+{
+ return hiu_read_reg(HIUTOPCTL1, ACTDVFS_MASK, ACTDVFS_SHIFT);
+}
+
+static void hiu_control_err_interrupts(int enable)
+{
+ if (enable)
+ hiu_update_reg(HIUTOPCTL1, ENB_ERR_INTERRUPTS_MASK, 0, ENB_ERR_INTERRUPTS_MASK);
+ else
+ hiu_update_reg(HIUTOPCTL1, ENB_ERR_INTERRUPTS_MASK, 0, 0);
+}
+
+static void hiu_control_mailbox(int enable)
+{
+ hiu_update_reg(HIUTOPCTL1, ENB_SR1INTR_MASK, ENB_SR1INTR_SHIFT, !!enable);
+ hiu_update_reg(HIUTOPCTL1, ENB_ACPM_COMM_MASK, ENB_ACPM_COMM_SHIFT, !!enable);
+}
+
+static void hiu_set_limit_dvfs(unsigned int freq)
+{
+ unsigned int level;
+
+ level = hiu_get_freq_level(freq);
+
+ hiu_update_reg(HIUTOPCTL2, LIMITDVFS_MASK, LIMITDVFS_SHIFT, level);
+}
+
+static void hiu_set_tb_dvfs(unsigned int freq)
+{
+ unsigned int level;
+
+ level = hiu_get_freq_level(freq);
+
+ hiu_update_reg(HIUTBCTL, TBDVFS_MASK, TBDVFS_SHIFT, level);
+}
+
+static void hiu_control_tb(int enable)
+{
+ hiu_update_reg(HIUTBCTL, TB_ENB_MASK, TB_ENB_SHIFT, !!enable);
+}
+
+static void hiu_control_pc(int enable)
+{
+ hiu_update_reg(HIUTBCTL, PC_DISABLE_MASK, PC_DISABLE_SHIFT, !enable);
+}
+
+static void hiu_set_boost_level_inc(void)
+{
+ unsigned int inc;
+ struct device_node *dn = data->dn;
+
+ if (!of_property_read_u32(dn, "bl1-inc", &inc))
+ hiu_update_reg(HIUTBCTL, B1_INC_MASK, B1_INC_SHIFT, inc);
+ if (!of_property_read_u32(dn, "bl2-inc", &inc))
+ hiu_update_reg(HIUTBCTL, B2_INC_MASK, B2_INC_SHIFT, inc);
+ if (!of_property_read_u32(dn, "bl3-inc", &inc))
+ hiu_update_reg(HIUTBCTL, B3_INC_MASK, B3_INC_SHIFT, inc);
+}
+
+static void hiu_set_tb_ps_cfg_each(int index, unsigned int cfg_val)
+{
+ int offset;
+
+ offset = HIUTBPSCFG_BASE + index * HIUTBPSCFG_OFFSET;
+ hiu_update_reg(offset, HIUTBPSCFG_MASK, 0, cfg_val);
+}
+
+static int hiu_set_tb_ps_cfg(void)
+{
+ int size, index;
+ unsigned int val;
+ struct hiu_cfg *table;
+ struct device_node *dn = data->dn;
+
+ size = of_property_count_u32_elems(dn, "config-table");
+ if (size < 0)
+ return size;
+
+ table = kzalloc(sizeof(struct hiu_cfg) * size / 4, GFP_KERNEL);
+ if (!table)
+ return -ENOMEM;
+
+ of_property_read_u32_array(dn, "config-table", (unsigned int *)table, size);
+
+ for (index = 0; index < size / 4; index++) {
+ val = 0;
+ val |= table[index].power_borrowed << PB_SHIFT;
+ val |= table[index].boost_level << BL_SHIFT;
+ val |= table[index].power_budget_limit << PBL_SHIFT;
+ val |= table[index].power_threshold_inc << TBPWRTHRESH_INC_SHIFT;
+
+ hiu_set_tb_ps_cfg_each(index, val);
+ }
+
+ kfree(table);
+
+ return 0;
+}
+
+static bool check_hiu_sr1_irq_pending(void)
+{
+ return !!hiu_read_reg(HIUTOPCTL1, HIU_MBOX_RESPONSE_MASK, SR1INTR_SHIFT);
+}
+
+static void clear_hiu_sr1_irq_pending(void)
+{
+ hiu_update_reg(HIUTOPCTL1, HIU_MBOX_RESPONSE_MASK, SR1INTR_SHIFT, 0);
+}
+
+static bool check_hiu_mailbox_err_pending(void)
+{
+ return !!hiu_read_reg(HIUTOPCTL1, HIU_MBOX_ERR_MASK, HIU_MBOX_ERR_SHIFT);
+}
+
+static unsigned int get_hiu_mailbox_err(void)
+{
+ return hiu_read_reg(HIUTOPCTL1, HIU_MBOX_ERR_MASK, HIU_MBOX_ERR_SHIFT);
+}
+
+static void hiu_mailbox_err_handler(void)
+{
+ unsigned int err, val;
+
+ err = get_hiu_mailbox_err();
+
+ if (err & SR1UXPERR_MASK)
+ pr_err("exynos-hiu: unexpected error occurs\n");
+
+ if (err & SR1SNERR_MASK) {
+ val = __raw_readl(data->base + HIUTOPCTL2);
+ val = (val >> SEQNUM_SHIFT) & SEQNUM_MASK;
+ pr_err("exynos-hiu: erroneous sequence num %d\n", val);
+ }
+
+ if (err & SR1TIMEOUT_MASK)
+ pr_err("exynos-hiu: TIMEOUT on SR1 write\n");
+
+ if (err & SR0RDERR_MASK)
+ pr_err("exynos-hiu: SR0 read twice or more\n");
+}
+
+static bool check_hiu_req_freq_updated(unsigned int req_freq)
+{
+ unsigned int cur_level, cur_freq;
+
+ cur_level = hiu_get_act_dvfs();
+ cur_freq = data->stats->freq_table[cur_level - data->level_offset];
+
+ /*
+ * If req_freq == boost_threshold, HIU could request turbo boost
+ * That's why in case of req_freq == boost_threshold,
+ * requested frequency update is consdered as done,
+ * if act_dvfs is larger than or equal to boost threshold
+ */
+ if (req_freq == data->boost_threshold)
+ return cur_freq >= data->boost_threshold;
+
+ return cur_freq == req_freq;
+}
+
+static bool check_hiu_normal_req_done(unsigned int req_freq)
+{
+ return check_hiu_sr1_irq_pending() &&
+ check_hiu_req_freq_updated(req_freq);
+}
+
+static bool check_hiu_need_register_restore(void)
+{
+ return !hiu_read_reg(HIUTOPCTL1, ENB_SR1INTR_MASK, ENB_SR1INTR_SHIFT);
+}
+
+static int request_dvfs_on_sr0(unsigned int req_freq)
+{
+ unsigned int val, level, budget;
+
+ /* Get dvfs level */
+ level = hiu_get_freq_level(req_freq);
+ if (level < 0)
+ return -EINVAL;
+
+ /* Get power budget */
+ budget = hiu_get_power_budget(req_freq);
+ if (budget < 0)
+ return -EINVAL;
+
+ /* write REQDVFS & REQPBL to HIU SFR */
+ val = __raw_readl(data->base + HIUTOPCTL2);
+ val &= ~(REQDVFS_MASK << REQDVFS_SHIFT | REQPBL_MASK << REQPBL_SHIFT);
+ val |= (level << REQDVFS_SHIFT | budget << REQPBL_SHIFT);
+ __raw_writel(val, data->base + HIUTOPCTL2);
+
+ return 0;
+}
+
+/****************************************************************/
+/* HIU API */
+/****************************************************************/
+static int hiu_sr1_check_loop(void *unused);
+static void __exynos_hiu_update_data(struct cpufreq_policy *policy);
+
+int exynos_hiu_set_freq(unsigned int id, unsigned int req_freq)
+{
+ bool need_update_cur_freq = true;
+
+ if (unlikely(!data))
+ return -ENODEV;
+
+ if (!data->enabled)
+ return -ENODEV;
+
+ pr_debug("exynos-hiu: update data->cur_freq:%d\n", data->cur_freq);
+
+ mutex_lock(&data->lock);
+
+ if (check_hiu_need_register_restore())
+ __exynos_hiu_update_data(NULL);
+
+ /* PM QoS could make req_freq bigger than boost_threshold */
+ if (req_freq >= data->boost_threshold){
+ /*
+ * 1) If turbo boost is already activated
+ * just update cur_freq and return.
+ * 2) If not, req_freq should be boost_threshold;
+ * DO NOT allow req_freq to be bigger than boost_threshold.
+ */
+ if (data->cur_freq >= data->boost_threshold) {
+ data->cur_freq = req_freq;
+ mutex_unlock(&data->lock);
+ return 0;
+ }
+ else {
+ data->cur_freq = req_freq;
+ req_freq = data->boost_threshold;
+ need_update_cur_freq = false;
+ }
+ }
+
+ /* Write req_freq on SR0 to request DVFS */
+ request_dvfs_on_sr0(req_freq);
+
+ if (data->operation_mode == POLLING_MODE) {
+ while (!check_hiu_normal_req_done(req_freq) &&
+ !check_hiu_mailbox_err_pending())
+ usleep_range(POLL_PERIOD, 2 * POLL_PERIOD);
+
+ if (check_hiu_mailbox_err_pending()) {
+ hiu_mailbox_err_handler();
+ BUG_ON(1);
+ }
+
+ if (need_update_cur_freq)
+ data->cur_freq = req_freq;
+ clear_hiu_sr1_irq_pending();
+
+ if (req_freq == data->boost_threshold && !data->boosting_activated) {
+ data->boosting_activated = true;
+ wake_up(&data->polling_wait);
+ }
+ }
+
+ mutex_unlock(&data->lock);
+
+ pr_debug("exynos-hiu: set REQDVFS to HIU : %ukHz\n", req_freq);
+
+ return 0;
+}
+
+int exynos_hiu_get_freq(unsigned int id)
+{
+ if (unlikely(!data))
+ return -ENODEV;
+
+ return data->cur_freq;
+}
+
+int exynos_hiu_get_max_freq(void)
+{
+ if (unlikely(!data))
+ return -1;
+
+ return data->clipped_freq;
+}
+
+/****************************************************************/
+/* HIU SR1 WRITE HANDLER */
+/****************************************************************/
+static void exynos_hiu_work(struct work_struct *work)
+{
+ unsigned int boost_freq, level;
+ struct cpufreq_policy *policy;
+ struct hiu_stats *stats = data->stats;
+ struct cpumask *mask;
+
+ level = hiu_get_act_dvfs();
+ boost_freq = stats->freq_table[level - data->level_offset];
+
+ /*
+ * Only when TB bit is set, this work callback is called.
+ * However, while this callback is waiting to start,
+ * well... turbo boost could be released.
+ * So, acting frequency coulde be lower than turbo boost threshold.
+ * This condition code is for treating that case.
+ */
+ if (boost_freq < data->boost_threshold)
+ goto done;
+
+ policy = cpufreq_cpu_get(cpumask_first(cpu_coregroup_mask(0)));
+ if (!policy) {
+ pr_debug("Failed to get CPUFreq policy in HIU work\n");
+ goto done;
+ }
+
+ __cpufreq_driver_target(policy, boost_freq,
+ CPUFREQ_RELATION_H | CPUFREQ_HW_DVFS_REQ);
+
+ cpufreq_cpu_put(policy);
+done:
+ data->hwidvfs_done = true;
+ wake_up(&data->hwidvfs_wait);
+}
+
+static irqreturn_t exynos_hiu_irq_handler(int irq, void *id)
+{
+ schedule_work_on(data->cpu, &data->work);
+
+ return IRQ_HANDLED;
+}
+
+static bool hiu_need_hw_request(void)
+{
+ unsigned int cur_level, cur_freq;
+
+ cur_level = hiu_get_act_dvfs();
+ cur_freq = data->stats->freq_table[cur_level - data->level_offset];
+
+ return cur_freq >= data->boost_threshold;
+}
+
+static int hiu_sr1_check_loop(void *unused)
+{
+wait:
+ wait_event(data->polling_wait, data->boosting_activated);
+poll:
+ mutex_lock(&data->lock);
+
+ if (data->cur_freq < data->boost_threshold)
+ goto done;
+
+ while (!check_hiu_sr1_irq_pending() &&
+ !check_hiu_mailbox_err_pending()) {
+ mutex_unlock(&data->lock);
+ usleep_range(POLL_PERIOD, 2 * POLL_PERIOD);
+ mutex_lock(&data->lock);
+
+ if (data->cur_freq < data->boost_threshold)
+ goto done;
+ }
+
+ if (check_hiu_mailbox_err_pending()) {
+ hiu_mailbox_err_handler();
+ BUG_ON(1);
+ }
+
+ if (hiu_need_hw_request()) {
+ schedule_work_on(cpumask_first(cpu_coregroup_mask(0)), &data->work);
+ clear_hiu_sr1_irq_pending();
+ mutex_unlock(&data->lock);
+
+ wait_event(data->hwidvfs_wait, data->hwidvfs_done);
+ data->hwidvfs_done = false;
+
+ goto poll;
+ }
+
+done:
+ data->boosting_activated = false;
+ mutex_unlock(&data->lock);
+ goto wait;
+
+ /* NEVER come here */
+
+ return 0;
+}
+
+/****************************************************************/
+/* EXTERNAL EVENT HANDLER */
+/****************************************************************/
+static void __exynos_hiu_update_data(struct cpufreq_policy *policy)
+{
+ /* Explicitly disable the whole HW */
+ /* ex) hiu_control_pc, tb(0), hiu_control_mailbox(0) */
+
+ /* Set dvfs limit and TB threshold */
+ hiu_set_limit_dvfs(data->clipped_freq);
+ hiu_set_tb_dvfs(data->boost_threshold);
+
+ /* Initialize TB level offset */
+ hiu_set_boost_level_inc();
+
+ /* Initialize TB power state config */
+ hiu_set_tb_ps_cfg();
+
+ /* Enable TB */
+ hiu_control_pc(data->pc_enabled);
+ hiu_control_tb(data->tb_enabled);
+
+ /* Enable error interrupts */
+ hiu_control_err_interrupts(1);
+ /* Enable mailbox communication with ACPM */
+ hiu_control_mailbox(1);
+}
+
+static int exynos_hiu_update_data(struct cpufreq_policy *policy)
+{
+ if (!cpumask_test_cpu(data->cpu, policy->cpus))
+ return 0;
+
+ data->boost_max = policy->user_policy.max;
+ data->clipped_freq = data->boost_max;
+ hiu_stats_create_table(policy);
+
+ __exynos_hiu_update_data(policy);
+
+ data->enabled = true;
+
+ pr_info("exynos-hiu: HIU data structure update complete\n");
+
+ return 0;
+}
+
+static struct exynos_cpufreq_ready_block exynos_hiu_ready = {
+ .update = exynos_hiu_update_data,
+};
+
+static bool check_hiu_need_boost_thrott(void)
+{
+ return data->cur_freq > data->boost_threshold &&
+ data->cur_freq > data->clipped_freq;
+}
+
+static int exynos_hiu_policy_callback(struct notifier_block *nb,
+ unsigned long event, void *info)
+{
+ struct cpufreq_policy *policy = info;
+
+ if (policy->cpu != data->cpu)
+ return NOTIFY_DONE;
+
+ if (policy->max == data->clipped_freq)
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case CPUFREQ_NOTIFY:
+
+ /* Note : MUST write LIMIT_DVFS to HIU SFR */
+ mutex_lock(&data->lock);
+ if (policy->max >= data->boost_threshold) {
+ data->clipped_freq = policy->max;
+ hiu_set_limit_dvfs(data->clipped_freq);
+ }
+ mutex_unlock(&data->lock);
+
+ pr_debug("exynos-hiu: update clipped freq:%d\n", data->clipped_freq);
+ if (check_hiu_need_boost_thrott())
+ atomic_inc(&boost_throttling);
+ break;
+ default:
+ ;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block exynos_hiu_policy_notifier = {
+ .notifier_call = exynos_hiu_policy_callback,
+};
+
+static int exynos_hiu_transition_callback(struct notifier_block *nb,
+ unsigned long event, void *info)
+{
+ struct cpufreq_freqs *freq = info;
+ int cpu = freq->cpu;
+
+ if (cpu != data->cpu)
+ return NOTIFY_DONE;
+
+ if (event != CPUFREQ_POSTCHANGE)
+ return NOTIFY_DONE;
+
+ if (atomic_read(&boost_throttling) &&
+ data->cur_freq <= data->clipped_freq) {
+ atomic_dec(&boost_throttling);
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block exynos_hiu_transition_notifier = {
+ .notifier_call = exynos_hiu_transition_callback,
+ .priority = INT_MIN,
+};
+
+/****************************************************************/
+/* SYSFS INTERFACE */
+/****************************************************************/
+static ssize_t
+hiu_enable_show(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", data->enabled);
+}
+
+static ssize_t
+hiu_enable_store(struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ unsigned int input;
+
+ if (kstrtos32(buf, 10, &input))
+ return -EINVAL;
+
+ return count;
+}
+
+static ssize_t
+hiu_boosted_show(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ unsigned int boosted = hiu_read_reg(HIUTBCTL, BOOSTED_MASK, BOOSTED_SHIFT);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", boosted);
+}
+
+static ssize_t
+hiu_boost_threshold_show(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", data->boost_threshold);
+}
+
+static ssize_t
+hiu_dvfs_limit_show(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ unsigned int dvfs_limit = hiu_read_reg(HIUTOPCTL2, LIMITDVFS_MASK, LIMITDVFS_SHIFT);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", dvfs_limit);
+}
+
+static ssize_t
+hiu_dvfs_limit_store(struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ unsigned int input;
+
+ if (kstrtos32(buf, 10, &input))
+ return -EINVAL;
+
+ hiu_update_reg(HIUTOPCTL2, LIMITDVFS_MASK, LIMITDVFS_SHIFT, input);
+
+ return count;
+}
+
+static DEVICE_ATTR(enabled, 0644, hiu_enable_show, hiu_enable_store);
+static DEVICE_ATTR(boosted, 0444, hiu_boosted_show, NULL);
+static DEVICE_ATTR(boost_threshold, 0444, hiu_boost_threshold_show, NULL);
+static DEVICE_ATTR(dvfs_limit, 0644, hiu_dvfs_limit_show, hiu_dvfs_limit_store);
+
+static struct attribute *exynos_hiu_attrs[] = {
+ &dev_attr_enabled.attr,
+ &dev_attr_boosted.attr,
+ &dev_attr_boost_threshold.attr,
+ &dev_attr_dvfs_limit.attr,
+ NULL,
+};
+
+static struct attribute_group exynos_hiu_attr_group = {
+ .name = "hiu",
+ .attrs = exynos_hiu_attrs,
+};
+
+/****************************************************************/
+/* INITIALIZE EXYNOS HIU DRIVER */
+/****************************************************************/
+static int hiu_dt_parsing(struct device_node *dn)
+{
+ const char *buf;
+ int ret = 0;
+
+ ret |= of_property_read_u32(dn, "operation-mode", &data->operation_mode);
+ ret |= of_property_read_u32(dn, "boot-freq", &data->cur_freq);
+ ret |= of_property_read_u32(dn, "boost-threshold", &data->boost_threshold);
+ ret |= of_property_read_u32(dn, "sw-pbl", &data->sw_pbl);
+ ret |= of_property_read_string(dn, "sibling-cpus", &buf);
+ if (ret)
+ return ret;
+
+ if (of_property_read_bool(dn, "pc-enabled"))
+ data->pc_enabled = true;
+
+ if (of_property_read_bool(dn, "tb-enabled"))
+ data->tb_enabled = true;
+
+ cpulist_parse(buf, &data->cpus);
+ cpumask_and(&data->cpus, &data->cpus, cpu_possible_mask);
+ if (cpumask_weight(&data->cpus) == 0)
+ return -ENODEV;
+
+ data->cpu = cpumask_first(&data->cpus);
+
+ return 0;
+}
+
+static void hiu_stats_create_table(struct cpufreq_policy *policy)
+{
+ unsigned int i = 0, count = 0, alloc_size;
+ struct hiu_stats *stats;
+ struct cpufreq_frequency_table *pos, *table;
+
+ table = policy->freq_table;
+ if (unlikely(!table))
+ return;
+
+ stats = kzalloc(sizeof(*stats), GFP_KERNEL);
+ if (!stats)
+ return;
+
+ cpufreq_for_each_valid_entry(pos, table)
+ count++;
+
+ alloc_size = count * (sizeof(unsigned int) + sizeof(u64));
+
+ stats->freq_table = kzalloc(alloc_size, GFP_KERNEL);
+ if (!stats->freq_table)
+ goto free_stat;
+
+ stats->time_in_state = (unsigned long long *)(stats->freq_table + count);
+
+ stats->last_level = count;
+
+ cpufreq_for_each_valid_entry(pos, table)
+ stats->freq_table[i++] = pos->frequency;
+
+ data->stats = stats;
+
+ cpufreq_for_each_valid_entry(pos, table) {
+ data->level_offset = pos->driver_data;
+ break;
+ }
+
+ return;
+free_stat:
+ kfree(stats);
+}
+
+static int exynos_hiu_probe(struct platform_device *pdev)
+{
+ struct task_struct *polling_thread;
+ struct device_node *dn = pdev->dev.of_node;
+ int ret;
+
+ data = kzalloc(sizeof(struct exynos_hiu_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ mutex_init(&data->lock);
+
+ platform_set_drvdata(pdev, data);
+
+ data->base = ioremap(GCU_BASE, SZ_4K);
+
+ ret = hiu_dt_parsing(dn);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to parse HIU data\n");
+ return -ENODEV;
+ }
+
+ data->dn = dn;
+
+ if (data->operation_mode == INTERRUPT_MODE) {
+ data->irq = irq_of_parse_and_map(dn, 0);
+ if (data->irq <= 0) {
+ dev_err(&pdev->dev, "Failed to get IRQ\n");
+ return -ENODEV;
+ }
+
+ ret = devm_request_irq(&pdev->dev, data->irq, exynos_hiu_irq_handler,
+ IRQF_TRIGGER_RISING, dev_name(&pdev->dev), data);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request IRQ handler: %d\n", data->irq);
+ return -ENODEV;
+ }
+ } else {
+ init_waitqueue_head(&data->polling_wait);
+ data->boosting_activated = false;
+
+ polling_thread = kthread_create(hiu_sr1_check_loop, NULL, "hiu_polling");
+ kthread_bind_mask(polling_thread, cpu_coregroup_mask(0));
+ wake_up_process(polling_thread);
+ }
+
+ cpufreq_register_notifier(&exynos_hiu_policy_notifier, CPUFREQ_POLICY_NOTIFIER);
+ cpufreq_register_notifier(&exynos_hiu_transition_notifier, CPUFREQ_TRANSITION_NOTIFIER);
+
+ INIT_WORK(&data->work, exynos_hiu_work);
+ init_waitqueue_head(&data->hwidvfs_wait);
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &exynos_hiu_attr_group);
+ if (ret)
+ dev_err(&pdev->dev, "Failed to create Exynos HIU attr group");
+
+ exynos_cpufreq_ready_list_add(&exynos_hiu_ready);
+
+ dev_info(&pdev->dev, "HIU Handler initialization complete\n");
+ return 0;
+}
+
+static int exynos_hiu_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ /* HACK : disable turbo boost */
+ return 0;
+}
+
+static int exynos_hiu_resume(struct platform_device *pdev)
+{
+ /* HACK : enable turbo boost */
+ return 0;
+}
+
+static const struct of_device_id of_exynos_hiu_match[] = {
+ { .compatible = "samsung,exynos-hiu", },
+ { },
+};
+
+static const struct platform_device_id exynos_hiu_ids[] = {
+ { "exynos-hiu", },
+ { }
+};
+
+static struct platform_driver exynos_hiu_driver = {
+ .driver = {
+ .name = "exynos-hiu",
+ .owner = THIS_MODULE,
+ .of_match_table = of_exynos_hiu_match,
+ },
+ .probe = exynos_hiu_probe,
+ .suspend = exynos_hiu_suspend,
+ .resume = exynos_hiu_resume,
+ .id_table = exynos_hiu_ids,
+};
+
+int __init exynos_hiu_init(void)
+{
+ return platform_driver_register(&exynos_hiu_driver);
+}
+arch_initcall(exynos_hiu_init);
+++ /dev/null
-/*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd.
- * http://www.samsung.com/
- *
- * EXYNOS - HIU(Hardware Intervention Unit) support
- * Auther : PARK CHOONGHOON (choong.park@samsung.com)
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
-
-#include <linux/cpumask.h>
-#include <linux/regmap.h>
-#include <linux/of_irq.h>
-#include <linux/of.h>
-#include <linux/slab.h>
-#include <linux/platform_device.h>
-#include <linux/io.h>
-#include <linux/kthread.h>
-#include <linux/delay.h>
-
-#include "exynos-hiu.h"
-#include "../../cpufreq/exynos-ff.h"
-#include "../../cpufreq/exynos-acme.h"
-
-static struct exynos_hiu_data *data;
-
-static void hiu_stats_create_table(struct cpufreq_policy *policy);
-
-#define POLL_PERIOD 100
-
-/****************************************************************/
-/* HIU HELPER FUNCTION */
-/****************************************************************/
-static unsigned int hiu_get_freq_level(unsigned int freq)
-{
- int level;
- struct hiu_stats *stats = data->stats;
-
- if (unlikely(!stats))
- return 0;
-
- for (level = 0; level < stats->last_level; level++)
- if (stats->freq_table[level] == freq)
- return level + data->level_offset;
-
- return -EINVAL;
-}
-
-static unsigned int hiu_get_power_budget(unsigned int freq)
-{
- return data->sw_pbl;
-}
-
-static void hiu_update_reg(int offset, int mask, int shift, unsigned int val)
-{
- unsigned int reg_val;
-
- reg_val = __raw_readl(data->base + offset);
- reg_val &= ~(mask << shift);
- reg_val |= val << shift;
- __raw_writel(reg_val, data->base + offset);
-}
-
-static unsigned int hiu_read_reg(int offset, int mask, int shift)
-{
- unsigned int reg_val;
-
- reg_val = __raw_readl(data->base + offset);
- return (reg_val >> shift) & mask;
-}
-
-static unsigned int hiu_get_act_dvfs(void)
-{
- return hiu_read_reg(HIUTOPCTL1, ACTDVFS_MASK, ACTDVFS_SHIFT);
-}
-
-static void hiu_control_err_interrupts(int enable)
-{
- if (enable)
- hiu_update_reg(HIUTOPCTL1, ENB_ERR_INTERRUPTS_MASK, 0, ENB_ERR_INTERRUPTS_MASK);
- else
- hiu_update_reg(HIUTOPCTL1, ENB_ERR_INTERRUPTS_MASK, 0, 0);
-}
-
-static void hiu_control_mailbox(int enable)
-{
- hiu_update_reg(HIUTOPCTL1, ENB_SR1INTR_MASK, ENB_SR1INTR_SHIFT, !!enable);
- hiu_update_reg(HIUTOPCTL1, ENB_ACPM_COMM_MASK, ENB_ACPM_COMM_SHIFT, !!enable);
-}
-
-static void hiu_set_limit_dvfs(unsigned int freq)
-{
- unsigned int level;
-
- level = hiu_get_freq_level(freq);
-
- hiu_update_reg(HIUTOPCTL2, LIMITDVFS_MASK, LIMITDVFS_SHIFT, level);
-}
-
-static void hiu_set_tb_dvfs(unsigned int freq)
-{
- unsigned int level;
-
- level = hiu_get_freq_level(freq);
-
- hiu_update_reg(HIUTBCTL, TBDVFS_MASK, TBDVFS_SHIFT, level);
-}
-
-static void hiu_control_tb(int enable)
-{
- hiu_update_reg(HIUTBCTL, TB_ENB_MASK, TB_ENB_SHIFT, !!enable);
-}
-
-static void hiu_control_pc(int enable)
-{
- hiu_update_reg(HIUTBCTL, PC_DISABLE_MASK, PC_DISABLE_SHIFT, !enable);
-}
-
-static void hiu_set_boost_level_inc(void)
-{
- unsigned int inc;
- struct device_node *dn = data->dn;
-
- if (!of_property_read_u32(dn, "bl1-inc", &inc))
- hiu_update_reg(HIUTBCTL, B1_INC_MASK, B1_INC_SHIFT, inc);
- if (!of_property_read_u32(dn, "bl2-inc", &inc))
- hiu_update_reg(HIUTBCTL, B2_INC_MASK, B2_INC_SHIFT, inc);
- if (!of_property_read_u32(dn, "bl3-inc", &inc))
- hiu_update_reg(HIUTBCTL, B3_INC_MASK, B3_INC_SHIFT, inc);
-}
-
-static void hiu_set_tb_ps_cfg_each(int index, unsigned int cfg_val)
-{
- int offset;
-
- offset = HIUTBPSCFG_BASE + index * HIUTBPSCFG_OFFSET;
- hiu_update_reg(offset, HIUTBPSCFG_MASK, 0, cfg_val);
-}
-
-static int hiu_set_tb_ps_cfg(void)
-{
- int size, index;
- unsigned int val;
- struct hiu_cfg *table;
- struct device_node *dn = data->dn;
-
- size = of_property_count_u32_elems(dn, "config-table");
- if (size < 0)
- return size;
-
- table = kzalloc(sizeof(struct hiu_cfg) * size / 4, GFP_KERNEL);
- if (!table)
- return -ENOMEM;
-
- of_property_read_u32_array(dn, "config-table", (unsigned int *)table, size);
-
- for (index = 0; index < size / 4; index++) {
- val = 0;
- val |= table[index].power_borrowed << PB_SHIFT;
- val |= table[index].boost_level << BL_SHIFT;
- val |= table[index].power_budget_limit << PBL_SHIFT;
- val |= table[index].power_threshold_inc << TBPWRTHRESH_INC_SHIFT;
-
- hiu_set_tb_ps_cfg_each(index, val);
- }
-
- kfree(table);
-
- return 0;
-}
-
-static bool check_hiu_sr1_irq_pending(void)
-{
- return !!hiu_read_reg(HIUTOPCTL1, HIU_MBOX_RESPONSE_MASK, SR1INTR_SHIFT);
-}
-
-static void clear_hiu_sr1_irq_pending(void)
-{
- hiu_update_reg(HIUTOPCTL1, HIU_MBOX_RESPONSE_MASK, SR1INTR_SHIFT, 0);
-}
-
-static bool check_hiu_mailbox_err_pending(void)
-{
- return !!hiu_read_reg(HIUTOPCTL1, HIU_MBOX_ERR_MASK, HIU_MBOX_ERR_SHIFT);
-}
-
-static unsigned int get_hiu_mailbox_err(void)
-{
- return hiu_read_reg(HIUTOPCTL1, HIU_MBOX_ERR_MASK, HIU_MBOX_ERR_SHIFT);
-}
-
-static void hiu_mailbox_err_handler(void)
-{
- unsigned int err, val;
-
- err = get_hiu_mailbox_err();
-
- if (err & SR1UXPERR_MASK)
- pr_err("exynos-hiu: unexpected error occurs\n");
-
- if (err & SR1SNERR_MASK) {
- val = __raw_readl(data->base + HIUTOPCTL2);
- val = (val >> SEQNUM_SHIFT) & SEQNUM_MASK;
- pr_err("exynos-hiu: erroneous sequence num %d\n", val);
- }
-
- if (err & SR1TIMEOUT_MASK)
- pr_err("exynos-hiu: TIMEOUT on SR1 write\n");
-
- if (err & SR0RDERR_MASK)
- pr_err("exynos-hiu: SR0 read twice or more\n");
-}
-
-static bool check_hiu_req_freq_updated(unsigned int req_freq)
-{
- unsigned int cur_level, cur_freq;
-
- cur_level = hiu_get_act_dvfs();
- cur_freq = data->stats->freq_table[cur_level - data->level_offset];
-
- /*
- * If req_freq == boost_threshold, HIU could request turbo boost
- * That's why in case of req_freq == boost_threshold,
- * requested frequency update is consdered as done,
- * if act_dvfs is larger than or equal to boost threshold
- */
- if (req_freq == data->boost_threshold)
- return cur_freq >= data->boost_threshold;
-
- return cur_freq == req_freq;
-}
-
-static bool check_hiu_normal_req_done(unsigned int req_freq)
-{
- return check_hiu_sr1_irq_pending() &&
- check_hiu_req_freq_updated(req_freq);
-}
-
-static bool check_hiu_need_register_restore(void)
-{
- return !hiu_read_reg(HIUTOPCTL1, ENB_SR1INTR_MASK, ENB_SR1INTR_SHIFT);
-}
-
-static int request_dvfs_on_sr0(unsigned int req_freq)
-{
- unsigned int val, level, budget;
-
- /* Get dvfs level */
- level = hiu_get_freq_level(req_freq);
- if (level < 0)
- return -EINVAL;
-
- /* Get power budget */
- budget = hiu_get_power_budget(req_freq);
- if (budget < 0)
- return -EINVAL;
-
- /* write REQDVFS & REQPBL to HIU SFR */
- val = __raw_readl(data->base + HIUTOPCTL2);
- val &= ~(REQDVFS_MASK << REQDVFS_SHIFT | REQPBL_MASK << REQPBL_SHIFT);
- val |= (level << REQDVFS_SHIFT | budget << REQPBL_SHIFT);
- __raw_writel(val, data->base + HIUTOPCTL2);
-
- return 0;
-}
-
-/****************************************************************/
-/* HIU API */
-/****************************************************************/
-static int hiu_sr1_check_loop(void *unused);
-static void __exynos_hiu_update_data(struct cpufreq_policy *policy);
-
-int exynos_hiu_set_freq(unsigned int id, unsigned int req_freq)
-{
- bool need_update_cur_freq = true;
-
- if (unlikely(!data))
- return -ENODEV;
-
- if (!data->enabled)
- return -ENODEV;
-
- pr_debug("exynos-hiu: update data->cur_freq:%d\n", data->cur_freq);
-
- mutex_lock(&data->lock);
-
- if (check_hiu_need_register_restore())
- __exynos_hiu_update_data(NULL);
-
- /* PM QoS could make req_freq bigger than boost_threshold */
- if (req_freq >= data->boost_threshold){
- /*
- * 1) If turbo boost is already activated
- * just update cur_freq and return.
- * 2) If not, req_freq should be boost_threshold;
- * DO NOT allow req_freq to be bigger than boost_threshold.
- */
- if (data->cur_freq >= data->boost_threshold) {
- data->cur_freq = req_freq;
- mutex_unlock(&data->lock);
- return 0;
- }
- else {
- data->cur_freq = req_freq;
- req_freq = data->boost_threshold;
- need_update_cur_freq = false;
- }
- }
-
- /* Write req_freq on SR0 to request DVFS */
- request_dvfs_on_sr0(req_freq);
-
- if (data->operation_mode == POLLING_MODE) {
- while (!check_hiu_normal_req_done(req_freq) &&
- !check_hiu_mailbox_err_pending())
- usleep_range(POLL_PERIOD, 2 * POLL_PERIOD);
-
- if (check_hiu_mailbox_err_pending()) {
- hiu_mailbox_err_handler();
- BUG_ON(1);
- }
-
- if (need_update_cur_freq)
- data->cur_freq = req_freq;
- clear_hiu_sr1_irq_pending();
-
- if (req_freq == data->boost_threshold && !data->boosting_activated) {
- data->boosting_activated = true;
- wake_up(&data->polling_wait);
- }
- }
-
- mutex_unlock(&data->lock);
-
- pr_debug("exynos-hiu: set REQDVFS to HIU : %ukHz\n", req_freq);
-
- return 0;
-}
-
-int exynos_hiu_get_freq(unsigned int id)
-{
- if (unlikely(!data))
- return -ENODEV;
-
- return data->cur_freq;
-}
-
-int exynos_hiu_get_max_freq(void)
-{
- if (unlikely(!data))
- return -1;
-
- return data->clipped_freq;
-}
-
-/****************************************************************/
-/* HIU SR1 WRITE HANDLER */
-/****************************************************************/
-static void exynos_hiu_work(struct work_struct *work)
-{
- int cpu;
- unsigned int boost_freq, level;
- struct cpufreq_policy *policy;
- struct hiu_stats *stats = data->stats;
- struct cpumask mask;
-
- cpumask_and(&mask, &data->cpus, cpu_online_mask);
- if (cpumask_empty(&mask)) {
- pr_debug("exynos-hiu: all cores in big cluster off\n");
- goto done;
- }
-
- /* Test current cpu is in data->cpus */
- cpu = smp_processor_id();
- if (!cpumask_test_cpu(cpu, &mask)) {
- pr_debug("exynos-hiu: work task is moved to cpu%d\n", cpu);
- goto done;
- }
-
- level = hiu_get_act_dvfs();
- boost_freq = stats->freq_table[level - data->level_offset];
-
- /*
- * Only when TB bit is set, this work callback is called.
- * However, while this callback is waiting to start,
- * well... turbo boost could be released.
- * So, acting frequency coulde be lower than turbo boost threshold.
- * This condition code is for treating that case.
- */
- if (boost_freq < data->boost_threshold)
- goto done;
-
- policy = cpufreq_cpu_get(cpumask_first(&mask));
- if (!policy) {
- pr_debug("Failed to get CPUFreq policy in HIU work\n");
- goto done;
- }
-
- __cpufreq_driver_target(policy, boost_freq,
- CPUFREQ_RELATION_H | CPUFREQ_HW_DVFS_REQ);
-
- cpufreq_cpu_put(policy);
-done:
- data->hwidvfs_done = true;
- wake_up(&data->hwidvfs_wait);
-}
-
-static irqreturn_t exynos_hiu_irq_handler(int irq, void *id)
-{
- schedule_work_on(data->cpu, &data->work);
-
- return IRQ_HANDLED;
-}
-
-static bool hiu_need_hw_request(void)
-{
- unsigned int cur_level, cur_freq;
-
- cur_level = hiu_get_act_dvfs();
- cur_freq = data->stats->freq_table[cur_level - data->level_offset];
-
- return cur_freq >= data->boost_threshold;
-}
-
-static int hiu_sr1_check_loop(void *unused)
-{
-wait:
- wait_event(data->polling_wait, data->boosting_activated);
-poll:
- mutex_lock(&data->lock);
-
- if (data->cur_freq < data->boost_threshold)
- goto done;
-
- while (!check_hiu_sr1_irq_pending() &&
- !check_hiu_mailbox_err_pending()) {
- mutex_unlock(&data->lock);
- usleep_range(POLL_PERIOD, 2 * POLL_PERIOD);
- mutex_lock(&data->lock);
-
- if (data->cur_freq < data->boost_threshold)
- goto done;
- }
-
- if (check_hiu_mailbox_err_pending()) {
- hiu_mailbox_err_handler();
- BUG_ON(1);
- }
-
- if (hiu_need_hw_request()) {
- schedule_work_on(data->cpu, &data->work);
- clear_hiu_sr1_irq_pending();
- mutex_unlock(&data->lock);
-
- wait_event(data->hwidvfs_wait, data->hwidvfs_done);
- data->hwidvfs_done = false;
-
- goto poll;
- }
-
-done:
- data->boosting_activated = false;
- mutex_unlock(&data->lock);
- goto wait;
-
- /* NEVER come here */
-
- return 0;
-}
-
-/****************************************************************/
-/* EXTERNAL EVENT HANDLER */
-/****************************************************************/
-static void __exynos_hiu_update_data(struct cpufreq_policy *policy)
-{
- /* Explicitly disable the whole HW */
- /* ex) hiu_control_pc, tb(0), hiu_control_mailbox(0) */
-
- /* Set dvfs limit and TB threshold */
- hiu_set_limit_dvfs(data->clipped_freq);
- hiu_set_tb_dvfs(data->boost_threshold);
-
- /* Initialize TB level offset */
- hiu_set_boost_level_inc();
-
- /* Initialize TB power state config */
- hiu_set_tb_ps_cfg();
-
- /* Enable TB */
- hiu_control_pc(data->pc_enabled);
- hiu_control_tb(data->tb_enabled);
-
- /* Enable error interrupts */
- hiu_control_err_interrupts(1);
- /* Enable mailbox communication with ACPM */
- hiu_control_mailbox(1);
-}
-
-static int exynos_hiu_update_data(struct cpufreq_policy *policy)
-{
- if (!cpumask_test_cpu(data->cpu, policy->cpus))
- return 0;
-
- data->boost_max = policy->user_policy.max;
- data->clipped_freq = data->boost_max;
- hiu_stats_create_table(policy);
-
- __exynos_hiu_update_data(policy);
-
- data->enabled = true;
-
- pr_info("exynos-hiu: HIU data structure update complete\n");
-
- return 0;
-}
-
-static struct exynos_cpufreq_ready_block exynos_hiu_ready = {
- .update = exynos_hiu_update_data,
-};
-
-static bool check_hiu_need_boost_thrott(void)
-{
- return data->cur_freq > data->boost_threshold &&
- data->cur_freq > data->clipped_freq;
-}
-
-static int exynos_hiu_policy_callback(struct notifier_block *nb,
- unsigned long event, void *info)
-{
- struct cpufreq_policy *policy = info;
-
- if (policy->cpu != data->cpu)
- return NOTIFY_DONE;
-
- if (policy->max == data->clipped_freq)
- return NOTIFY_DONE;
-
- switch (event) {
- case CPUFREQ_NOTIFY:
-
- /* Note : MUST write LIMIT_DVFS to HIU SFR */
- mutex_lock(&data->lock);
- if (policy->max >= data->boost_threshold) {
- data->clipped_freq = policy->max;
- hiu_set_limit_dvfs(data->clipped_freq);
- }
- mutex_unlock(&data->lock);
-
- pr_debug("exynos-hiu: update clipped freq:%d\n", data->clipped_freq);
- if (check_hiu_need_boost_thrott())
- atomic_inc(&boost_throttling);
- break;
- default:
- ;
- }
-
- return NOTIFY_OK;
-}
-
-static struct notifier_block exynos_hiu_policy_notifier = {
- .notifier_call = exynos_hiu_policy_callback,
-};
-
-static int exynos_hiu_transition_callback(struct notifier_block *nb,
- unsigned long event, void *info)
-{
- struct cpufreq_freqs *freq = info;
- int cpu = freq->cpu;
-
- if (cpu != data->cpu)
- return NOTIFY_DONE;
-
- if (event != CPUFREQ_POSTCHANGE)
- return NOTIFY_DONE;
-
- if (atomic_read(&boost_throttling) &&
- data->cur_freq <= data->clipped_freq) {
- atomic_dec(&boost_throttling);
- }
-
- return NOTIFY_OK;
-}
-
-static struct notifier_block exynos_hiu_transition_notifier = {
- .notifier_call = exynos_hiu_transition_callback,
- .priority = INT_MIN,
-};
-
-/****************************************************************/
-/* SYSFS INTERFACE */
-/****************************************************************/
-static ssize_t
-hiu_enable_show(struct device *dev, struct device_attribute *devattr,
- char *buf)
-{
- return snprintf(buf, PAGE_SIZE, "%d\n", data->enabled);
-}
-
-static ssize_t
-hiu_enable_store(struct device *dev, struct device_attribute *devattr,
- const char *buf, size_t count)
-{
- unsigned int input;
-
- if (kstrtos32(buf, 10, &input))
- return -EINVAL;
-
- return count;
-}
-
-static DEVICE_ATTR(enabled, 0644, hiu_enable_show, hiu_enable_store);
-
-static struct attribute *exynos_hiu_attrs[] = {
- &dev_attr_enabled.attr,
- NULL,
-};
-
-static struct attribute_group exynos_hiu_attr_group = {
- .name = "hiu",
- .attrs = exynos_hiu_attrs,
-};
-
-/****************************************************************/
-/* INITIALIZE EXYNOS HIU DRIVER */
-/****************************************************************/
-static int hiu_dt_parsing(struct device_node *dn)
-{
- const char *buf;
- int ret = 0;
-
- ret |= of_property_read_u32(dn, "operation-mode", &data->operation_mode);
- ret |= of_property_read_u32(dn, "boot-freq", &data->cur_freq);
- ret |= of_property_read_u32(dn, "boost-threshold", &data->boost_threshold);
- ret |= of_property_read_u32(dn, "sw-pbl", &data->sw_pbl);
- ret |= of_property_read_string(dn, "sibling-cpus", &buf);
- if (ret)
- return ret;
-
- if (of_property_read_bool(dn, "pc-enabled"))
- data->pc_enabled = true;
-
- if (of_property_read_bool(dn, "tb-enabled"))
- data->tb_enabled = true;
-
- cpulist_parse(buf, &data->cpus);
- cpumask_and(&data->cpus, &data->cpus, cpu_possible_mask);
- if (cpumask_weight(&data->cpus) == 0)
- return -ENODEV;
-
- data->cpu = cpumask_first(&data->cpus);
-
- return 0;
-}
-
-static void hiu_stats_create_table(struct cpufreq_policy *policy)
-{
- unsigned int i = 0, count = 0, alloc_size;
- struct hiu_stats *stats;
- struct cpufreq_frequency_table *pos, *table;
-
- table = policy->freq_table;
- if (unlikely(!table))
- return;
-
- stats = kzalloc(sizeof(*stats), GFP_KERNEL);
- if (!stats)
- return;
-
- cpufreq_for_each_valid_entry(pos, table)
- count++;
-
- alloc_size = count * (sizeof(unsigned int) + sizeof(u64));
-
- stats->freq_table = kzalloc(alloc_size, GFP_KERNEL);
- if (!stats->freq_table)
- goto free_stat;
-
- stats->time_in_state = (unsigned long long *)(stats->freq_table + count);
-
- stats->last_level = count;
-
- cpufreq_for_each_valid_entry(pos, table)
- stats->freq_table[i++] = pos->frequency;
-
- data->stats = stats;
-
- cpufreq_for_each_valid_entry(pos, table) {
- data->level_offset = pos->driver_data;
- break;
- }
-
- return;
-free_stat:
- kfree(stats);
-}
-
-static int exynos_hiu_probe(struct platform_device *pdev)
-{
- struct task_struct *polling_thread;
- struct device_node *dn = pdev->dev.of_node;
- int ret;
-
- data = kzalloc(sizeof(struct exynos_hiu_data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
-
- mutex_init(&data->lock);
-
- platform_set_drvdata(pdev, data);
-
- data->base = ioremap(GCU_BASE, SZ_4K);
-
- ret = hiu_dt_parsing(dn);
- if (ret) {
- dev_err(&pdev->dev, "Failed to parse HIU data\n");
- return -ENODEV;
- }
-
- data->dn = dn;
-
- if (data->operation_mode == INTERRUPT_MODE) {
- data->irq = irq_of_parse_and_map(dn, 0);
- if (data->irq <= 0) {
- dev_err(&pdev->dev, "Failed to get IRQ\n");
- return -ENODEV;
- }
-
- ret = devm_request_irq(&pdev->dev, data->irq, exynos_hiu_irq_handler,
- IRQF_TRIGGER_RISING, dev_name(&pdev->dev), data);
- if (ret) {
- dev_err(&pdev->dev, "Failed to request IRQ handler: %d\n", data->irq);
- return -ENODEV;
- }
- } else {
- init_waitqueue_head(&data->polling_wait);
- data->boosting_activated = false;
-
- polling_thread = kthread_create(hiu_sr1_check_loop, NULL, "hiu_polling");
- kthread_bind_mask(polling_thread, cpu_coregroup_mask(0));
- wake_up_process(polling_thread);
- }
-
- cpufreq_register_notifier(&exynos_hiu_policy_notifier, CPUFREQ_POLICY_NOTIFIER);
- cpufreq_register_notifier(&exynos_hiu_transition_notifier, CPUFREQ_TRANSITION_NOTIFIER);
-
- INIT_WORK(&data->work, exynos_hiu_work);
- init_waitqueue_head(&data->hwidvfs_wait);
-
- ret = sysfs_create_group(&pdev->dev.kobj, &exynos_hiu_attr_group);
- if (ret)
- dev_err(&pdev->dev, "Failed to create Exynos HIU attr group");
-
- exynos_cpufreq_ready_list_add(&exynos_hiu_ready);
-
- dev_info(&pdev->dev, "HIU Handler initialization complete\n");
- return 0;
-}
-
-static int exynos_hiu_suspend(struct platform_device *pdev, pm_message_t state)
-{
- /* HACK : disable turbo boost */
- return 0;
-}
-
-static int exynos_hiu_resume(struct platform_device *pdev)
-{
- /* HACK : enable turbo boost */
- return 0;
-}
-
-static const struct of_device_id of_exynos_hiu_match[] = {
- { .compatible = "samsung,exynos-hiu", },
- { },
-};
-
-static const struct platform_device_id exynos_hiu_ids[] = {
- { "exynos-hiu", },
- { }
-};
-
-static struct platform_driver exynos_hiu_driver = {
- .driver = {
- .name = "exynos-hiu",
- .owner = THIS_MODULE,
- .of_match_table = of_exynos_hiu_match,
- },
- .probe = exynos_hiu_probe,
- .suspend = exynos_hiu_suspend,
- .resume = exynos_hiu_resume,
- .id_table = exynos_hiu_ids,
-};
-
-int __init exynos_hiu_init(void)
-{
- return platform_driver_register(&exynos_hiu_driver);
-}
-arch_initcall(exynos_hiu_init);