/* * Copyright (C) ST-Ericsson SA 2011 * * License Terms: GNU General Public License v2 * * Author: Johan Rudholm * Author: Jonas Aaberg */ #include #include #include #include #include #include #include #include #include #include #include #define WLAN_PROBE_DELAY 3000 /* 3 seconds */ #define WLAN_LIMIT (3000/3) /* If we have more than 1000 irqs per second */ /* * MMC TODO: * o Develop a more power-aware algorithm * o Make the parameters visible through debugfs * o Get the value of CONFIG_MMC_BLOCK_MINORS in runtime instead, since * it may be altered by drivers/mmc/card/block.c */ /* Sample reads and writes every n ms */ #define PERF_MMC_PROBE_DELAY 1000 /* Read threshold, sectors/second */ #define PERF_MMC_LIMIT_READ 10240 /* Write threshold, sectors/second */ #define PERF_MMC_LIMIT_WRITE 8192 /* Nr of MMC devices */ #define PERF_MMC_HOSTS 8 /* * Rescan for new MMC devices every * PERF_MMC_PROBE_DELAY * PERF_MMC_RESCAN_CYCLES ms */ #define PERF_MMC_RESCAN_CYCLES 10 #ifdef CONFIG_MMC_BLOCK static struct delayed_work work_mmc; #endif static struct delayed_work work_wlan_workaround; static struct pm_qos_request_list wlan_pm_qos_latency; static bool wlan_pm_qos_is_latency_0; static void wlan_load(struct work_struct *work) { int cpu; unsigned int num_irqs = 0; static unsigned int old_num_irqs = UINT_MAX; for_each_online_cpu(cpu) num_irqs += kstat_irqs_cpu(IRQ_DB8500_SDMMC1, cpu); if ((num_irqs > old_num_irqs) && (num_irqs - old_num_irqs) > WLAN_LIMIT) { prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, "wlan", 125); if (!wlan_pm_qos_is_latency_0) { /* * The wake up latency is set to 0 to prevent * the system from going to sleep. This improves * the wlan throughput in DMA mode. * The wake up latency from sleep adds ~5% overhead * for TX in some cases. * This change doesn't increase performance for wlan * PIO since the CPU usage prevents sleep in this mode. */ pm_qos_add_request(&wlan_pm_qos_latency, PM_QOS_CPU_DMA_LATENCY, 0); wlan_pm_qos_is_latency_0 = true; } } else { prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, "wlan", 25); if (wlan_pm_qos_is_latency_0) { pm_qos_remove_request(&wlan_pm_qos_latency); wlan_pm_qos_is_latency_0 = false; } } old_num_irqs = num_irqs; schedule_delayed_work_on(0, &work_wlan_workaround, msecs_to_jiffies(WLAN_PROBE_DELAY)); } #ifdef CONFIG_MMC_BLOCK /* * Loop through every CONFIG_MMC_BLOCK_MINORS'th minor device for * MMC_BLOCK_MAJOR, get the struct gendisk for each device. Returns * nr of found disks. Populate mmc_disks. */ static int scan_mmc_devices(struct gendisk *mmc_disks[]) { dev_t devnr; int i, j = 0, part; struct gendisk *mmc_devices[256 / CONFIG_MMC_BLOCK_MINORS]; memset(&mmc_devices, 0, sizeof(mmc_devices)); for (i = 0; i * CONFIG_MMC_BLOCK_MINORS < 256; i++) { devnr = MKDEV(MMC_BLOCK_MAJOR, i * CONFIG_MMC_BLOCK_MINORS); mmc_devices[i] = get_gendisk(devnr, &part); /* Invalid capacity of device, do not add to list */ if (!mmc_devices[i] || !get_capacity(mmc_devices[i])) continue; mmc_disks[j] = mmc_devices[i]; j++; if (j == PERF_MMC_HOSTS) break; } return j; } /* * Sample sectors read and written to any MMC devices, update PRCMU * qos requirement */ static void mmc_load(struct work_struct *work) { static unsigned long long old_sectors_read[PERF_MMC_HOSTS]; static unsigned long long old_sectors_written[PERF_MMC_HOSTS]; static struct gendisk *mmc_disks[PERF_MMC_HOSTS]; static int cycle, nrdisk; static bool old_mode; unsigned long long sectors; bool new_mode = false; int i; if (!cycle) { memset(&mmc_disks, 0, sizeof(mmc_disks)); nrdisk = scan_mmc_devices(mmc_disks); cycle = PERF_MMC_RESCAN_CYCLES; } cycle--; for (i = 0; i < nrdisk; i++) { sectors = part_stat_read(&(mmc_disks[i]->part0), sectors[READ]); if (old_sectors_read[i] && sectors > old_sectors_read[i] && (sectors - old_sectors_read[i]) > PERF_MMC_LIMIT_READ) new_mode = true; old_sectors_read[i] = sectors; sectors = part_stat_read(&(mmc_disks[i]->part0), sectors[WRITE]); if (old_sectors_written[i] && sectors > old_sectors_written[i] && (sectors - old_sectors_written[i]) > PERF_MMC_LIMIT_WRITE) new_mode = true; old_sectors_written[i] = sectors; } if (!old_mode && new_mode) prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, "mmc", 125); if (old_mode && !new_mode) prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, "mmc", 25); old_mode = new_mode; schedule_delayed_work(&work_mmc, msecs_to_jiffies(PERF_MMC_PROBE_DELAY)); } #endif /* CONFIG_MMC_BLOCK */ static int __init performance_register(void) { int ret; #ifdef CONFIG_MMC_BLOCK ret = prcmu_qos_add_requirement(PRCMU_QOS_ARM_OPP, "mmc", 25); if (ret) { pr_err("%s: Failed to add PRCMU req for mmc\n", __func__); goto out; } INIT_DELAYED_WORK_DEFERRABLE(&work_mmc, mmc_load); schedule_delayed_work(&work_mmc, msecs_to_jiffies(PERF_MMC_PROBE_DELAY)); #endif ret = prcmu_qos_add_requirement(PRCMU_QOS_ARM_OPP, "wlan", 25); if (ret) { pr_err("%s: Failed to add PRCMU req for wlan\n", __func__); goto out; } INIT_DELAYED_WORK_DEFERRABLE(&work_wlan_workaround, wlan_load); schedule_delayed_work_on(0, &work_wlan_workaround, msecs_to_jiffies(WLAN_PROBE_DELAY)); out: return ret; } late_initcall(performance_register);