/* * drivers/devfreq/ddrfreq/sunxi_dramfreq.c * * Copyright(c) 2013-2015 Allwinnertech Co., Ltd. * * Author: Pan Nan * * SUNXI dram frequency dynamic scaling driver * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum { DEBUG_NONE = 0, DEBUG_FREQ = 1, }; static int debug_mask = DEBUG_FREQ; module_param(debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); #define DRAMFREQ_DBG(mask, format, args...) \ do { if (mask & debug_mask) printk("[dramfreq] "format, ##args); } while (0) #define DRAMFREQ_ERR(format, args...) \ printk(KERN_ERR "[dramfreq] ERR:"format, ##args) #define SUNXI_DRAMFREQ_MDFS_RETRIES (3) static unsigned long sunxi_dramfreq_table[LV_END] = { 672000, /*LV_0*/ 480000, /*LV_1*/ 336000, /*LV_2*/ 240000, /*LV_3*/ 168000, /*LV_4*/ }; /** * @key_masters_int_idx * the key_masters_int_idx: * first dimension that is master what need to interrupt. * second dimension that is property what we care. * eg: * master master_access_bit in_which_list_num */ #if defined(CONFIG_ARCH_SUN8IW10) static int key_masters_int_idx[MASTER_MAX][3] = { #ifdef CONFIG_EINK_PANEL_USED { MASTER_EINK0, 5, 0 }, { MASTER_EDMA, 6, 0 }, { MASTER_EINK1, 7, 0 }, #else { MASTER_DE, 9, 0 }, #endif { MASTER_CSI, 3, 0 }, }; #elif defined(CONFIG_ARCH_SUN8IW11) static int key_masters_int_idx[MASTER_MAX][3] = { { MASTER_GPU, 1, 0 }, { MASTER_CSI, 5, 0 }, { MASTER_DE, 13, 0 }, }; #elif defined(CONFIG_ARCH_SUN50IW2) static int key_masters_int_idx[MASTER_MAX][3] = { { MASTER_GPU, 1, 0 }, { MASTER_CSI, 5, 0 }, { MASTER_DE, 10, 0 }, }; #elif defined(CONFIG_ARCH_SUN50IW3) static int key_masters_int_idx[MASTER_MAX][3] = { { MASTER_GPU, 1, 0 }, { MASTER_VE, 4, 0 }, { MASTER_DE, 16, 0 }, }; #elif defined(CONFIG_ARCH_SUN50IW6) /* the sun50iw6 doesn't support dramfreq, but need to offer cur_freq attr */ static int key_masters_int_idx[MASTER_MAX][3] = { { MASTER_NULL, -1, -1 }, }; #endif struct sunxi_dramfreq *dramfreq; struct task_struct *sunxi_dramfreq_task; #ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC #define MDFS_LEAST_TIME_RETRIES (5) #define MDFS_LEAST_TIME_US (2000) struct dramfreq_vb_time_ops { int (*get_vb_time) (void); int (*get_next_vb_time) (void); int (*is_in_vb) (void); } dramfreq_vbtime_ops; int dramfreq_set_vb_time_ops(struct dramfreq_vb_time_ops *ops) { dramfreq_vbtime_ops.get_vb_time = ops->get_vb_time; dramfreq_vbtime_ops.get_next_vb_time = ops->get_next_vb_time; dramfreq_vbtime_ops.is_in_vb = ops->is_in_vb; return 0; } EXPORT_SYMBOL(dramfreq_set_vb_time_ops); #endif #ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ static DEFINE_MUTEX(busfreq_lock); struct busfreq_table { char *name; struct clk *bus_clk; unsigned long normal_freq; unsigned long idle_freq; }; static struct busfreq_table busfreq_tbl[] = { { .name = "ahb1", .normal_freq = 200000000, .idle_freq = 50000000 }, }; static struct clk *clk_ahb1; static int sunxi_dramfreq_busfreq_target(const char *name, struct clk *clk, unsigned long target_freq) { unsigned long cur_freq; mutex_lock(&busfreq_lock); if (clk_prepare_enable(clk)) { DRAMFREQ_ERR("try to enable %s output failed!\n", name); goto err; } cur_freq = clk_get_rate(clk); if (cur_freq == target_freq) { mutex_unlock(&busfreq_lock); return 0; } if (clk_set_rate(clk, target_freq)) { DRAMFREQ_ERR("try to set %s rate to %lu failed!\n", name, target_freq); goto err; } cur_freq = clk_get_rate(clk); if (cur_freq != target_freq) { DRAMFREQ_ERR("%s: %lu != %lu\n", name, cur_freq, target_freq); goto err; } mutex_unlock(&busfreq_lock); return 0; err: mutex_unlock(&busfreq_lock); return -1; } #endif /* CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ */ #ifdef CONFIG_SMP struct cpumask dramfreq_ipi_mask; static volatile bool cpu_pause[NR_CPUS]; static volatile bool pause_flag; static bool mdfs_is_paused(void) { smp_rmb(); return pause_flag; } static void mdfs_set_paused(bool pause) { pause_flag = pause; smp_wmb(); } static bool mdfs_cpu_is_paused(unsigned int cpu) { smp_rmb(); return cpu_pause[cpu]; } static void mdfs_cpu_set_paused(unsigned int cpu, bool pause) { cpu_pause[cpu] = pause; smp_wmb(); } static void mdfs_cpu_pause(void *info) { unsigned int cpu = raw_smp_processor_id(); dsb(sy); isb(); mdfs_cpu_set_paused(cpu, true); while (mdfs_is_paused()) ; mdfs_cpu_set_paused(cpu, false); } static void mdfs_cpu_wait(void *info) {} #endif /* CONFIG_SMP */ #ifdef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY int dramfreq_master_access(enum DRAM_KEY_MASTER master, bool access) { if (dramfreq == NULL) return -EINVAL; dramfreq->key_masters[master] = access ? 1 : 0; if (!dramfreq->pause) wake_up_process(sunxi_dramfreq_task); return 0; } EXPORT_SYMBOL(dramfreq_master_access); #endif static int mdfs_cfs(unsigned int freq_jump, struct sunxi_dramfreq *dramfreq, unsigned int freq) { volatile unsigned int reg_val; unsigned int i, div, n = 4; unsigned int rank_num, trefi, trfc, ctrl_freq; unsigned int timeout = 1000000000; struct dram_para_t *para = &dramfreq->dram_para; /* wait for process finished */ do { reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR) & 0x1; } while (reg_val && timeout--); if (timeout == 0) return -EBUSY; /*for CFS only support LPDDR */ ctrl_freq = freq >> 1; if ((para->dram_type == 3) || (para->dram_type == 2)) { trefi = ((7800*ctrl_freq)/1000 + ((((7800*ctrl_freq)%1000) != 0) ? 1:0))/32; trfc = (350*ctrl_freq)/1000 + ((((350*ctrl_freq)%1000) != 0) ? 1:0); } else { trefi = ((3900*ctrl_freq)/1000 + ((((3900*ctrl_freq)%1000) != 0) ? 1:0))/32; trfc = (210*ctrl_freq)/1000 + ((((210*ctrl_freq)%1000) != 0) ? 1:0); } /* set dual buffer for timing change and power save */ reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR); reg_val |= (0x1U<<15); writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR); /* change refresh timing */ reg_val = readl(dramfreq->dramctl_base + RFSHTMG); reg_val &= ~((0xfff<<0)|(0xfff<<16)); reg_val |= ((trfc<<0)|(trefi<<16)); writel(reg_val, dramfreq->dramctl_base + RFSHTMG); /* change ODT status for power save */ if (!((para->dram_tpr13>>12) & 0x1)) { if (freq > 400) { if ((para->dram_odt_en & 0x1)) { for (i = 0; i < n; i++) { /*byte 0/byte 1*/ reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i)); reg_val &= ~(0x3U<<4); reg_val |= (0x0<<4);/*ODT dynamic*/ writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i)); } rank_num = readl(dramfreq->dramcom_base + MC_WORK_MODE) & 0x1; if (rank_num) writel(0x00000303, dramfreq->dramctl_base + ODTMAP); else writel(0x00000201, dramfreq->dramctl_base + ODTMAP); } } else { if ((para->dram_odt_en & 0x1)) { for (i = 0; i < n; i++) { /*byte 0/byte 1*/ reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i)); reg_val &= ~(0x3U<<4); reg_val |= (0x2<<4); writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i)); } writel(0x0, dramfreq->dramctl_base + ODTMAP); } } } /* change pll-ddr N value */ div = freq / 12; reg_val = readl(dramfreq->ccu_base + CCM_PLL_DDR1_REG); reg_val &= ~(0x7f<<8); reg_val |= (((div-1)<<8)); writel(reg_val, dramfreq->ccu_base + CCM_PLL_DDR1_REG); /* setting MDFS configuration */ reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR); reg_val &= ~(0x1 << 2); reg_val |= ((freq_jump & 0x1) << 2); /*1: increase 0:decrease*/ reg_val |= (0x1 << 1); /*CFS mode*/ reg_val |= (0x1 << 11); /*vtf enable*/ reg_val |= (0x1 << 4); /*bypass*/ writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR); reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR); reg_val |= (0x1U<<0); writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR); return 0; } static int mdfs_dfs(unsigned int freq_jump, struct sunxi_dramfreq *dramfreq, unsigned int freq) { volatile unsigned int reg_val; unsigned int trefi, trfc, ctrl_freq; unsigned int i, n = 4; unsigned int div, source; unsigned int vtf_status; unsigned int timeout = 1000000; struct dram_para_t *para = &dramfreq->dram_para; #if !defined(CONFIG_ARCH_SUN50IW3) unsigned int rank_num; #endif /* calculate source and divider */ if (para->dram_tpr9 != 0) { if (((para->dram_clk % freq) == 0) && ((para->dram_tpr9 % freq) == 0)) { if ((para->dram_clk / freq) > (para->dram_tpr9 / freq)) { source = !((para->dram_tpr13 >> 6) & 0x1); div = para->dram_tpr9 / freq ; } else { source = (para->dram_tpr13 >> 6) & 0x1; div = para->dram_clk / freq ; } } else if ((para->dram_clk % freq) == 0) { source = (para->dram_tpr13 >> 6) & 0x1; div = para->dram_clk / freq ; } else if ((para->dram_tpr9 % freq) == 0) { source = !((para->dram_tpr13 >> 6) & 0x1); div = para->dram_tpr9 / freq ; } else { DRAMFREQ_ERR("unsupported freq!\n"); return 1; } } else { #if defined(CONFIG_ARCH_SUN50IW1) source = 1; #elif defined(CONFIG_ARCH_SUN8IW10) || defined(CONFIG_ARCH_SUN8IW11) || \ defined(CONFIG_ARCH_SUN50IW2) || defined(CONFIG_ARCH_SUN50IW3) source = (para->dram_tpr13 >> 6) & 0x1; #endif div = para->dram_clk / freq ; } ctrl_freq = freq >> 1; if ((para->dram_type == 3) || (para->dram_type == 2)) { trefi = ((7800*ctrl_freq)/1000 + ((((7800*ctrl_freq)%1000) != 0) ? 1:0))/32; trfc = (350*ctrl_freq)/1000 + ((((350*ctrl_freq)%1000) != 0) ? 1:0); } else { trefi = ((3900*ctrl_freq)/1000 + ((((3900*ctrl_freq)%1000) != 0) ? 1:0))/32; trfc = (210*ctrl_freq)/1000 + ((((210*ctrl_freq)%1000) != 0) ? 1:0); } /* turn off vtf */ reg_val = readl(dramfreq->dramctl_base + VTFCR); #if defined(CONFIG_ARCH_SUN50IW1) vtf_status = (reg_val & (0x1<<8)); if (vtf_status) { reg_val &= ~(0x1<<8); writel(reg_val, dramfreq->dramctl_base + VTFCR); } #elif defined(CONFIG_ARCH_SUN8IW10) || defined(CONFIG_ARCH_SUN8IW11) || \ defined(CONFIG_ARCH_SUN50IW2) || defined(CONFIG_ARCH_SUN50IW3) vtf_status = (reg_val & (0x3<<8)); if (vtf_status) { reg_val &= ~(0x3<<8); writel(reg_val, dramfreq->dramctl_base + VTFCR); } #endif /* set dual buffer for timing change and power save */ reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR); /* VTC dual buffer can not be used */ reg_val |= (0x1U << 15); writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR); /* change refresh timing */ reg_val = readl(dramfreq->dramctl_base + RFSHTMG); reg_val &= ~((0xfff<<0)|(0xfff<<16)); reg_val |= ((trfc<<0)|(trefi<<16)); writel(reg_val, dramfreq->dramctl_base + RFSHTMG); #if defined(CONFIG_ARCH_SUN50IW3) reg_val = ((trefi << 15) & 0x0fff0000); writel(reg_val, dramfreq->dramctl_base + RFSHCTL1); #endif /* change ODT status for power save */ if (!((para->dram_tpr13 >> 12) & 0x1)) { #if !defined(CONFIG_ARCH_SUN50IW3) if (freq > 400) { #else if (freq > 600) { #endif if ((para->dram_odt_en & 0x1)) { for (i = 0; i < n; i++) { /* byte 0/byte 1 */ reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i)); reg_val &= ~(0x3U << 4); reg_val |= (0x0 << 4);/*ODT dynamic*/ writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i)); } #if !defined(CONFIG_ARCH_SUN50IW3) rank_num = readl(dramfreq->dramcom_base + MC_WORK_MODE) & 0x1; if (rank_num) writel(0x00000303, dramfreq->dramctl_base + ODTMAP); else writel(0x00000201, dramfreq->dramctl_base + ODTMAP); #endif } } else { if ((para->dram_odt_en & 0x1)) { for (i = 0; i < n; i++) { /* byte 0/byte 1*/ reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i)); reg_val &= ~(0x3U<<4); reg_val |= (0x2<<4); writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i)); } #if !defined(CONFIG_ARCH_SUN50IW3) writel(0x0, dramfreq->dramctl_base + ODTMAP); #endif } } } /* set the DRAM_CFG_REG divider in CCMU */ reg_val = readl(dramfreq->ccu_base + CCM_DRAM_CFG_REG); reg_val &= ~(0xf<<0); reg_val |= ((div-1)<<0); writel(reg_val, dramfreq->ccu_base + CCM_DRAM_CFG_REG); /* set clock source in CCMU */ reg_val &= ~(0x3<ccu_base + CCM_DRAM_CFG_REG); /* set MDFS register */ reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR); reg_val |= (0x1 << 4); /*bypass*/ #if !defined(CONFIG_ARCH_SUN50IW3) reg_val |= (0x1 << 13); /*pad hold*/ #endif reg_val &= ~(0x1U << 1); /*DFS mode*/ writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR); reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR); reg_val |= (0x1U << 0); /* start mdfs */ writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR); /* wait for process finished */ while (timeout--) { reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR) & 0x1; if (!reg_val) break; } if (timeout == 0) return -EBUSY; /* turn off dual buffer */ reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR); reg_val &= ~(0x1U<<15); writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR); /* turn on vtf */ if (vtf_status) { #if defined(CONFIG_ARCH_SUN50IW1) reg_val = readl(dramfreq->dramctl_base + VTFCR); vtf_status |= (0x1<<8); writel(reg_val, dramfreq->dramctl_base + VTFCR); #elif defined(CONFIG_ARCH_SUN8IW10) || defined(CONFIG_ARCH_SUN8IW11) || \ defined(CONFIG_ARCH_SUN50IW2) || defined(CONFIG_ARCH_SUN50IW3) reg_val = readl(dramfreq->dramctl_base + VTFCR); vtf_status |= (0x3<<8); writel(reg_val, dramfreq->dramctl_base + VTFCR); #endif } return 0; } static int sunxi_dramfreq_get_cur_freq(struct device *dev, unsigned long *freq) { unsigned long pll_ddr_rate; unsigned int dram_div_m; #ifdef CONFIG_DEBUG_FS ktime_t calltime = ktime_get(); #endif if ((readl(dramfreq->ccu_base + CCM_DRAM_CFG_REG) >> CCM_DRAM_CFG_REG_PLL0_1_BIT) & 0x1) pll_ddr_rate = clk_get_rate(dramfreq->clk_pll_ddr1) / 1000; else pll_ddr_rate = clk_get_rate(dramfreq->clk_pll_ddr0) / 1000; dram_div_m = (readl(dramfreq->ccu_base + CCM_DRAM_CFG_REG) & 0x3) + 1; *freq = pll_ddr_rate / 2 / dram_div_m; #ifdef CONFIG_DEBUG_FS dramfreq->dramfreq_get_us = ktime_to_us(ktime_sub(ktime_get(), calltime)); #endif return 0; } unsigned long dramfreq_get(void) { unsigned long cur_freq; if (dramfreq == NULL) return 0; if (dramfreq->ccu_base == NULL || dramfreq->clk_pll_ddr0 == NULL #if !defined(CONFIG_ARCH_SUN50IW6) || dramfreq->clk_pll_ddr1 == NULL #endif ) return 0; sunxi_dramfreq_get_cur_freq(NULL, &cur_freq); return cur_freq; } EXPORT_SYMBOL_GPL(dramfreq_get); static int sunxi_dramfreq_get_max_freq(void) { return sunxi_dramfreq_table[0]; } static int sunxi_dramfreq_get_min_freq(void) { unsigned int i, min = UINT_MAX; for (i = 0; i < LV_END; i++) { if (sunxi_dramfreq_table[i] == 0) continue; if (sunxi_dramfreq_table[i] < min) min = sunxi_dramfreq_table[i]; } return min; } static int sunxi_dramfreq_get_valid_freq(unsigned long freq) { unsigned long *valid_freq = &sunxi_dramfreq_table[0]; int i = 0; while (*(valid_freq+1) >= freq) { i++; if (i >= LV_END) break; valid_freq++; } return *valid_freq; } static int sunxi_dramfreq_set_rate(unsigned int jump, unsigned int freq_target, struct sunxi_dramfreq *dramfreq, unsigned int *masters_access) { int i, ret = 0; s64 mdfs_time_us = 0; ktime_t calltime; #ifdef CONFIG_SMP unsigned int cpu, cur_cpu, timeout = 0; #endif #ifdef CONFIG_SMP cpumask_clear(&dramfreq_ipi_mask); cpumask_copy(&dramfreq_ipi_mask, cpu_online_mask); cur_cpu = raw_smp_processor_id(); cpumask_clear_cpu(cur_cpu, &dramfreq_ipi_mask); local_bh_disable(); mdfs_set_paused(true); preempt_disable(); smp_call_function_many(&dramfreq_ipi_mask, mdfs_cpu_pause, NULL, 0); preempt_enable(); dsb(sy); isb(); for_each_online_cpu(cpu) { if (cpu == cur_cpu) continue; while (!mdfs_cpu_is_paused(cpu) && timeout < 100) { udelay(100); timeout++; } if (timeout >= 100) { DRAMFREQ_ERR("Pause cpu%d time out!\n", cpu); ret = -EAGAIN; goto out; } } #endif if (jump == FREQ_DOWN) { for (i = 0; i < MASTER_MAX; i++) { if (masters_access[i] < dramfreq->key_masters[i]) { ret = -EINVAL; goto out; } } } local_irq_disable(); if (dramfreq->mode == DFS_MODE) { #ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC while (!dramfreq_vbtime_ops.is_in_vb()) ; #endif } calltime = ktime_get(); flush_tlb_all(); isb(); if (dramfreq->mode == CFS_MODE) ret = mdfs_cfs(jump, dramfreq, freq_target / 1000); else if (dramfreq->mode == DFS_MODE) ret = mdfs_dfs(jump, dramfreq, freq_target / 1000); mdfs_time_us = ktime_to_us(ktime_sub(ktime_get(), calltime)); local_irq_enable(); #ifdef CONFIG_SMP DRAMFREQ_DBG(DEBUG_FREQ, "[cpu%d] elapsed:%lldus\n", cur_cpu, mdfs_time_us); #else DRAMFREQ_DBG(DEBUG_FREQ, "[cpu0] elapsed:%lldus\n", mdfs_time_us); #endif #ifdef CONFIG_DEBUG_FS dramfreq->dramfreq_set_us = mdfs_time_us; #endif out: #ifdef CONFIG_SMP mdfs_set_paused(false); local_bh_enable(); preempt_disable(); smp_call_function_many(&dramfreq_ipi_mask, mdfs_cpu_wait, NULL, true); preempt_enable(); #endif return ret; } static int sunxi_dramfreq_target(struct device *dev, unsigned long *freq, u32 flags) { struct platform_device *pdev = container_of(dev, struct platform_device, dev); struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev); int i, ret = 0, retries = 0, jump = 0; unsigned int masters_access[MASTER_MAX]; #ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC int next_vbtime_us = 0, next_vb_retries = 0; #endif unsigned long cur_freq, valid_freq; #ifdef CONFIG_CPU_FREQ struct cpufreq_policy *policy; #endif #if defined(CONFIG_ARCH_SUN50IW6) /* the sun50iw6 doesn't support dramfreq, but need to offer cur_freq attr */ return 0; #endif for (i = 0; i < MASTER_MAX; i++) masters_access[i] = dramfreq->key_masters[i]; if ((dramfreq == NULL) || (dramfreq->devfreq == NULL)) return -EINVAL; mutex_lock(&dramfreq->lock); get_online_cpus(); valid_freq = sunxi_dramfreq_get_valid_freq(*freq); if (valid_freq == dramfreq->devfreq->previous_freq) { if (*freq != valid_freq) *freq = valid_freq; goto unlock; } jump = (valid_freq > dramfreq->devfreq->previous_freq) ? FREQ_UP : FREQ_DOWN; DRAMFREQ_DBG(DEBUG_FREQ, "%luKHz->%luKHz start\n", dramfreq->devfreq->previous_freq, valid_freq); #ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ if ((dramfreq->devfreq->previous_freq == dramfreq->devfreq->min_freq) && (jump == FREQ_UP)) { for (i = ARRAY_SIZE(busfreq_tbl) - 1; i >= 0; i--) { sunxi_dramfreq_busfreq_target(busfreq_tbl[i].name, busfreq_tbl[i].bus_clk, busfreq_tbl[i].normal_freq); } DRAMFREQ_DBG(DEBUG_FREQ, "AHB1:%lu\n", clk_get_rate(clk_ahb1) / 1000000); } #endif if (dramfreq->mode == DFS_MODE) { #ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC if (!(dramfreq_vbtime_ops.get_next_vb_time && dramfreq_vbtime_ops.is_in_vb)) { DRAMFREQ_ERR("dramfreq_vbtime_ops is not initialized!\n"); ret = -EINVAL; goto unlock; } do { next_vbtime_us = dramfreq_vbtime_ops.get_next_vb_time(); if (next_vbtime_us < MDFS_LEAST_TIME_US) { next_vb_retries++; msleep(1); } else { break; } } while (next_vb_retries < MDFS_LEAST_TIME_RETRIES); if (next_vb_retries >= MDFS_LEAST_TIME_RETRIES) { DRAMFREQ_ERR("Retrying next vb time failed, next time!\n"); ret = -EINVAL; goto unlock; } else { usleep_range(next_vbtime_us - MDFS_LEAST_TIME_US, next_vbtime_us - MDFS_LEAST_TIME_US); } #endif } #ifdef CONFIG_CPU_FREQ policy = cpufreq_cpu_get(0); if (policy && (policy->cur < policy->max)) { cpufreq_driver_target(policy, policy->max, CPUFREQ_RELATION_H); cpufreq_cpu_put(policy); } #endif do { ret = sunxi_dramfreq_set_rate(jump, valid_freq, dramfreq, masters_access); if (ret == -EAGAIN) retries++; else break; } while (retries < SUNXI_DRAMFREQ_MDFS_RETRIES); if (retries >= SUNXI_DRAMFREQ_MDFS_RETRIES) { DRAMFREQ_ERR("Retrying mdfs failed, next time!\n"); ret = -EINVAL; goto unlock; } if (ret == -EBUSY) { DRAMFREQ_ERR("mdfs timeout\n"); goto unlock; } else if (ret == -EINVAL) { DRAMFREQ_DBG(DEBUG_FREQ, "no need for freq down\n"); goto unlock; } sunxi_dramfreq_get_cur_freq(dev, &cur_freq); if (cur_freq != valid_freq) { DRAMFREQ_ERR("current freq is %lu != %lu\n", cur_freq, valid_freq); goto unlock; } if (*freq != cur_freq) *freq = cur_freq; DRAMFREQ_DBG(DEBUG_FREQ, "%luKHz->%luKHz ok\n", dramfreq->devfreq->previous_freq, cur_freq); #ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ if ((cur_freq == dramfreq->devfreq->min_freq) && (jump == FREQ_DOWN)) { for (i = 0; i < ARRAY_SIZE(busfreq_tbl); i++) { sunxi_dramfreq_busfreq_target(busfreq_tbl[i].name, busfreq_tbl[i].bus_clk, busfreq_tbl[i].idle_freq); } DRAMFREQ_DBG(DEBUG_FREQ, "AHB1:%lu\n", clk_get_rate(clk_ahb1) / 1000000); } #endif unlock: put_online_cpus(); mutex_unlock(&dramfreq->lock); return ret; } #ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER extern int sunxi_get_sensor_temp(u32 sensor_num, long *temperature); bool temptrigger_initialized; static int sunxi_get_dev_status(struct device *dev, struct devfreq_dev_status *stat) { int i, ret; long temp = 0, max_temp = 0, std_temp = 0; for (i = 0; i < 3; i++) { ret = sunxi_get_sensor_temp(i, &temp); if (ret == 0) max_temp = max(max_temp, temp); else break; } stat->private_data = temptrigger_initialized ? &max_temp : &std_temp; return 0; } #endif static struct devfreq_dev_profile sunxi_dramfreq_profile = { .get_cur_freq = sunxi_dramfreq_get_cur_freq, .target = sunxi_dramfreq_target, .freq_table = sunxi_dramfreq_table, #ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER .get_dev_status = sunxi_get_dev_status, #endif .max_state = LV_END, }; extern int update_devfreq(struct devfreq *devfreq); static int sunxi_dramfreq_task_func(void *data) { struct devfreq *df = (struct devfreq *)data; while (1) { mutex_lock(&df->lock); update_devfreq(df); mutex_unlock(&df->lock); set_current_state(TASK_INTERRUPTIBLE); schedule(); if (kthread_should_stop()) break; set_current_state(TASK_RUNNING); } return 0; } static void sunxi_dramfreq_masters_state_init(struct sunxi_dramfreq *dramfreq) { int i; for (i = 0; i < MASTER_MAX; i++) #if defined(CONFIG_ARCH_SUN50IW3) dramfreq->key_masters[i] = (i == MASTER_DE) ? 1 : 0; #else dramfreq->key_masters[i] = (i == MASTER_CSI) ? 0 : 1; #endif } #ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY static void sunxi_dramfreq_irq_clear_flag(struct sunxi_dramfreq *dramfreq) { int i; for (i = 0; i < dramfreq->master_reg_num; i++) { writel(0xFFFFFFFF, dramfreq->dramcom_base + MDFS_IRQ_ACCESS_STATUS(i)); writel(0xFFFFFFFF, dramfreq->dramcom_base + MDFS_IRQ_IDLE_STATUS(i)); } } static void sunxi_dramfreq_read_irq_status(struct sunxi_dramfreq *dramfreq) { int i; for (i = 0; i < dramfreq->master_reg_num; i++) { dramfreq->irq_access_status[i] = readl(dramfreq->dramcom_base + MDFS_IRQ_ACCESS_STATUS(i)); dramfreq->irq_idle_status[i] = readl(dramfreq->dramcom_base + MDFS_IRQ_IDLE_STATUS(i)); } } static void sunxi_dramfreq_read_irq_mask_status(struct sunxi_dramfreq *dramfreq) { int i; for (i = 0; i < dramfreq->master_reg_num; i++) { dramfreq->irq_access_mask_sta[i] = readl(dramfreq->dramcom_base + MDFS_IRQ_ACCESS_MASK_STA(i)); dramfreq->irq_idle_mask_sta[i] = readl(dramfreq->dramcom_base + MDFS_IRQ_IDLE_MASK_STA(i)); } } static void sunxi_dramfreq_write_irq_mask_status(struct sunxi_dramfreq *dramfreq) { int i; for (i = 0; i < dramfreq->master_reg_num; i++) { writel(dramfreq->irq_access_mask_sta[i], dramfreq->dramcom_base + MDFS_IRQ_ACCESS_MASK_STA(i)); writel(dramfreq->irq_idle_mask_sta[i], dramfreq->dramcom_base + MDFS_IRQ_IDLE_MASK_STA(i)); } } static void sunxi_dramfreq_read_irq_enable_status(struct sunxi_dramfreq *dramfreq, unsigned int *reg_value) { int i; for (i = 0; i < dramfreq->master_reg_num; i++) *(reg_value + i) = readl(dramfreq->dramcom_base + MDFS_MASTER_ENABLE(i)); } static void sunxi_dramfreq_write_irq_enable_status(struct sunxi_dramfreq *dramfreq, unsigned int *reg_value) { int i; for (i = 0; i < dramfreq->master_reg_num; i++) writel(*(reg_value + i), dramfreq->dramcom_base + MDFS_MASTER_ENABLE(i)); } static void sunxi_dramfreq_irq_mask_control(struct sunxi_dramfreq *dramfreq, bool valid) { unsigned int i; int idx, reg_list_num; sunxi_dramfreq_read_irq_mask_status(dramfreq); for (i = 0; i < MASTER_MAX; i++) { idx = key_masters_int_idx[i][1]; reg_list_num = key_masters_int_idx[i][2]; if (idx < 0 || reg_list_num < 0) { pr_err("%s: the key master doesn't exit", __func__); return; } if (valid) { dramfreq->irq_access_mask_sta[reg_list_num] |= (0x1 << idx); dramfreq->irq_idle_mask_sta[reg_list_num] |= (0x1 << idx); } else { dramfreq->irq_access_mask_sta[reg_list_num] &= (~(0x1 << idx)); dramfreq->irq_idle_mask_sta[reg_list_num] &= (~(0x1 << idx)); } } sunxi_dramfreq_write_irq_mask_status(dramfreq); } static void sunxi_dramfreq_irq_enable_control(struct sunxi_dramfreq *dramfreq, bool enable) { unsigned int i; int idx, reg_list_num; unsigned int reg_value[MASTER_REG_NUM] = {0}; sunxi_dramfreq_read_irq_enable_status(dramfreq, reg_value); for (i = 0; i < MASTER_MAX; i++) { idx = key_masters_int_idx[i][1]; reg_list_num = key_masters_int_idx[i][2]; if (idx < 0 || reg_list_num < 0) { pr_err("%s: the key master doesn't exit", __func__); return; } if (enable) reg_value[reg_list_num] |= (0x1 << idx); else reg_value[reg_list_num] &= (~(0x1 << idx)); } sunxi_dramfreq_write_irq_enable_status(dramfreq, reg_value); } static void sunxi_dramfreq_irq_judgment_and_clear(struct sunxi_dramfreq *dramfreq, bool *phandled) { int idx, reg_list_num; unsigned int i; for (i = 0; i < MASTER_MAX; i++) { idx = key_masters_int_idx[i][1]; reg_list_num = key_masters_int_idx[i][2]; if (idx < 0 || reg_list_num < 0) { pr_err("%s: the key master doesn't exit", __func__); return; } if ((dramfreq->irq_access_status[reg_list_num] >> idx) & 0x1) { dramfreq->key_masters[i] = 1; dramfreq->irq_access_status[reg_list_num] |= (0x1 << idx); writel(dramfreq->irq_access_status[reg_list_num], dramfreq->dramcom_base + MDFS_IRQ_ACCESS_STATUS(reg_list_num)); *phandled = true; } else if ((dramfreq->irq_idle_status[reg_list_num] >> idx) & 0x1) { dramfreq->key_masters[i] = 0; dramfreq->irq_idle_status[reg_list_num] |= (0x1 << idx); writel(dramfreq->irq_idle_status[reg_list_num], dramfreq->dramcom_base + MDFS_IRQ_IDLE_STATUS(reg_list_num)); *phandled = true; } } } static irqreturn_t sunxi_dramfreq_irq_handler(int irq, void *data) { struct sunxi_dramfreq *dramfreq = (struct sunxi_dramfreq *)data; bool handled = false; sunxi_dramfreq_read_irq_status(dramfreq); sunxi_dramfreq_irq_judgment_and_clear(dramfreq, &handled); if (!handled) { int i; for (i = 0; i < dramfreq->master_reg_num; i++) { DRAMFREQ_ERR("(IRQ) irq_access_sta%d=0x%x, irq_idle_sta%d=0x%x\n", i, dramfreq->irq_access_status[i], i, dramfreq->irq_idle_status[i]); } return IRQ_NONE; } if (!dramfreq->pause) wake_up_process(sunxi_dramfreq_task); return IRQ_HANDLED; } #endif /* CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY */ #ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER struct delayed_work sunxi_temp_work; struct workqueue_struct *dramfreq_temp_workqueue; static void sunxi_dramfreq_temp_work_func(struct work_struct *work) { if ((dramfreq == NULL) || (dramfreq->devfreq == NULL)) { DRAMFREQ_ERR("%s: para error\n", __func__); return; } mutex_lock(&dramfreq->devfreq->lock); update_devfreq(dramfreq->devfreq); mutex_unlock(&dramfreq->devfreq->lock); queue_delayed_work_on(0, dramfreq_temp_workqueue, &sunxi_temp_work, msecs_to_jiffies(100)); } #endif static int sunxi_dramfreq_governor_state_update(char *name, enum GOVERNOR_STATE type) { if (!strcmp(name, "adaptive")) { switch (type) { case STATE_INIT: dramfreq->pause = 1; sunxi_dramfreq_task = kthread_create(sunxi_dramfreq_task_func, dramfreq->devfreq, "dramfreq_task"); if (IS_ERR(sunxi_dramfreq_task)) return PTR_ERR(sunxi_dramfreq_task); get_task_struct(sunxi_dramfreq_task); wake_up_process(sunxi_dramfreq_task); break; case STATE_RUNNING: dramfreq->pause = 0; wake_up_process(sunxi_dramfreq_task); /* set master access init state */ sunxi_dramfreq_masters_state_init(dramfreq); #ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY /* set irq mask invalid */ sunxi_dramfreq_irq_mask_control(dramfreq, false); /* clear irq flag */ sunxi_dramfreq_irq_clear_flag(dramfreq); /* set irq enable */ sunxi_dramfreq_irq_enable_control(dramfreq, true); #endif break; case STATE_PAUSE: #ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY /* set irq disable */ sunxi_dramfreq_irq_enable_control(dramfreq, false); /* set irq mask valid */ sunxi_dramfreq_irq_mask_control(dramfreq, true); #endif dramfreq->pause = 1; wake_up_process(sunxi_dramfreq_task); break; case STATE_EXIT: #ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY /* set irq disable */ sunxi_dramfreq_irq_enable_control(dramfreq, false); /* set irq mask valid */ sunxi_dramfreq_irq_mask_control(dramfreq, true); #endif dramfreq->pause = 1; mutex_lock(&dramfreq->devfreq->lock); update_devfreq(dramfreq->devfreq); mutex_unlock(&dramfreq->devfreq->lock); kthread_stop(sunxi_dramfreq_task); put_task_struct(sunxi_dramfreq_task); break; } } #ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER else if (!strcmp(name, "temptrigger")) { switch (type) { case STATE_INIT: temptrigger_initialized = true; dramfreq_temp_workqueue = create_singlethread_workqueue("dramfreq_temp"); INIT_DELAYED_WORK(&sunxi_temp_work, sunxi_dramfreq_temp_work_func); queue_delayed_work_on(0, dramfreq_temp_workqueue, &sunxi_temp_work, msecs_to_jiffies(100)); break; case STATE_RUNNING: case STATE_PAUSE: break; case STATE_EXIT: temptrigger_initialized = false; mutex_lock(&dramfreq->devfreq->lock); update_devfreq(dramfreq->devfreq); mutex_unlock(&dramfreq->devfreq->lock); cancel_delayed_work_sync(&sunxi_temp_work); destroy_workqueue(dramfreq_temp_workqueue); break; } } #endif return 0; } static int sunxi_dramfreq_paras_init(struct sunxi_dramfreq *dramfreq) { struct device_node *dram_np; int ret = 0; dram_np = of_find_node_by_path("/dram"); if (!dram_np) { DRAMFREQ_ERR("Get dram node failed!\n"); return -ENODEV; } if (of_property_read_u32(dram_np, "dram_clk", &dramfreq->dram_para.dram_clk)) { DRAMFREQ_ERR("Get dram_clk failed!\n"); ret = -ENODEV; goto out_put_node; } if (of_property_read_u32(dram_np, "dram_type", &dramfreq->dram_para.dram_type)) { DRAMFREQ_ERR("Get dram_type failed!\n"); ret = -ENODEV; goto out_put_node; } if (of_property_read_u32(dram_np, "dram_odt_en", &dramfreq->dram_para.dram_odt_en)) { DRAMFREQ_ERR("Get dram_odt_en failed!\n"); ret = -ENODEV; goto out_put_node; } if (of_property_read_u32(dram_np, "dram_tpr9", &dramfreq->dram_para.dram_tpr9)) { DRAMFREQ_ERR("Get dram_tpr9 failed!\n"); ret = -ENODEV; goto out_put_node; } if (of_property_read_u32(dram_np, "dram_tpr13", &dramfreq->dram_para.dram_tpr13)) { DRAMFREQ_ERR("Get dram_tpr13 failed!\n"); ret = -ENODEV; goto out_put_node; } if (!((dramfreq->dram_para.dram_tpr13 >> 11) & 0x1)) { ret = -EINVAL; goto out_put_node; } dramfreq->mode = (dramfreq->dram_para.dram_tpr13 >> 10) & 0x1; out_put_node: of_node_put(dram_np); return ret; } static int sunxi_dramfreq_resource_init(struct platform_device *pdev, struct sunxi_dramfreq *dramfreq) { int ret = 0; dramfreq->dramcom_base = of_iomap(pdev->dev.of_node, 0); if (!dramfreq->dramcom_base) { DRAMFREQ_ERR("Map dramcom_base failed!\n"); ret = -EBUSY; goto out; } dramfreq->dramctl_base = of_iomap(pdev->dev.of_node, 1); if (!dramfreq->dramctl_base) { DRAMFREQ_ERR("Map dramctl_base failed!\n"); ret = -EBUSY; goto out; } dramfreq->ccu_base = of_iomap(pdev->dev.of_node, 2); if (!dramfreq->ccu_base) { DRAMFREQ_ERR("Map ccu_base failed!\n"); ret = -EBUSY; goto out; } #ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY dramfreq->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); if (!dramfreq->irq) { DRAMFREQ_ERR("Map IRQ failed!\n"); ret = -EBUSY; goto out; } #endif dramfreq->clk_pll_ddr0 = of_clk_get(pdev->dev.of_node, 0); if (IS_ERR(dramfreq->clk_pll_ddr0)) { DRAMFREQ_ERR("Get clk_pll_ddr0 failed!\n"); ret = -EINVAL; goto out; } #if !defined(CONFIG_ARCH_SUN50IW6) dramfreq->clk_pll_ddr1 = of_clk_get(pdev->dev.of_node, 1); if (IS_ERR(dramfreq->clk_pll_ddr1)) { DRAMFREQ_ERR("Get clk_pll_ddr1 failed!\n"); ret = -EINVAL; goto out; } #endif #ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ clk_ahb1 = of_clk_get(pdev->dev.of_node, 2); if (IS_ERR(clk_ahb1)) { DRAMFREQ_ERR("Get clk_ahb1 failed!\n"); ret = -EINVAL; goto out; } busfreq_tbl[0].bus_clk = clk_ahb1; #endif out: return ret; } static void __array_insert_sort(unsigned int *array, int count) { unsigned int i, j, temp; for (i = 1; i < count; i++) { temp = array[i]; j = i-1; while (array[j] < temp && j >= 0) { array[j+1] = array[j]; j--; } if (j != (i-1)) array[j+1] = temp; } } static void __array_delete_repeat(unsigned int *array, int count) { int i, j; for (i = 0, j = 0; i < count; i++) { while (array[i] == array[i+1]) i++; array[j++] = array[i]; } for (i = j; i < count; i++) array[i] = 0; } static int sunxi_dramfreq_opp_init(struct platform_device *pdev, struct sunxi_dramfreq *dramfreq) { int i, j, ret = 0; unsigned int tmp_table[LV_END], tmp_max_state = 0; for (i = 0; i < 3; i++) tmp_table[i] = dramfreq->dram_para.dram_clk * 1000 / (i + 1); if ((dramfreq->dram_para.dram_clk * 1000 / (i + 1)) >= SUNXI_DRAMFREQ_IDLE) tmp_table[i-1] = dramfreq->dram_para.dram_clk * 1000 / (i + 1); for (j = 0; i < LV_END; i++, j++) { if ((dramfreq->dram_para.dram_tpr9 / (j + 1)) > dramfreq->dram_para.dram_clk) { tmp_table[i] = 0; } else { tmp_table[i] = dramfreq->dram_para.dram_tpr9 * 1000 / (j + 1); } } if (dramfreq->mode == CFS_MODE) { for (i = 0; i < LV_END; i++) { if ((tmp_table[i] % 12) != 0) tmp_table[i] = ((tmp_table[i] / 1000) / 12) * 12 * 1000; } } for (i = 0; i < LV_END; i++) { if (tmp_table[i] < SUNXI_DRAMFREQ_IDLE) tmp_table[i] = 0; } __array_insert_sort(tmp_table, LV_END); __array_delete_repeat(tmp_table, LV_END); memcpy(sunxi_dramfreq_table, tmp_table, sizeof(tmp_table)); for (i = 0; i < LV_END; i++) { if (sunxi_dramfreq_table[i] == 0) continue; ret = dev_pm_opp_add(&pdev->dev, sunxi_dramfreq_table[i] * 1000, 0); if (ret) { DRAMFREQ_ERR("Failed to add OPP[%d]\n", i); goto out; } tmp_max_state++; } if (tmp_max_state < sunxi_dramfreq_profile.max_state) sunxi_dramfreq_profile.max_state = tmp_max_state; out: return ret; } static int sunxi_dramfreq_reboot(struct notifier_block *this, unsigned long code, void *_cmd) { dramfreq->pause = 1; #ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY /* set irq disable */ sunxi_dramfreq_irq_enable_control(dramfreq, false); /* set irq mask valid */ sunxi_dramfreq_irq_mask_control(dramfreq, true); #endif printk("%s:%s: stop dramfreq done\n", __FILE__, __func__); return NOTIFY_OK; } static struct notifier_block reboot_notifier = { .notifier_call = sunxi_dramfreq_reboot, }; static void sunxi_dramfreq_hw_init(struct sunxi_dramfreq *dramfreq) { volatile unsigned int reg_val; #ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY writel(0x18f, dramfreq->dramcom_base + MC_TIME_MEASUREMENT); /* set master idle period: 1000ms */ writel(0x3e7, dramfreq->dramcom_base + MDFS_BWC_PRD); #endif if (dramfreq->mode == DFS_MODE) { writel(0xFFFFFFFF, dramfreq->dramcom_base + MC_MDFSMRMR); /* set DFS time */ reg_val = readl(dramfreq->dramctl_base + PTR2); reg_val &= ~0x7fff; reg_val |= (0x7<<10 | 0x7<<5 | 0x7<<0); writel(reg_val, dramfreq->dramctl_base + PTR2); } } static int sunxi_dramfreq_probe(struct platform_device *pdev) { int ret = 0; dramfreq = kzalloc(sizeof(struct sunxi_dramfreq), GFP_KERNEL); if (dramfreq == NULL) { DRAMFREQ_ERR("Allocate memory failed!\n"); return -ENOMEM; } ret = sunxi_dramfreq_paras_init(dramfreq); if (ret == -ENODEV) { DRAMFREQ_ERR("Init dram para failed!\n"); goto err; } else if (ret == -EINVAL) { #if defined(CONFIG_ARCH_SUN50IW6) pr_info("[ddrfreq] disabled, but continue to get freq!\n"); #else pr_info("[ddrfreq] disabled!\n"); goto err; #endif } ret = sunxi_dramfreq_resource_init(pdev, dramfreq); if (ret) { DRAMFREQ_ERR("Init resource failed!\n"); goto err; } ret = sunxi_dramfreq_opp_init(pdev, dramfreq); if (ret) { DRAMFREQ_ERR("Init opp failed!\n"); goto err; } dev_set_name(&pdev->dev, "dramfreq"); platform_set_drvdata(pdev, dramfreq); sunxi_dramfreq_get_cur_freq(&pdev->dev, &sunxi_dramfreq_profile.initial_freq); dramfreq->devfreq = devfreq_add_device(&pdev->dev, &sunxi_dramfreq_profile, SUNXI_DRAM_FREQ_GOVERNOR, NULL); if (IS_ERR(dramfreq->devfreq)) { DRAMFREQ_ERR("Add devfreq device failed!\n"); ret = PTR_ERR(dramfreq->devfreq); goto err; } dramfreq->max = sunxi_dramfreq_get_max_freq(); dramfreq->min = sunxi_dramfreq_get_min_freq(); dramfreq->devfreq->max_freq = dramfreq->max; dramfreq->devfreq->min_freq = dramfreq->min; dramfreq->master_reg_num = MASTER_REG_NUM; dramfreq->pause = 1; dramfreq->governor_state_update = sunxi_dramfreq_governor_state_update; mutex_init(&dramfreq->lock); spin_lock_init(&dramfreq->master_lock); register_reboot_notifier(&reboot_notifier); /* init some hardware paras*/ sunxi_dramfreq_hw_init(dramfreq); #ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY if (request_irq(dramfreq->irq, sunxi_dramfreq_irq_handler, 0, "mdfs", dramfreq)) { DRAMFREQ_ERR("Request IRQ failed!\n"); ret = -EBUSY; goto err_irq_req; } /* set irq mask invalid */ sunxi_dramfreq_irq_mask_control(dramfreq, false); #endif return 0; #ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY err_irq_req: mutex_destroy(&dramfreq->lock); devfreq_remove_device(dramfreq->devfreq); #endif err: kfree(dramfreq); dramfreq = NULL; return ret; } static int sunxi_dramfreq_remove(struct platform_device *pdev) { struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev); if (!dramfreq) return -EINVAL; mutex_destroy(&dramfreq->lock); if (dramfreq->devfreq) devfreq_remove_device(dramfreq->devfreq); #if !defined(CONFIG_ARCH_SUN50IW6) if (dramfreq->clk_pll_ddr1) { clk_put(dramfreq->clk_pll_ddr1); dramfreq->clk_pll_ddr1 = NULL; } #endif if (dramfreq->clk_pll_ddr0) { clk_put(dramfreq->clk_pll_ddr0); dramfreq->clk_pll_ddr0 = NULL; } #ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ if (clk_ahb1) { clk_put(clk_ahb1); clk_ahb1 = NULL; } #endif kfree(dramfreq); return 0; } #ifdef CONFIG_OF static const struct of_device_id sunxi_dramfreq_match[] = { { .compatible = "allwinner,sunxi-dramfreq", }, {}, }; #else struct platform_device sunxi_dramfreq_device = { .name = "sunxi-dramfreq", .id = -1, }; #endif #ifdef CONFIG_PM static bool sunxi_dramfreq_cur_pause; static int sunxi_dramfreq_suspend(struct platform_device *pdev, pm_message_t state) { struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev); unsigned long cur_freq, target = dramfreq->max; int err = -1; if (!strcmp(dramfreq->devfreq->governor_name, "adaptive")) { sunxi_dramfreq_cur_pause = dramfreq->pause; if (!sunxi_dramfreq_cur_pause) { #ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY /* set irq disable */ sunxi_dramfreq_irq_enable_control(dramfreq, false); /* set irq mask valid */ sunxi_dramfreq_irq_mask_control(dramfreq, true); #endif dramfreq->pause = 1; sunxi_dramfreq_get_cur_freq(&pdev->dev, &cur_freq); if (cur_freq != target) { err = sunxi_dramfreq_target(&pdev->dev, &target, 0); if (!err) dramfreq->devfreq->previous_freq = target; } } } #ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER else if (!strcmp(dramfreq->devfreq->governor_name, "temptrigger")) cancel_delayed_work_sync(&sunxi_temp_work); #endif printk("%s:%d\n", __func__, __LINE__); return 0; } static int sunxi_dramfreq_resume(struct platform_device *pdev) { struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev); unsigned long cur_freq; /* * we should init hw, * otherwise dram hw will block when change freq. */ sunxi_dramfreq_hw_init(dramfreq); if (!strcmp(dramfreq->devfreq->governor_name, "adaptive")) { sunxi_dramfreq_get_cur_freq(&pdev->dev, &cur_freq); if (dramfreq->devfreq->previous_freq != cur_freq) dramfreq->devfreq->previous_freq = cur_freq; if (!sunxi_dramfreq_cur_pause) { dramfreq->pause = 0; /* set master access init state */ sunxi_dramfreq_masters_state_init(dramfreq); #ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY /* set irq mask invalid */ sunxi_dramfreq_irq_mask_control(dramfreq, false); /* clear irq flag */ sunxi_dramfreq_irq_clear_flag(dramfreq); /* set irq enable */ sunxi_dramfreq_irq_enable_control(dramfreq, true); #endif } } #ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER else if (!strcmp(dramfreq->devfreq->governor_name, "temptrigger")) { INIT_DELAYED_WORK(&sunxi_temp_work, sunxi_dramfreq_temp_work_func); queue_delayed_work_on(0, dramfreq_temp_workqueue, &sunxi_temp_work, msecs_to_jiffies(2000)); } #endif printk("%s:%d\n", __func__, __LINE__); return 0; } #endif static struct platform_driver sunxi_dramfreq_driver = { .probe = sunxi_dramfreq_probe, .remove = sunxi_dramfreq_remove, #ifdef CONFIG_PM .suspend = sunxi_dramfreq_suspend, .resume = sunxi_dramfreq_resume, #endif .driver = { .name = "sunxi-dramfreq", .owner = THIS_MODULE, #ifdef CONFIG_OF .of_match_table = sunxi_dramfreq_match, #endif }, }; static int __init sunxi_dramfreq_initcall(void) { int ret = 0; #ifndef CONFIG_OF ret = platform_device_register(&sunxi_dramfreq_device); if (ret) { DRAMFREQ_ERR("Register dramfreq device failed!\n"); goto out; } #endif ret = platform_driver_register(&sunxi_dramfreq_driver); if (ret) { DRAMFREQ_ERR("Register dramfreq driver failed!\n"); goto out; } out: return ret; } fs_initcall(sunxi_dramfreq_initcall); #ifdef CONFIG_DEBUG_FS static struct dentry *debugfs_dramfreq_root; static int dramfreq_debugfs_gettime_show(struct seq_file *s, void *data) { if (!dramfreq) seq_printf(s, "Invalid paras\n"); seq_printf(s, "%lld\n", dramfreq->dramfreq_get_us); return 0; } static int dramfreq_debugfs_gettime_open(struct inode *inode, struct file *file) { return single_open(file, dramfreq_debugfs_gettime_show, inode->i_private); } static const struct file_operations dramfreq_debugfs_gettime_fops = { .open = dramfreq_debugfs_gettime_open, .read = seq_read, }; static int dramfreq_debugfs_settime_show(struct seq_file *s, void *data) { if (!dramfreq) seq_printf(s, "Invalid paras\n"); seq_printf(s, "%lld\n", dramfreq->dramfreq_set_us); return 0; } static int dramfreq_debugfs_settime_open(struct inode *inode, struct file *file) { return single_open(file, dramfreq_debugfs_settime_show, inode->i_private); } static const struct file_operations dramfreq_debugfs_settime_fops = { .open = dramfreq_debugfs_settime_open, .read = seq_read, }; static int __init dramfreq_debugfs_init(void) { int err = 0; debugfs_dramfreq_root = debugfs_create_dir("dramfreq", 0); if (!debugfs_dramfreq_root) return -ENOMEM; if (!debugfs_create_file("get_time", 0444, debugfs_dramfreq_root, NULL, &dramfreq_debugfs_gettime_fops)) { err = -ENOMEM; goto out; } if (!debugfs_create_file("set_time", 0444, debugfs_dramfreq_root, NULL, &dramfreq_debugfs_settime_fops)) { err = -ENOMEM; goto out; } return 0; out: debugfs_remove_recursive(debugfs_dramfreq_root); return err; } static void __exit dramfreq_debugfs_exit(void) { debugfs_remove_recursive(debugfs_dramfreq_root); } late_initcall(dramfreq_debugfs_init); module_exit(dramfreq_debugfs_exit); #endif /* CONFIG_DEBUG_FS */ MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("SUNXI dramfreq driver with devfreq framework"); MODULE_AUTHOR("Pan Nan ");