diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 7d2f43550700..49e74c1fc639 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -70,19 +70,20 @@ config ARM_EXYNOS4_BUS_DEVFREQ depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412) && !ARCH_MULTIPLATFORM select ARCH_HAS_OPP select DEVFREQ_GOV_SIMPLE_ONDEMAND + select PM_OPP help This adds the DEVFREQ driver for Exynos4210 memory bus (vdd_int) and Exynos4212/4412 memory interface and bus (vdd_mif + vdd_int). It reads PPMU counters of memory controllers and adjusts the operating frequencies and voltages with OPP support. - To operate with optimal voltages, ASV support is required - (CONFIG_EXYNOS_ASV). + This does not yet operate with optimal voltages. config ARM_EXYNOS5_BUS_DEVFREQ bool "ARM Exynos5250 Bus DEVFREQ Driver" depends on SOC_EXYNOS5250 select ARCH_HAS_OPP select DEVFREQ_GOV_SIMPLE_ONDEMAND + select PM_OPP help This adds the DEVFREQ driver for Exynos5250 bus interface (vdd_int). It reads PPMU counters of memory controllers and adjusts the diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 2042ec3656ba..9f90369dd6bd 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -394,7 +394,7 @@ static int devfreq_notifier_call(struct notifier_block *nb, unsigned long type, * @devfreq: the devfreq struct * @skip: skip calling device_unregister(). */ -static void _remove_devfreq(struct devfreq *devfreq, bool skip) +static void _remove_devfreq(struct devfreq *devfreq) { mutex_lock(&devfreq_list_lock); if (IS_ERR(find_device_devfreq(devfreq->dev.parent))) { @@ -412,11 +412,6 @@ static void _remove_devfreq(struct devfreq *devfreq, bool skip) if (devfreq->profile->exit) devfreq->profile->exit(devfreq->dev.parent); - if (!skip && get_device(&devfreq->dev)) { - device_unregister(&devfreq->dev); - put_device(&devfreq->dev); - } - mutex_destroy(&devfreq->lock); kfree(devfreq); } @@ -426,14 +421,12 @@ static void _remove_devfreq(struct devfreq *devfreq, bool skip) * @dev: the devfreq device * * This calls _remove_devfreq() if _remove_devfreq() is not called. - * Note that devfreq_dev_release() could be called by _remove_devfreq() as - * well as by others unregistering the device. */ static void devfreq_dev_release(struct device *dev) { struct devfreq *devfreq = to_devfreq(dev); - _remove_devfreq(devfreq, true); + _remove_devfreq(devfreq); } /** @@ -544,12 +537,76 @@ int devfreq_remove_device(struct devfreq *devfreq) if (!devfreq) return -EINVAL; - _remove_devfreq(devfreq, false); + device_unregister(&devfreq->dev); + put_device(&devfreq->dev); return 0; } EXPORT_SYMBOL(devfreq_remove_device); +static int devm_devfreq_dev_match(struct device *dev, void *res, void *data) +{ + struct devfreq **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +static void devm_devfreq_dev_release(struct device *dev, void *res) +{ + devfreq_remove_device(*(struct devfreq **)res); +} + +/** + * devm_devfreq_add_device() - Resource-managed devfreq_add_device() + * @dev: the device to add devfreq feature. + * @profile: device-specific profile to run devfreq. + * @governor_name: name of the policy to choose frequency. + * @data: private data for the governor. The devfreq framework does not + * touch this value. + * + * This function manages automatically the memory of devfreq device using device + * resource management and simplify the free operation for memory of devfreq + * device. + */ +struct devfreq *devm_devfreq_add_device(struct device *dev, + struct devfreq_dev_profile *profile, + const char *governor_name, + void *data) +{ + struct devfreq **ptr, *devfreq; + + ptr = devres_alloc(devm_devfreq_dev_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + devfreq = devfreq_add_device(dev, profile, governor_name, data); + if (IS_ERR(devfreq)) { + devres_free(ptr); + return ERR_PTR(-ENOMEM); + } + + *ptr = devfreq; + devres_add(dev, ptr); + + return devfreq; +} +EXPORT_SYMBOL(devm_devfreq_add_device); + +/** + * devm_devfreq_remove_device() - Resource-managed devfreq_remove_device() + * @dev: the device to add devfreq feature. + * @devfreq: the devfreq instance to be removed + */ +void devm_devfreq_remove_device(struct device *dev, struct devfreq *devfreq) +{ + WARN_ON(devres_release(dev, devm_devfreq_dev_release, + devm_devfreq_dev_match, devfreq)); +} +EXPORT_SYMBOL(devm_devfreq_remove_device); + /** * devfreq_suspend_device() - Suspend devfreq of a device. * @devfreq: the devfreq instance to be suspended @@ -1112,6 +1169,54 @@ int devfreq_unregister_opp_notifier(struct device *dev, struct devfreq *devfreq) return ret; } +static void devm_devfreq_opp_release(struct device *dev, void *res) +{ + devfreq_unregister_opp_notifier(dev, *(struct devfreq **)res); +} + +/** + * devm_ devfreq_register_opp_notifier() + * - Resource-managed devfreq_register_opp_notifier() + * @dev: The devfreq user device. (parent of devfreq) + * @devfreq: The devfreq object. + */ +int devm_devfreq_register_opp_notifier(struct device *dev, + struct devfreq *devfreq) +{ + struct devfreq **ptr; + int ret; + + ptr = devres_alloc(devm_devfreq_opp_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = devfreq_register_opp_notifier(dev, devfreq); + if (ret) { + devres_free(ptr); + return ret; + } + + *ptr = devfreq; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL(devm_devfreq_register_opp_notifier); + +/** + * devm_devfreq_unregister_opp_notifier() + * - Resource-managed devfreq_unregister_opp_notifier() + * @dev: The devfreq user device. (parent of devfreq) + * @devfreq: The devfreq object. + */ +void devm_devfreq_unregister_opp_notifier(struct device *dev, + struct devfreq *devfreq) +{ + WARN_ON(devres_release(dev, devm_devfreq_opp_release, + devm_devfreq_dev_match, devfreq)); +} +EXPORT_SYMBOL(devm_devfreq_unregister_opp_notifier); + MODULE_AUTHOR("MyungJoo Ham "); MODULE_DESCRIPTION("devfreq class support"); MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/exynos/Makefile b/drivers/devfreq/exynos/Makefile index bfaaf5b0d61d..49bc9175f923 100644 --- a/drivers/devfreq/exynos/Makefile +++ b/drivers/devfreq/exynos/Makefile @@ -1,3 +1,3 @@ # Exynos DEVFREQ Drivers -obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos4_bus.o +obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos_ppmu.o exynos4_bus.o obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos_ppmu.o exynos5_bus.o diff --git a/drivers/devfreq/exynos/exynos4_bus.c b/drivers/devfreq/exynos/exynos4_bus.c index e07b0c68c715..d9b08d3b6830 100644 --- a/drivers/devfreq/exynos/exynos4_bus.c +++ b/drivers/devfreq/exynos/exynos4_bus.c @@ -25,13 +25,9 @@ #include #include -/* Exynos4 ASV has been in the mailing list, but not upstreamed, yet. */ -#ifdef CONFIG_EXYNOS_ASV -extern unsigned int exynos_result_of_asv; -#endif - #include +#include "exynos_ppmu.h" #include "exynos4_bus.h" #define MAX_SAFEVOLT 1200000 /* 1.2V */ @@ -44,22 +40,6 @@ enum exynos4_busf_type { /* Assume that the bus is saturated if the utilization is 40% */ #define BUS_SATURATION_RATIO 40 -enum ppmu_counter { - PPMU_PMNCNT0 = 0, - PPMU_PMCCNT1, - PPMU_PMNCNT2, - PPMU_PMNCNT3, - PPMU_PMNCNT_MAX, -}; -struct exynos4_ppmu { - void __iomem *hw_base; - unsigned int ccnt; - unsigned int event; - unsigned int count[PPMU_PMNCNT_MAX]; - bool ccnt_overflow; - bool count_overflow[PPMU_PMNCNT_MAX]; -}; - enum busclk_level_idx { LV_0 = 0, LV_1, @@ -68,6 +48,13 @@ enum busclk_level_idx { LV_4, _LV_END }; + +enum exynos_ppmu_idx { + PPMU_DMC0, + PPMU_DMC1, + PPMU_END, +}; + #define EX4210_LV_MAX LV_2 #define EX4x12_LV_MAX LV_4 #define EX4210_LV_NUM (LV_2 + 1) @@ -91,7 +78,7 @@ struct busfreq_data { struct regulator *vdd_int; struct regulator *vdd_mif; /* Exynos4412/4212 only */ struct busfreq_opp_info curr_oppinfo; - struct exynos4_ppmu dmc[2]; + struct busfreq_ppmu_data ppmu_data; struct notifier_block pm_notifier; struct mutex lock; @@ -101,12 +88,6 @@ struct busfreq_data { unsigned int top_divtable[_LV_END]; }; -struct bus_opp_table { - unsigned int idx; - unsigned long clk; - unsigned long volt; -}; - /* 4210 controls clock of mif and voltage of int */ static struct bus_opp_table exynos4210_busclk_table[] = { {LV_0, 400000, 1150000}, @@ -524,57 +505,6 @@ static int exynos4x12_set_busclk(struct busfreq_data *data, return 0; } - -static void busfreq_mon_reset(struct busfreq_data *data) -{ - unsigned int i; - - for (i = 0; i < 2; i++) { - void __iomem *ppmu_base = data->dmc[i].hw_base; - - /* Reset PPMU */ - __raw_writel(0x8000000f, ppmu_base + 0xf010); - __raw_writel(0x8000000f, ppmu_base + 0xf050); - __raw_writel(0x6, ppmu_base + 0xf000); - __raw_writel(0x0, ppmu_base + 0xf100); - - /* Set PPMU Event */ - data->dmc[i].event = 0x6; - __raw_writel(((data->dmc[i].event << 12) | 0x1), - ppmu_base + 0xfc); - - /* Start PPMU */ - __raw_writel(0x1, ppmu_base + 0xf000); - } -} - -static void exynos4_read_ppmu(struct busfreq_data *data) -{ - int i, j; - - for (i = 0; i < 2; i++) { - void __iomem *ppmu_base = data->dmc[i].hw_base; - u32 overflow; - - /* Stop PPMU */ - __raw_writel(0x0, ppmu_base + 0xf000); - - /* Update local data from PPMU */ - overflow = __raw_readl(ppmu_base + 0xf050); - - data->dmc[i].ccnt = __raw_readl(ppmu_base + 0xf100); - data->dmc[i].ccnt_overflow = overflow & (1 << 31); - - for (j = 0; j < PPMU_PMNCNT_MAX; j++) { - data->dmc[i].count[j] = __raw_readl( - ppmu_base + (0xf110 + (0x10 * j))); - data->dmc[i].count_overflow[j] = overflow & (1 << j); - } - } - - busfreq_mon_reset(data); -} - static int exynos4x12_get_intspec(unsigned long mifclk) { int i = 0; @@ -698,84 +628,35 @@ out: return err; } -static int exynos4_get_busier_dmc(struct busfreq_data *data) -{ - u64 p0 = data->dmc[0].count[0]; - u64 p1 = data->dmc[1].count[0]; - - p0 *= data->dmc[1].ccnt; - p1 *= data->dmc[0].ccnt; - - if (data->dmc[1].ccnt == 0) - return 0; - - if (p0 > p1) - return 0; - return 1; -} - static int exynos4_bus_get_dev_status(struct device *dev, struct devfreq_dev_status *stat) { struct busfreq_data *data = dev_get_drvdata(dev); - int busier_dmc; - int cycles_x2 = 2; /* 2 x cycles */ - void __iomem *addr; - u32 timing; - u32 memctrl; + struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; + int busier; - exynos4_read_ppmu(data); - busier_dmc = exynos4_get_busier_dmc(data); + exynos_read_ppmu(ppmu_data); + busier = exynos_get_busier_ppmu(ppmu_data); stat->current_frequency = data->curr_oppinfo.rate; - if (busier_dmc) - addr = S5P_VA_DMC1; - else - addr = S5P_VA_DMC0; - - memctrl = __raw_readl(addr + 0x04); /* one of DDR2/3/LPDDR2 */ - timing = __raw_readl(addr + 0x38); /* CL or WL/RL values */ - - switch ((memctrl >> 8) & 0xf) { - case 0x4: /* DDR2 */ - cycles_x2 = ((timing >> 16) & 0xf) * 2; - break; - case 0x5: /* LPDDR2 */ - case 0x6: /* DDR3 */ - cycles_x2 = ((timing >> 8) & 0xf) + ((timing >> 0) & 0xf); - break; - default: - pr_err("%s: Unknown Memory Type(%d).\n", __func__, - (memctrl >> 8) & 0xf); - return -EINVAL; - } - /* Number of cycles spent on memory access */ - stat->busy_time = data->dmc[busier_dmc].count[0] / 2 * (cycles_x2 + 2); + stat->busy_time = ppmu_data->ppmu[busier].count[PPMU_PMNCNT3]; stat->busy_time *= 100 / BUS_SATURATION_RATIO; - stat->total_time = data->dmc[busier_dmc].ccnt; + stat->total_time = ppmu_data->ppmu[busier].ccnt; /* If the counters have overflown, retry */ - if (data->dmc[busier_dmc].ccnt_overflow || - data->dmc[busier_dmc].count_overflow[0]) + if (ppmu_data->ppmu[busier].ccnt_overflow || + ppmu_data->ppmu[busier].count_overflow[0]) return -EAGAIN; return 0; } -static void exynos4_bus_exit(struct device *dev) -{ - struct busfreq_data *data = dev_get_drvdata(dev); - - devfreq_unregister_opp_notifier(dev, data->devfreq); -} - static struct devfreq_dev_profile exynos4_devfreq_profile = { .initial_freq = 400000, .polling_ms = 50, .target = exynos4_bus_target, .get_dev_status = exynos4_bus_get_dev_status, - .exit = exynos4_bus_exit, }; static int exynos4210_init_tables(struct busfreq_data *data) @@ -837,11 +718,11 @@ static int exynos4210_init_tables(struct busfreq_data *data) data->top_divtable[i] = tmp; } -#ifdef CONFIG_EXYNOS_ASV - tmp = exynos4_result_of_asv; -#else + /* + * TODO: init tmp based on busfreq_data + * (device-tree or platform-data) + */ tmp = 0; /* Max voltages for the reliability of the unknown */ -#endif pr_debug("ASV Group of Exynos4 is %d\n", tmp); /* Use merged grouping for voltage */ @@ -922,11 +803,7 @@ static int exynos4x12_init_tables(struct busfreq_data *data) data->dmc_divtable[i] = tmp; } -#ifdef CONFIG_EXYNOS_ASV - tmp = exynos4_result_of_asv; -#else tmp = 0; /* Max voltages for the reliability of the unknown */ -#endif if (tmp > 8) tmp = 0; @@ -1020,6 +897,7 @@ unlock: static int exynos4_busfreq_probe(struct platform_device *pdev) { struct busfreq_data *data; + struct busfreq_ppmu_data *ppmu_data; struct dev_pm_opp *opp; struct device *dev = &pdev->dev; int err = 0; @@ -1030,9 +908,19 @@ static int exynos4_busfreq_probe(struct platform_device *pdev) return -ENOMEM; } + ppmu_data = &data->ppmu_data; + ppmu_data->ppmu_end = PPMU_END; + ppmu_data->ppmu = devm_kzalloc(dev, + sizeof(struct exynos_ppmu) * PPMU_END, + GFP_KERNEL); + if (!ppmu_data->ppmu) { + dev_err(dev, "Failed to allocate memory for exynos_ppmu\n"); + return -ENOMEM; + } + data->type = pdev->id_entry->driver_data; - data->dmc[0].hw_base = S5P_VA_DMC0; - data->dmc[1].hw_base = S5P_VA_DMC1; + ppmu_data->ppmu[PPMU_DMC0].hw_base = S5P_VA_DMC0; + ppmu_data->ppmu[PPMU_DMC1].hw_base = S5P_VA_DMC1; data->pm_notifier.notifier_call = exynos4_busfreq_pm_notifier_event; data->dev = dev; mutex_init(&data->lock); @@ -1048,8 +936,11 @@ static int exynos4_busfreq_probe(struct platform_device *pdev) dev_err(dev, "Cannot determine the device id %d\n", data->type); err = -EINVAL; } - if (err) + if (err) { + dev_err(dev, "Cannot initialize busfreq table %d\n", + data->type); return err; + } data->vdd_int = devm_regulator_get(dev, "vdd_int"); if (IS_ERR(data->vdd_int)) { @@ -1079,19 +970,28 @@ static int exynos4_busfreq_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); - busfreq_mon_reset(data); - - data->devfreq = devfreq_add_device(dev, &exynos4_devfreq_profile, + data->devfreq = devm_devfreq_add_device(dev, &exynos4_devfreq_profile, "simple_ondemand", NULL); if (IS_ERR(data->devfreq)) return PTR_ERR(data->devfreq); - devfreq_register_opp_notifier(dev, data->devfreq); + /* + * Start PPMU (Performance Profiling Monitoring Unit) to check + * utilization of each IP in the Exynos4 SoC. + */ + busfreq_mon_reset(ppmu_data); + /* Register opp_notifier for Exynos4 busfreq */ + err = devm_devfreq_register_opp_notifier(dev, data->devfreq); + if (err < 0) { + dev_err(dev, "Failed to register opp notifier\n"); + return err; + } + + /* Register pm_notifier for Exynos4 busfreq */ err = register_pm_notifier(&data->pm_notifier); if (err) { dev_err(dev, "Failed to setup pm notifier\n"); - devfreq_remove_device(data->devfreq); return err; } @@ -1102,23 +1002,24 @@ static int exynos4_busfreq_remove(struct platform_device *pdev) { struct busfreq_data *data = platform_get_drvdata(pdev); + /* Unregister all of notifier chain */ unregister_pm_notifier(&data->pm_notifier); - devfreq_remove_device(data->devfreq); return 0; } +#ifdef CONFIG_PM_SLEEP static int exynos4_busfreq_resume(struct device *dev) { struct busfreq_data *data = dev_get_drvdata(dev); + struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; - busfreq_mon_reset(data); + busfreq_mon_reset(ppmu_data); return 0; } +#endif -static const struct dev_pm_ops exynos4_busfreq_pm = { - .resume = exynos4_busfreq_resume, -}; +static SIMPLE_DEV_PM_OPS(exynos4_busfreq_pm_ops, NULL, exynos4_busfreq_resume); static const struct platform_device_id exynos4_busfreq_id[] = { { "exynos4210-busfreq", TYPE_BUSF_EXYNOS4210 }, @@ -1134,7 +1035,7 @@ static struct platform_driver exynos4_busfreq_driver = { .driver = { .name = "exynos4-busfreq", .owner = THIS_MODULE, - .pm = &exynos4_busfreq_pm, + .pm = &exynos4_busfreq_pm_ops, }, }; diff --git a/drivers/devfreq/exynos/exynos5_bus.c b/drivers/devfreq/exynos/exynos5_bus.c index 6eef1f7397c6..6cd0392e2798 100644 --- a/drivers/devfreq/exynos/exynos5_bus.c +++ b/drivers/devfreq/exynos/exynos5_bus.c @@ -50,7 +50,7 @@ struct busfreq_data_int { struct device *dev; struct devfreq *devfreq; struct regulator *vdd_int; - struct exynos_ppmu ppmu[PPMU_END]; + struct busfreq_ppmu_data ppmu_data; unsigned long curr_freq; bool disabled; @@ -75,49 +75,6 @@ static struct int_bus_opp_table exynos5_int_opp_table[] = { {0, 0, 0}, }; -static void busfreq_mon_reset(struct busfreq_data_int *data) -{ - unsigned int i; - - for (i = PPMU_RIGHT; i < PPMU_END; i++) { - void __iomem *ppmu_base = data->ppmu[i].hw_base; - - /* Reset the performance and cycle counters */ - exynos_ppmu_reset(ppmu_base); - - /* Setup count registers to monitor read/write transactions */ - data->ppmu[i].event[PPMU_PMNCNT3] = RDWR_DATA_COUNT; - exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3, - data->ppmu[i].event[PPMU_PMNCNT3]); - - exynos_ppmu_start(ppmu_base); - } -} - -static void exynos5_read_ppmu(struct busfreq_data_int *data) -{ - int i, j; - - for (i = PPMU_RIGHT; i < PPMU_END; i++) { - void __iomem *ppmu_base = data->ppmu[i].hw_base; - - exynos_ppmu_stop(ppmu_base); - - /* Update local data from PPMU */ - data->ppmu[i].ccnt = __raw_readl(ppmu_base + PPMU_CCNT); - - for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) { - if (data->ppmu[i].event[j] == 0) - data->ppmu[i].count[j] = 0; - else - data->ppmu[i].count[j] = - exynos_ppmu_read(ppmu_base, j); - } - } - - busfreq_mon_reset(data); -} - static int exynos5_int_setvolt(struct busfreq_data_int *data, unsigned long volt) { @@ -185,59 +142,33 @@ out: return err; } -static int exynos5_get_busier_dmc(struct busfreq_data_int *data) -{ - int i, j; - int busy = 0; - unsigned int temp = 0; - - for (i = PPMU_RIGHT; i < PPMU_END; i++) { - for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) { - if (data->ppmu[i].count[j] > temp) { - temp = data->ppmu[i].count[j]; - busy = i; - } - } - } - - return busy; -} - static int exynos5_int_get_dev_status(struct device *dev, struct devfreq_dev_status *stat) { struct platform_device *pdev = container_of(dev, struct platform_device, dev); struct busfreq_data_int *data = platform_get_drvdata(pdev); + struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; int busier_dmc; - exynos5_read_ppmu(data); - busier_dmc = exynos5_get_busier_dmc(data); + exynos_read_ppmu(ppmu_data); + busier_dmc = exynos_get_busier_ppmu(ppmu_data); stat->current_frequency = data->curr_freq; /* Number of cycles spent on memory access */ - stat->busy_time = data->ppmu[busier_dmc].count[PPMU_PMNCNT3]; + stat->busy_time = ppmu_data->ppmu[busier_dmc].count[PPMU_PMNCNT3]; stat->busy_time *= 100 / INT_BUS_SATURATION_RATIO; - stat->total_time = data->ppmu[busier_dmc].ccnt; + stat->total_time = ppmu_data->ppmu[busier_dmc].ccnt; return 0; } -static void exynos5_int_exit(struct device *dev) -{ - struct platform_device *pdev = container_of(dev, struct platform_device, - dev); - struct busfreq_data_int *data = platform_get_drvdata(pdev); - - devfreq_unregister_opp_notifier(dev, data->devfreq); -} static struct devfreq_dev_profile exynos5_devfreq_int_profile = { .initial_freq = 160000, .polling_ms = 100, .target = exynos5_busfreq_int_target, .get_dev_status = exynos5_int_get_dev_status, - .exit = exynos5_int_exit, }; static int exynos5250_init_int_tables(struct busfreq_data_int *data) @@ -315,6 +246,7 @@ unlock: static int exynos5_busfreq_int_probe(struct platform_device *pdev) { struct busfreq_data_int *data; + struct busfreq_ppmu_data *ppmu_data; struct dev_pm_opp *opp; struct device *dev = &pdev->dev; struct device_node *np; @@ -330,16 +262,26 @@ static int exynos5_busfreq_int_probe(struct platform_device *pdev) return -ENOMEM; } + ppmu_data = &data->ppmu_data; + ppmu_data->ppmu_end = PPMU_END; + ppmu_data->ppmu = devm_kzalloc(dev, + sizeof(struct exynos_ppmu) * PPMU_END, + GFP_KERNEL); + if (!ppmu_data->ppmu) { + dev_err(dev, "Failed to allocate memory for exynos_ppmu\n"); + return -ENOMEM; + } + np = of_find_compatible_node(NULL, NULL, "samsung,exynos5250-ppmu"); if (np == NULL) { pr_err("Unable to find PPMU node\n"); return -ENOENT; } - for (i = PPMU_RIGHT; i < PPMU_END; i++) { + for (i = 0; i < ppmu_data->ppmu_end; i++) { /* map PPMU memory region */ - data->ppmu[i].hw_base = of_iomap(np, i); - if (data->ppmu[i].hw_base == NULL) { + ppmu_data->ppmu[i].hw_base = of_iomap(np, i); + if (ppmu_data->ppmu[i].hw_base == NULL) { dev_err(&pdev->dev, "failed to map memory region\n"); return -ENOMEM; } @@ -390,32 +332,29 @@ static int exynos5_busfreq_int_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); - busfreq_mon_reset(data); + busfreq_mon_reset(ppmu_data); - data->devfreq = devfreq_add_device(dev, &exynos5_devfreq_int_profile, + data->devfreq = devm_devfreq_add_device(dev, &exynos5_devfreq_int_profile, "simple_ondemand", NULL); + if (IS_ERR(data->devfreq)) + return PTR_ERR(data->devfreq); - if (IS_ERR(data->devfreq)) { - err = PTR_ERR(data->devfreq); - goto err_devfreq_add; + err = devm_devfreq_register_opp_notifier(dev, data->devfreq); + if (err < 0) { + dev_err(dev, "Failed to register opp notifier\n"); + return err; } - devfreq_register_opp_notifier(dev, data->devfreq); - err = register_pm_notifier(&data->pm_notifier); if (err) { dev_err(dev, "Failed to setup pm notifier\n"); - goto err_devfreq_add; + return err; } /* TODO: Add a new QOS class for int/mif bus */ pm_qos_add_request(&data->int_req, PM_QOS_NETWORK_THROUGHPUT, -1); return 0; - -err_devfreq_add: - devfreq_remove_device(data->devfreq); - return err; } static int exynos5_busfreq_int_remove(struct platform_device *pdev) @@ -424,24 +363,27 @@ static int exynos5_busfreq_int_remove(struct platform_device *pdev) pm_qos_remove_request(&data->int_req); unregister_pm_notifier(&data->pm_notifier); - devfreq_remove_device(data->devfreq); return 0; } +#ifdef CONFIG_PM_SLEEP static int exynos5_busfreq_int_resume(struct device *dev) { struct platform_device *pdev = container_of(dev, struct platform_device, dev); struct busfreq_data_int *data = platform_get_drvdata(pdev); + struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; - busfreq_mon_reset(data); + busfreq_mon_reset(ppmu_data); return 0; } - static const struct dev_pm_ops exynos5_busfreq_int_pm = { .resume = exynos5_busfreq_int_resume, }; +#endif +static SIMPLE_DEV_PM_OPS(exynos5_busfreq_int_pm_ops, NULL, + exynos5_busfreq_int_resume); /* platform device pointer for exynos5 devfreq device. */ static struct platform_device *exynos5_devfreq_pdev; @@ -452,7 +394,7 @@ static struct platform_driver exynos5_busfreq_int_driver = { .driver = { .name = "exynos5-bus-int", .owner = THIS_MODULE, - .pm = &exynos5_busfreq_int_pm, + .pm = &exynos5_busfreq_int_pm_ops, }, }; diff --git a/drivers/devfreq/exynos/exynos_ppmu.c b/drivers/devfreq/exynos/exynos_ppmu.c index 85fc5ac1036a..75fcc5140ffb 100644 --- a/drivers/devfreq/exynos/exynos_ppmu.c +++ b/drivers/devfreq/exynos/exynos_ppmu.c @@ -54,3 +54,63 @@ unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch) return total; } + +void busfreq_mon_reset(struct busfreq_ppmu_data *ppmu_data) +{ + unsigned int i; + + for (i = 0; i < ppmu_data->ppmu_end; i++) { + void __iomem *ppmu_base = ppmu_data->ppmu[i].hw_base; + + /* Reset the performance and cycle counters */ + exynos_ppmu_reset(ppmu_base); + + /* Setup count registers to monitor read/write transactions */ + ppmu_data->ppmu[i].event[PPMU_PMNCNT3] = RDWR_DATA_COUNT; + exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3, + ppmu_data->ppmu[i].event[PPMU_PMNCNT3]); + + exynos_ppmu_start(ppmu_base); + } +} + +void exynos_read_ppmu(struct busfreq_ppmu_data *ppmu_data) +{ + int i, j; + + for (i = 0; i < ppmu_data->ppmu_end; i++) { + void __iomem *ppmu_base = ppmu_data->ppmu[i].hw_base; + + exynos_ppmu_stop(ppmu_base); + + /* Update local data from PPMU */ + ppmu_data->ppmu[i].ccnt = __raw_readl(ppmu_base + PPMU_CCNT); + + for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) { + if (ppmu_data->ppmu[i].event[j] == 0) + ppmu_data->ppmu[i].count[j] = 0; + else + ppmu_data->ppmu[i].count[j] = + exynos_ppmu_read(ppmu_base, j); + } + } + + busfreq_mon_reset(ppmu_data); +} + +int exynos_get_busier_ppmu(struct busfreq_ppmu_data *ppmu_data) +{ + unsigned int count = 0; + int i, j, busy = 0; + + for (i = 0; i < ppmu_data->ppmu_end; i++) { + for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) { + if (ppmu_data->ppmu[i].count[j] > count) { + count = ppmu_data->ppmu[i].count[j]; + busy = i; + } + } + } + + return busy; +} diff --git a/drivers/devfreq/exynos/exynos_ppmu.h b/drivers/devfreq/exynos/exynos_ppmu.h index 7dfb221eaccd..71f17ba3563c 100644 --- a/drivers/devfreq/exynos/exynos_ppmu.h +++ b/drivers/devfreq/exynos/exynos_ppmu.h @@ -69,10 +69,18 @@ struct exynos_ppmu { bool count_overflow[PPMU_PMNCNT_MAX]; }; +struct busfreq_ppmu_data { + struct exynos_ppmu *ppmu; + int ppmu_end; +}; + void exynos_ppmu_reset(void __iomem *ppmu_base); void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch, unsigned int evt); void exynos_ppmu_start(void __iomem *ppmu_base); void exynos_ppmu_stop(void __iomem *ppmu_base); unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch); +void busfreq_mon_reset(struct busfreq_ppmu_data *ppmu_data); +void exynos_read_ppmu(struct busfreq_ppmu_data *ppmu_data); +int exynos_get_busier_ppmu(struct busfreq_ppmu_data *ppmu_data); #endif /* __DEVFREQ_EXYNOS_PPMU_H */ diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index d48dc00232a4..f1863dcd83ea 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -181,6 +181,12 @@ extern struct devfreq *devfreq_add_device(struct device *dev, const char *governor_name, void *data); extern int devfreq_remove_device(struct devfreq *devfreq); +extern struct devfreq *devm_devfreq_add_device(struct device *dev, + struct devfreq_dev_profile *profile, + const char *governor_name, + void *data); +extern void devm_devfreq_remove_device(struct device *dev, + struct devfreq *devfreq); /* Supposed to be called by PM_SLEEP/PM_RUNTIME callbacks */ extern int devfreq_suspend_device(struct devfreq *devfreq); @@ -193,6 +199,10 @@ extern int devfreq_register_opp_notifier(struct device *dev, struct devfreq *devfreq); extern int devfreq_unregister_opp_notifier(struct device *dev, struct devfreq *devfreq); +extern int devm_devfreq_register_opp_notifier(struct device *dev, + struct devfreq *devfreq); +extern void devm_devfreq_unregister_opp_notifier(struct device *dev, + struct devfreq *devfreq); #if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND) /** @@ -220,7 +230,7 @@ static inline struct devfreq *devfreq_add_device(struct device *dev, const char *governor_name, void *data) { - return NULL; + return ERR_PTR(-ENOSYS); } static inline int devfreq_remove_device(struct devfreq *devfreq) @@ -228,6 +238,19 @@ static inline int devfreq_remove_device(struct devfreq *devfreq) return 0; } +static inline struct devfreq *devm_devfreq_add_device(struct device *dev, + struct devfreq_dev_profile *profile, + const char *governor_name, + void *data) +{ + return ERR_PTR(-ENOSYS); +} + +static inline void devm_devfreq_remove_device(struct device *dev, + struct devfreq *devfreq) +{ +} + static inline int devfreq_suspend_device(struct devfreq *devfreq) { return 0; @@ -256,6 +279,16 @@ static inline int devfreq_unregister_opp_notifier(struct device *dev, return -EINVAL; } +static inline int devm_devfreq_register_opp_notifier(struct device *dev, + struct devfreq *devfreq) +{ + return -EINVAL; +} + +static inline void devm_devfreq_unregister_opp_notifier(struct device *dev, + struct devfreq *devfreq) +{ +} #endif /* CONFIG_PM_DEVFREQ */ #endif /* __LINUX_DEVFREQ_H__ */