diff options
Diffstat (limited to 'arch/arm/mach-ux500')
47 files changed, 13470 insertions, 640 deletions
diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index 4bc27fc908f..165435edeed 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -8,6 +8,7 @@ obj-y := clock.o cpu.o devices.o \ uart-db8500.o clock-debug.o obj-y += pm/ test/ obj-$(CONFIG_CACHE_L2X0) += cache-l2x0.o +obj-$(CONFIG_CPU_IDLE) += cpuidle.o ifeq ($(CONFIG_UX500_SOC_DB5500), y) diff --git a/arch/arm/mach-ux500/board-mop500-bm.c b/arch/arm/mach-ux500/board-mop500-bm.c new file mode 100644 index 00000000000..afdc6ee59a3 --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-bm.c @@ -0,0 +1,517 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U8500 board specific charger and battery initialization parameters. + * + * Author: Johan Palsson <johan.palsson@stericsson.com> for ST-Ericsson. + * Author: Johan Gardsmark <johan.gardsmark@stericsson.com> for ST-Ericsson. + * + */ + +#include <linux/power_supply.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/ab8500/pwmleds.h> +#include "board-mop500-bm.h" + +#ifdef CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL +/* + * These are the defined batteries that uses a NTC and ID resistor placed + * inside of the battery pack. + * Note that the res_to_temp table must be strictly sorted by falling resistance + * values to work. + */ +static struct abx500_res_to_temp temp_tbl_A[] = { + {-5, 53407}, + { 0, 48594}, + { 5, 43804}, + {10, 39188}, + {15, 34870}, + {20, 30933}, + {25, 27422}, + {30, 24347}, + {35, 21694}, + {40, 19431}, + {45, 17517}, + {50, 15908}, + {55, 14561}, + {60, 13437}, + {65, 12500}, +}; +static struct abx500_res_to_temp temp_tbl_B[] = { + {-5, 165418}, + { 0, 159024}, + { 5, 151921}, + {10, 144300}, + {15, 136424}, + {20, 128565}, + {25, 120978}, + {30, 113875}, + {35, 107397}, + {40, 101629}, + {45, 96592}, + {50, 92253}, + {55, 88569}, + {60, 85461}, + {65, 82869}, +}; +static struct abx500_v_to_cap cap_tbl_A[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; +static struct abx500_v_to_cap cap_tbl_B[] = { + {4161, 100}, + {4124, 98}, + {4044, 90}, + {4003, 85}, + {3966, 80}, + {3933, 75}, + {3888, 67}, + {3849, 60}, + {3813, 55}, + {3787, 47}, + {3772, 30}, + {3751, 25}, + {3718, 20}, + {3681, 16}, + {3660, 14}, + {3589, 10}, + {3546, 7}, + {3495, 4}, + {3404, 2}, + {3250, 0}, +}; +#endif +static struct abx500_v_to_cap cap_tbl[] = { + {4186, 100}, + {4163, 99}, + {4114, 95}, + {4068, 90}, + {3990, 80}, + {3926, 70}, + {3898, 65}, + {3866, 60}, + {3833, 55}, + {3812, 50}, + {3787, 40}, + {3768, 30}, + {3747, 25}, + {3730, 20}, + {3705, 15}, + {3699, 14}, + {3684, 12}, + {3672, 9}, + {3657, 7}, + {3638, 6}, + {3556, 4}, + {3424, 2}, + {3317, 1}, + {3094, 0}, +}; + +/* + * Note that the res_to_temp table must be strictly sorted by falling + * resistance values to work. + */ +static struct abx500_res_to_temp temp_tbl[] = { + {-5, 214834}, + { 0, 162943}, + { 5, 124820}, + {10, 96520}, + {15, 75306}, + {20, 59254}, + {25, 47000}, + {30, 37566}, + {35, 30245}, + {40, 24520}, + {45, 20010}, + {50, 16432}, + {55, 13576}, + {60, 11280}, + {65, 9425}, +}; + +#ifdef CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL +/* + * Note that the batres_vs_temp table must be strictly sorted by falling + * temperature values to work. + */ +static struct batres_vs_temp temp_to_batres_tbl[] = { + { 40, 120}, + { 30, 135}, + { 20, 165}, + { 10, 230}, + { 00, 325}, + {-10, 445}, + {-20, 595}, +}; +#else +/* + * Note that the batres_vs_temp table must be strictly sorted by falling + * temperature values to work. + */ +#ifdef CONFIG_AB8500_9100_LI_ION_BATTERY +#define BATRES 180 +#else +#define BATRES 300 +#endif +static struct batres_vs_temp temp_to_batres_tbl[] = { + { 60, BATRES}, + { 30, BATRES}, + { 20, BATRES}, + { 10, BATRES}, + { 00, BATRES}, + {-10, BATRES}, + {-20, BATRES}, +}; +#endif +static const struct abx500_battery_type bat_type[] = { + [BATTERY_UNKNOWN] = { + /* First element always represent the UNKNOWN battery */ + .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, + .resis_high = 0, + .resis_low = 0, + .battery_resistance = 300, +#ifdef CONFIG_AB8500_9100_LI_ION_BATTERY + .charge_full_design = 2600, +#else + .charge_full_design = 612, +#endif + .nominal_voltage = 3700, +#ifdef CONFIG_AB8500_9100_LI_ION_BATTERY + .termination_vol = 4150, +#else + .termination_vol = 4050, +#endif + .termination_curr = 200, +#ifdef CONFIG_AB8500_9100_LI_ION_BATTERY + .recharge_vol = 4130, + .normal_cur_lvl = 520, + .normal_vol_lvl = 4200, +#else + .recharge_vol = 3990, + .normal_cur_lvl = 400, + .normal_vol_lvl = 4100, +#endif + .maint_a_cur_lvl = 400, + .maint_a_vol_lvl = 4050, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 400, + .maint_b_vol_lvl = 4000, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl), + .batres_tbl = temp_to_batres_tbl, + }, + +#ifdef CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 53407, + .resis_low = 12500, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_vol = 4130, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_A), + .r_to_t_tbl = temp_tbl_A, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_A), + .v_to_cap_tbl = cap_tbl_A, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl), + .batres_tbl = temp_to_batres_tbl, + + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 165418, + .resis_low = 82869, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_vol = 4130, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_B), + .r_to_t_tbl = temp_tbl_B, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_B), + .v_to_cap_tbl = cap_tbl_B, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl), + .batres_tbl = temp_to_batres_tbl, + }, +#else +/* + * These are the batteries that doesn't have an internal NTC resistor to measure + * its temperature. The temperature in this case is measure with a NTC placed + * near the battery but on the PCB. + */ + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 76000, + .resis_low = 53000, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4130, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl), + .batres_tbl = temp_to_batres_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 30000, + .resis_low = 10000, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4130, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl), + .batres_tbl = temp_to_batres_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 95000, + .resis_low = 76001, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4130, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl), + .batres_tbl = temp_to_batres_tbl, + }, +#endif +}; + +static char *ab8500_charger_supplied_to[] = { + "ab8500_chargalg", + "ab8500_fg", + "ab8500_btemp", +}; + +static char *ab8500_btemp_supplied_to[] = { + "ab8500_chargalg", + "ab8500_fg", +}; + +static char *ab8500_fg_supplied_to[] = { + "ab8500_chargalg", + "ab8500_usb", +}; + +static char *ab8500_chargalg_supplied_to[] = { + "ab8500_fg", +}; + +struct abx500_charger_platform_data ab8500_charger_plat_data = { + .supplied_to = ab8500_charger_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_charger_supplied_to), + .autopower_cfg = false, +}; + +struct abx500_btemp_platform_data ab8500_btemp_plat_data = { + .supplied_to = ab8500_btemp_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_btemp_supplied_to), +}; + +struct abx500_fg_platform_data ab8500_fg_plat_data = { + .supplied_to = ab8500_fg_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_fg_supplied_to), +}; + +struct abx500_chargalg_platform_data ab8500_chargalg_plat_data = { + .supplied_to = ab8500_chargalg_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_chargalg_supplied_to), +}; + +static struct ab8500_led_pwm leds_pwm_data[] = { + [0] = { + .pwm_id = 1, + .blink_en = 1, + }, + [1] = { + .pwm_id = 2, + .blink_en = 0, + }, + [2] = { + .pwm_id = 3, + .blink_en = 0, + }, +}; + +struct ab8500_pwmled_platform_data ab8500_pwmled_plat_data = { + .num_pwm = 3, + .leds = leds_pwm_data, +}; + +static const struct abx500_bm_capacity_levels cap_levels = { + .critical = 2, + .low = 10, + .normal = 70, + .high = 95, + .full = 100, +}; + +static const struct abx500_fg_parameters fg = { + .recovery_sleep_timer = 10, + .recovery_total_time = 100, + .init_timer = 1, + .init_discard_time = 5, + .init_total_time = 40, + .high_curr_time = 60, + .accu_charging = 30, + .accu_high_curr = 30, + .high_curr_threshold = 50, + .lowbat_threshold = 3100, + .battok_falling_th_sel0 = 2860, + .battok_raising_th_sel1 = 2860, + .user_cap_limit = 15, + .maint_thres = 97, +}; + +static const struct abx500_maxim_parameters maxi_params = { + .ena_maxi = true, + .chg_curr = 910, + .wait_cycles = 10, + .charger_curr_step = 100, +}; + +static const struct abx500_bm_charger_parameters chg = { + .usb_volt_max = 5500, + .usb_curr_max = 1500, + .ac_volt_max = 7500, + .ac_curr_max = 1500, +}; + +struct abx500_bm_data ab8500_bm_data = { + .temp_under = 3, + .temp_low = 8, + .temp_high = 43, + .temp_over = 48, + .main_safety_tmr_h = 4, + .temp_interval_chg = 20, + .temp_interval_nochg = 120, + .usb_safety_tmr_h = 4, + .bkup_bat_v = BUP_VCH_SEL_2P6V, + .bkup_bat_i = BUP_ICH_SEL_150UA, +#ifdef CONFIG_AB8500_9100_LI_ION_BATTERY + .no_maintenance = true, +#else + .no_maintenance = false, +#endif +#ifdef CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL + .adc_therm = ABx500_ADC_THERM_BATCTRL, +#else + .adc_therm = ABx500_ADC_THERM_BATTEMP, +#endif +#ifdef CONFIG_AB8500_9100_LI_ION_BATTERY + .chg_unknown_bat = true, +#else + .chg_unknown_bat = false, +#endif + .enable_overshoot = false, + .fg_res = 100, + .cap_levels = &cap_levels, + .bat_type = bat_type, + .n_btypes = ARRAY_SIZE(bat_type), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .gnd_lift_resistance = 34, + .maxi = &maxi_params, + .chg_params = &chg, + .fg_params = &fg, +}; diff --git a/arch/arm/mach-ux500/board-mop500-bm.h b/arch/arm/mach-ux500/board-mop500-bm.h new file mode 100644 index 00000000000..61d6bc71cc3 --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-bm.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U8500 board specific charger and battery initialization parameters. + * + * Author: Johan Palsson <johan.palsson@stericsson.com> for ST-Ericsson. + * Author: Johan Gardsmark <johan.gardsmark@stericsson.com> for ST-Ericsson. + * + */ + +#ifndef __BOARD_MOP500_BM_H +#define __BOARD_MOP500_BM_H + +#include <linux/mfd/abx500/ab8500-bm.h> + +extern struct abx500_charger_platform_data ab8500_charger_plat_data; +extern struct abx500_btemp_platform_data ab8500_btemp_plat_data; +extern struct abx500_fg_platform_data ab8500_fg_plat_data; +extern struct abx500_chargalg_platform_data ab8500_chargalg_plat_data; +extern struct abx500_bm_data ab8500_bm_data; +extern struct ab8500_pwmled_platform_data ab8500_pwmled_plat_data; + +#endif diff --git a/arch/arm/mach-ux500/board-mop500-regulators.c b/arch/arm/mach-ux500/board-mop500-regulators.c index 52426a42578..ffa0d4f9cdc 100644 --- a/arch/arm/mach-ux500/board-mop500-regulators.c +++ b/arch/arm/mach-ux500/board-mop500-regulators.c @@ -11,8 +11,52 @@ #include <linux/kernel.h> #include <linux/regulator/machine.h> #include <linux/regulator/ab8500.h> +#include <mach/id.h> /* to identify older boards for fixes */ +#include <asm/mach-types.h> #include "board-mop500-regulators.h" +#ifdef CONFIG_REGULATOR_FIXED_VOLTAGE +/* + * GPIO regulator controlled by the ab8500 GPIO16 + */ +static struct regulator_consumer_supply gpio_wlan_vbat_consumers[] = { + /* for cg2900 chip */ + REGULATOR_SUPPLY("vdd", "cg2900-uart.0"), + /* for cw1200 chip */ + REGULATOR_SUPPLY("vdd", "cw1200_wlan"), +}; + +struct regulator_init_data gpio_wlan_vbat_regulator = { + .constraints = { + .name = "WLAN-VBAT", + .min_uV = 3600000, + .max_uV = 3600000, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(gpio_wlan_vbat_consumers), + .consumer_supplies = gpio_wlan_vbat_consumers, +}; + +/* + * GPIO regulator controlled by the ab8500 GPIO26 + */ +static struct regulator_consumer_supply gpio_en_3v3_consumers[] = { + /* for LAN chip */ + REGULATOR_SUPPLY("vdd33a", "smsc911x.0"), +}; + +struct regulator_init_data gpio_en_3v3_regulator = { + .constraints = { + .name = "EN-3V3", + .min_uV = 3300000, + .max_uV = 3300000, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(gpio_en_3v3_consumers), + .consumer_supplies = gpio_en_3v3_consumers, +}; +#endif + /* * TPS61052 regulator */ @@ -38,21 +82,37 @@ struct regulator_init_data tps61052_regulator = { }; static struct regulator_consumer_supply ab8500_vaux1_consumers[] = { - /* External displays, connector on board 2v5 power supply */ - REGULATOR_SUPPLY("vaux12v5", "mcde.0"), + /* Main display, u8500 R3 uib */ + REGULATOR_SUPPLY("vddi", "mcde_disp_sony_acx424akp.0"), + /* Main display, u8500 uib and ST uib */ + REGULATOR_SUPPLY("vdd1", "samsung_s6d16d0.0"), + /* Secondary display, ST uib */ + REGULATOR_SUPPLY("vdd1", "samsung_s6d16d0.1"), /* SFH7741 proximity sensor */ REGULATOR_SUPPLY("vcc", "gpio-keys.0"), /* BH1780GLS ambient light sensor */ REGULATOR_SUPPLY("vcc", "2-0029"), /* lsm303dlh accelerometer */ - REGULATOR_SUPPLY("vdd", "3-0018"), + REGULATOR_SUPPLY("vdd", "lsm303dlh.0"), /* lsm303dlh magnetometer */ - REGULATOR_SUPPLY("vdd", "3-001e"), + REGULATOR_SUPPLY("vdd", "lsm303dlh.1"), /* Rohm BU21013 Touchscreen devices */ REGULATOR_SUPPLY("avdd", "3-005c"), REGULATOR_SUPPLY("avdd", "3-005d"), /* Synaptics RMI4 Touchscreen device */ REGULATOR_SUPPLY("vdd", "3-004b"), + /* L3G4200D Gyroscope device */ + REGULATOR_SUPPLY("vdd", "l3g4200d"), + /* Proximity and Hal sensor device */ + REGULATOR_SUPPLY("vdd", "sensor1p.0"), + /* Ambient light sensor device */ + REGULATOR_SUPPLY("vdd", "3-0029"), + /* Pressure sensor device */ + REGULATOR_SUPPLY("vdd", "2-005c"), + /* Cypress TrueTouch Touchscreen device */ + REGULATOR_SUPPLY("vcpin", "spi8.0"), + /* Camera device */ + REGULATOR_SUPPLY("vaux12v5", "mmio_camera"), }; static struct regulator_consumer_supply ab8500_vaux2_consumers[] = { @@ -60,6 +120,12 @@ static struct regulator_consumer_supply ab8500_vaux2_consumers[] = { REGULATOR_SUPPLY("vmmc", "sdi4"), /* AB8500 audio codec */ REGULATOR_SUPPLY("vcc-N2158", "ab8500-codec.0"), + /* AB8500 accessory detect 1 */ + REGULATOR_SUPPLY("vcc-N2158", "ab8500-acc-det.0"), + /* AB8500 Tv-out device */ + REGULATOR_SUPPLY("vcc-N2158", "mcde_tv_ab8500.4"), + /* AV8100 HDMI device */ + REGULATOR_SUPPLY("vcc-N2158", "av8100_hdmi.3"), }; static struct regulator_consumer_supply ab8500_vaux3_consumers[] = { @@ -72,26 +138,30 @@ static struct regulator_consumer_supply ab8500_vtvout_consumers[] = { REGULATOR_SUPPLY("vtvout", "ab8500-denc.0"), /* Internal general-purpose ADC */ REGULATOR_SUPPLY("vddadc", "ab8500-gpadc.0"), + /* ADC for charger */ + REGULATOR_SUPPLY("vddadc", "ab8500-charger.0"), + /* AB8500 Tv-out device */ + REGULATOR_SUPPLY("vtvout", "mcde_tv_ab8500.4"), }; static struct regulator_consumer_supply ab8500_vaud_consumers[] = { /* AB8500 audio-codec main supply */ - REGULATOR_SUPPLY("vaud", "ab8500-codec.0"), + REGULATOR_SUPPLY("v-audio", NULL), }; static struct regulator_consumer_supply ab8500_vamic1_consumers[] = { /* AB8500 audio-codec Mic1 supply */ - REGULATOR_SUPPLY("vamic1", "ab8500-codec.0"), + REGULATOR_SUPPLY("v-amic1", NULL), }; static struct regulator_consumer_supply ab8500_vamic2_consumers[] = { /* AB8500 audio-codec Mic2 supply */ - REGULATOR_SUPPLY("vamic2", "ab8500-codec.0"), + REGULATOR_SUPPLY("v-amic2", NULL), }; static struct regulator_consumer_supply ab8500_vdmic_consumers[] = { /* AB8500 audio-codec DMic supply */ - REGULATOR_SUPPLY("vdmic", "ab8500-codec.0"), + REGULATOR_SUPPLY("v-dmic", NULL), }; static struct regulator_consumer_supply ab8500_vintcore_consumers[] = { @@ -102,74 +172,85 @@ static struct regulator_consumer_supply ab8500_vintcore_consumers[] = { }; static struct regulator_consumer_supply ab8500_vana_consumers[] = { - /* External displays, connector on board, 1v8 power supply */ - REGULATOR_SUPPLY("vsmps2", "mcde.0"), + /* DB8500 DSI */ + REGULATOR_SUPPLY("vdddsi1v2", "mcde"), + /* DB8500 CSI */ + REGULATOR_SUPPLY("vddcsi1v2", "mmio_camera"), +}; + +static struct regulator_consumer_supply ab8500_sysclkreq_2_consumers[] = { + /* CG2900 device */ + REGULATOR_SUPPLY("gbf_1v8", "cg2900-uart.0"), +}; + +static struct regulator_consumer_supply ab8500_sysclkreq_4_consumers[] = { + /* CW1200 device */ + REGULATOR_SUPPLY("wlan_1v8", "cw1200_wlan.0"), }; /* ab8500 regulator register initialization */ -struct ab8500_regulator_reg_init -ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { +static struct ab8500_regulator_reg_init ab8500_reg_init[] = { /* * VanaRequestCtrl = HP/LP depending on VxRequest * VextSupply1RequestCtrl = HP/LP depending on VxRequest */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL2, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL2, 0xf0, 0x00), /* * VextSupply2RequestCtrl = HP/LP depending on VxRequest * VextSupply3RequestCtrl = HP/LP depending on VxRequest * Vaux1RequestCtrl = HP/LP depending on VxRequest * Vaux2RequestCtrl = HP/LP depending on VxRequest */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL3, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL3, 0xff, 0x00), /* * Vaux3RequestCtrl = HP/LP depending on VxRequest * SwHPReq = Control through SWValid disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL4, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL4, 0x07, 0x00), /* * VanaSysClkReq1HPValid = disabled * Vaux1SysClkReq1HPValid = disabled * Vaux2SysClkReq1HPValid = disabled * Vaux3SysClkReq1HPValid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID1, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID1, 0xe8, 0x00), /* * VextSupply1SysClkReq1HPValid = disabled * VextSupply2SysClkReq1HPValid = disabled * VextSupply3SysClkReq1HPValid = SysClkReq1 controlled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID2, 0x40), + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID2, 0x70, 0x40), /* * VanaHwHPReq1Valid = disabled * Vaux1HwHPreq1Valid = disabled * Vaux2HwHPReq1Valid = disabled * Vaux3HwHPReqValid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID1, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID1, 0xe8, 0x00), /* * VextSupply1HwHPReq1Valid = disabled * VextSupply2HwHPReq1Valid = disabled * VextSupply3HwHPReq1Valid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID2, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID2, 0x07, 0x00), /* * VanaHwHPReq2Valid = disabled * Vaux1HwHPReq2Valid = disabled * Vaux2HwHPReq2Valid = disabled * Vaux3HwHPReq2Valid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID1, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID1, 0xe8, 0x00), /* * VextSupply1HwHPReq2Valid = disabled * VextSupply2HwHPReq2Valid = disabled * VextSupply3HwHPReq2Valid = HWReq2 controlled */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID2, 0x04), + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID2, 0x07, 0x04), /* * VanaSwHPReqValid = disabled * Vaux1SwHPReqValid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID1, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID1, 0xa0, 0x00), /* * Vaux2SwHPReqValid = disabled * Vaux3SwHPReqValid = disabled @@ -177,7 +258,7 @@ ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { * VextSupply2SwHPReqValid = disabled * VextSupply3SwHPReqValid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID2, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID2, 0x1f, 0x00), /* * SysClkReq2Valid1 = SysClkReq2 controlled * SysClkReq3Valid1 = disabled @@ -187,7 +268,7 @@ ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { * SysClkReq7Valid1 = disabled * SysClkReq8Valid1 = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID1, 0x2a), + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID1, 0xfe, 0x2a), /* * SysClkReq2Valid2 = disabled * SysClkReq3Valid2 = disabled @@ -197,7 +278,7 @@ ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { * SysClkReq7Valid2 = disabled * SysClkReq8Valid2 = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID2, 0x20), + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID2, 0xfe, 0x20), /* * VTVoutEna = disabled * Vintcore12Ena = disabled @@ -205,66 +286,62 @@ ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { * Vintcore12LP = inactive (HP) * VTVoutLP = inactive (HP) */ - INIT_REGULATOR_REGISTER(AB8500_REGUMISC1, 0x10), + INIT_REGULATOR_REGISTER(AB8500_REGUMISC1, 0xfe, 0x10), /* * VaudioEna = disabled * VdmicEna = disabled * Vamic1Ena = disabled * Vamic2Ena = disabled */ - INIT_REGULATOR_REGISTER(AB8500_VAUDIOSUPPLY, 0x00), + INIT_REGULATOR_REGISTER(AB8500_VAUDIOSUPPLY, 0x1e, 0x00), /* * Vamic1_dzout = high-Z when Vamic1 is disabled * Vamic2_dzout = high-Z when Vamic2 is disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRL1VAMIC, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUCTRL1VAMIC, 0x03, 0x00), /* - * VPll = Hw controlled + * VPll = Hw controlled (NOTE! PRCMU bits) * VanaRegu = force off */ - INIT_REGULATOR_REGISTER(AB8500_VPLLVANAREGU, 0x02), + INIT_REGULATOR_REGISTER(AB8500_VPLLVANAREGU, 0x0f, 0x02), /* * VrefDDREna = disabled * VrefDDRSleepMode = inactive (no pulldown) */ - INIT_REGULATOR_REGISTER(AB8500_VREFDDR, 0x00), + INIT_REGULATOR_REGISTER(AB8500_VREFDDR, 0x03, 0x00), /* - * VextSupply1Regu = HW control - * VextSupply2Regu = HW control - * VextSupply3Regu = HW control + * VextSupply1Regu = force LP + * VextSupply2Regu = force OFF + * VextSupply3Regu = force HP (-> STBB2=LP and TPS=LP) * ExtSupply2Bypass = ExtSupply12LPn ball is 0 when Ena is 0 * ExtSupply3Bypass = ExtSupply3LPn ball is 0 when Ena is 0 */ - INIT_REGULATOR_REGISTER(AB8500_EXTSUPPLYREGU, 0x2a), + INIT_REGULATOR_REGISTER(AB8500_EXTSUPPLYREGU, 0xff, 0x13), /* * Vaux1Regu = force HP * Vaux2Regu = force off */ - INIT_REGULATOR_REGISTER(AB8500_VAUX12REGU, 0x01), - /* - * Vaux3regu = force off - */ - INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3REGU, 0x00), + INIT_REGULATOR_REGISTER(AB8500_VAUX12REGU, 0x0f, 0x01), /* - * Vsmps1 = 1.15V + * Vaux3Regu = force off */ - INIT_REGULATOR_REGISTER(AB8500_VSMPS1SEL1, 0x24), + INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3REGU, 0x03, 0x00), /* - * Vaux1Sel = 2.5 V + * Vaux1Sel = 2.8 V */ - INIT_REGULATOR_REGISTER(AB8500_VAUX1SEL, 0x08), + INIT_REGULATOR_REGISTER(AB8500_VAUX1SEL, 0x0f, 0x0C), /* * Vaux2Sel = 2.9 V */ - INIT_REGULATOR_REGISTER(AB8500_VAUX2SEL, 0x0d), + INIT_REGULATOR_REGISTER(AB8500_VAUX2SEL, 0x0f, 0x0d), /* * Vaux3Sel = 2.91 V */ - INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3SEL, 0x07), + INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3SEL, 0x07, 0x07), /* * VextSupply12LP = disabled (no LP) */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRL2SPARE, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUCTRL2SPARE, 0x01, 0x00), /* * Vaux1Disch = short discharge time * Vaux2Disch = short discharge time @@ -273,23 +350,24 @@ ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { * VTVoutDisch = short discharge time * VaudioDisch = short discharge time */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH, 0xfc, 0x00), /* * VanaDisch = short discharge time * VdmicPullDownEna = pulldown disabled when Vdmic is disabled * VdmicDisch = short discharge time */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH2, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH2, 0x16, 0x00), }; /* AB8500 regulators */ -struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { +static struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { /* supplies to the display/camera */ [AB8500_LDO_AUX1] = { + .supply_regulator = "ab8500-ext-supply3", .constraints = { .name = "V-DISPLAY", - .min_uV = 2500000, - .max_uV = 2900000, + .min_uV = 2800000, + .max_uV = 3300000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, .boot_on = 1, /* display is on at boot */ @@ -306,24 +384,32 @@ struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { }, /* supplies to the on-board eMMC */ [AB8500_LDO_AUX2] = { + .supply_regulator = "ab8500-ext-supply3", .constraints = { .name = "V-eMMC1", .min_uV = 1100000, .max_uV = 3300000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | - REGULATOR_CHANGE_STATUS, + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, }, .num_consumer_supplies = ARRAY_SIZE(ab8500_vaux2_consumers), .consumer_supplies = ab8500_vaux2_consumers, }, /* supply for VAUX3, supplies to SDcard slots */ [AB8500_LDO_AUX3] = { + .supply_regulator = "ab8500-ext-supply3", .constraints = { .name = "V-MMC-SD", .min_uV = 1100000, .max_uV = 3300000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | - REGULATOR_CHANGE_STATUS, + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, }, .num_consumer_supplies = ARRAY_SIZE(ab8500_vaux3_consumers), .consumer_supplies = ab8500_vaux3_consumers, @@ -377,18 +463,167 @@ struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { [AB8500_LDO_INTCORE] = { .constraints = { .name = "V-INTCORE", - .valid_ops_mask = REGULATOR_CHANGE_STATUS, + .min_uV = 1250000, + .max_uV = 1350000, + .input_uV = 1800000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE | + REGULATOR_CHANGE_DRMS, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, }, .num_consumer_supplies = ARRAY_SIZE(ab8500_vintcore_consumers), .consumer_supplies = ab8500_vintcore_consumers, }, - /* supply for U8500 CSI/DSI, VANA LDO */ + /* supply for U8500 CSI-DSI, VANA LDO */ [AB8500_LDO_ANA] = { .constraints = { - .name = "V-CSI/DSI", + .name = "V-CSI-DSI", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, .num_consumer_supplies = ARRAY_SIZE(ab8500_vana_consumers), .consumer_supplies = ab8500_vana_consumers, }, + /* sysclkreq 2 pin */ + [AB8500_SYSCLKREQ_2] = { + .constraints = { + .name = "V-SYSCLKREQ-2", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = + ARRAY_SIZE(ab8500_sysclkreq_2_consumers), + .consumer_supplies = ab8500_sysclkreq_2_consumers, + }, + /* sysclkreq 4 pin */ + [AB8500_SYSCLKREQ_4] = { + .constraints = { + .name = "V-SYSCLKREQ-4", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = + ARRAY_SIZE(ab8500_sysclkreq_4_consumers), + .consumer_supplies = ab8500_sysclkreq_4_consumers, + }, }; + +/* supply for VextSupply3 */ +static struct regulator_consumer_supply ab8500_ext_supply3_consumers[] = { + /* SIM supply for 3 V SIM cards */ + REGULATOR_SUPPLY("vinvsim", "sim-detect.0"), +}; + +/* extended configuration for VextSupply2, only used for HREFP_V20 boards */ +static struct ab8500_ext_regulator_cfg ab8500_ext_supply2 = { + .hwreq = true, +}; + +/* + * AB8500 external regulators + */ +static struct regulator_init_data ab8500_ext_regulators[] = { + /* fixed Vbat supplies VSMPS1_EXT_1V8 */ + [AB8500_EXT_SUPPLY1] = { + .constraints = { + .name = "ab8500-ext-supply1", + .min_uV = 1800000, + .max_uV = 1800000, + .initial_mode = REGULATOR_MODE_IDLE, + .boot_on = 1, + .always_on = 1, + }, + }, + /* fixed Vbat supplies VSMPS2_EXT_1V36 and VSMPS5_EXT_1V15 */ + [AB8500_EXT_SUPPLY2] = { + .constraints = { + .name = "ab8500-ext-supply2", + .min_uV = 1360000, + .max_uV = 1360000, + }, + }, + /* fixed Vbat supplies VSMPS3_EXT_3V4 and VSMPS4_EXT_3V4 */ + [AB8500_EXT_SUPPLY3] = { + .constraints = { + .name = "ab8500-ext-supply3", + .min_uV = 3400000, + .max_uV = 3400000, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + .boot_on = 1, + }, + .num_consumer_supplies = + ARRAY_SIZE(ab8500_ext_supply3_consumers), + .consumer_supplies = ab8500_ext_supply3_consumers, + }, +}; + +struct ab8500_regulator_platform_data ab8500_regulator_plat_data = { + .reg_init = ab8500_reg_init, + .num_reg_init = ARRAY_SIZE(ab8500_reg_init), + .regulator = ab8500_regulators, + .num_regulator = ARRAY_SIZE(ab8500_regulators), + .ext_regulator = ab8500_ext_regulators, + .num_ext_regulator = ARRAY_SIZE(ab8500_ext_regulators), +}; + +static void ab8500_modify_reg_init(int id, u8 mask, u8 value) +{ + int i; + + for (i = ARRAY_SIZE(ab8500_reg_init) - 1; i >= 0; i--) { + if (ab8500_reg_init[i].id == id) { + u8 initval = ab8500_reg_init[i].value; + initval = (initval & ~mask) | (value & mask); + ab8500_reg_init[i].value = initval; + + BUG_ON(mask & ~ab8500_reg_init[i].mask); + return; + } + } + + BUG_ON(1); +} + +void mop500_regulator_init(void) +{ + struct regulator_init_data *regulator; + + /* + * Temporarily turn on Vaux2 on 8520 machine + */ + if (machine_is_u8520()) { + /* Vaux2 initialized to be on */ + ab8500_modify_reg_init(AB8500_VAUX12REGU, 0x0f, 0x05); + + /* Vaux2 always on */ + regulator = &ab8500_ext_regulators[AB8500_LDO_AUX2]; + regulator->constraints.always_on = 1; + } + + /* + * Handle AB8500_EXT_SUPPLY2 on HREFP_V20_V50 boards (do it for + * all HREFP_V20 boards) + */ + if (cpu_is_u8500v20()) { + /* VextSupply2RequestCtrl = HP/OFF depending on VxRequest */ + ab8500_modify_reg_init(AB8500_REGUREQUESTCTRL3, 0x01, 0x01); + + /* VextSupply2SysClkReq1HPValid = SysClkReq1 controlled */ + ab8500_modify_reg_init(AB8500_REGUSYSCLKREQ1HPVALID2, + 0x20, 0x20); + + /* VextSupply2 = force HP at initialization */ + ab8500_modify_reg_init(AB8500_EXTSUPPLYREGU, 0x0c, 0x04); + + /* enable VextSupply2 during platform active */ + regulator = &ab8500_ext_regulators[AB8500_EXT_SUPPLY2]; + regulator->constraints.always_on = 1; + + /* disable VextSupply2 in suspend */ + regulator = &ab8500_ext_regulators[AB8500_EXT_SUPPLY2]; + regulator->constraints.state_mem.disabled = 1; + regulator->constraints.state_standby.disabled = 1; + + /* enable VextSupply2 HW control (used in suspend) */ + regulator->driver_data = (void *)&ab8500_ext_supply2; + } +} diff --git a/arch/arm/mach-ux500/board-mop500-regulators.h b/arch/arm/mach-ux500/board-mop500-regulators.h index 94992158d96..b5fc81a3649 100644 --- a/arch/arm/mach-ux500/board-mop500-regulators.h +++ b/arch/arm/mach-ux500/board-mop500-regulators.h @@ -14,9 +14,11 @@ #include <linux/regulator/machine.h> #include <linux/regulator/ab8500.h> -extern struct ab8500_regulator_reg_init -ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS]; -extern struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS]; +extern struct ab8500_regulator_platform_data ab8500_regulator_plat_data; extern struct regulator_init_data tps61052_regulator; +extern struct regulator_init_data gpio_wlan_vbat_regulator; +extern struct regulator_init_data gpio_en_3v3_regulator; + +void mop500_regulator_init(void); #endif diff --git a/arch/arm/mach-ux500/board-u5500-bm.c b/arch/arm/mach-ux500/board-u5500-bm.c new file mode 100644 index 00000000000..333f2cb8563 --- /dev/null +++ b/arch/arm/mach-ux500/board-u5500-bm.c @@ -0,0 +1,497 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U5500 board specific charger and battery initialization parameters. + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + */ + +#include <linux/power_supply.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500-bm.h> +#include "board-u5500-bm.h" + +#ifdef CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL +/* + * These are the defined batteries that uses a NTC and ID resistor placed + * inside of the battery pack. + * Note that the abx500_res_to_temp table must be strictly sorted by falling + * resistance values to work. + */ +static struct abx500_res_to_temp temp_tbl_type1[] = { + {-20, 67400}, + { 0, 49200}, + { 5, 44200}, + { 10, 39400}, + { 15, 35000}, + { 20, 31000}, + { 25, 27400}, + { 30, 24300}, + { 35, 21700}, + { 40, 19400}, + { 45, 17500}, + { 50, 15900}, + { 55, 14600}, + { 60, 13500}, + { 65, 12500}, + { 70, 11800}, + {100, 9200}, +}; + +static struct abx500_res_to_temp temp_tbl_type2[] = { + {-20, 180700}, + { 0, 160000}, + { 5, 152700}, + { 10, 144900}, + { 15, 136800}, + { 20, 128700}, + { 25, 121000}, + { 30, 113800}, + { 35, 107300}, + { 40, 101500}, + { 45, 96500}, + { 50, 92200}, + { 55, 88600}, + { 60, 85600}, + { 65, 83000}, + { 70, 80900}, + {100, 73900}, +}; + +static struct abx500_res_to_temp temp_tbl_A[] = { + {-5, 53407}, + { 0, 48594}, + { 5, 43804}, + {10, 39188}, + {15, 34870}, + {20, 30933}, + {25, 27422}, + {30, 24347}, + {35, 21694}, + {40, 19431}, + {45, 17517}, + {50, 15908}, + {55, 14561}, + {60, 13437}, + {65, 12500}, +}; + +static struct abx500_res_to_temp temp_tbl_B[] = { + {-5, 165418}, + { 0, 159024}, + { 5, 151921}, + {10, 144300}, + {15, 136424}, + {20, 128565}, + {25, 120978}, + {30, 113875}, + {35, 107397}, + {40, 101629}, + {45, 96592}, + {50, 92253}, + {55, 88569}, + {60, 85461}, + {65, 82869}, +}; + +static struct abx500_v_to_cap cap_tbl_type1[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; + +static struct abx500_v_to_cap cap_tbl_A[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; +static struct abx500_v_to_cap cap_tbl_B[] = { + {4161, 100}, + {4124, 98}, + {4044, 90}, + {4003, 85}, + {3966, 80}, + {3933, 75}, + {3888, 67}, + {3849, 60}, + {3813, 55}, + {3787, 47}, + {3772, 30}, + {3751, 25}, + {3718, 20}, + {3681, 16}, + {3660, 14}, + {3589, 10}, + {3546, 7}, + {3495, 4}, + {3404, 2}, + {3250, 0}, +}; +#endif +static struct abx500_v_to_cap cap_tbl[] = { + {4186, 100}, + {4163, 99}, + {4114, 95}, + {4068, 90}, + {3990, 80}, + {3926, 70}, + {3898, 65}, + {3866, 60}, + {3833, 55}, + {3812, 50}, + {3787, 40}, + {3768, 30}, + {3747, 25}, + {3730, 20}, + {3705, 15}, + {3699, 14}, + {3684, 12}, + {3672, 9}, + {3657, 7}, + {3638, 6}, + {3556, 4}, + {3424, 2}, + {3317, 1}, + {3094, 0}, +}; + +/* + * Note that the abx500_res_to_temp table must be strictly sorted by falling + * resistance values to work. + */ +static struct abx500_res_to_temp temp_tbl[] = { + {-5, 214834}, + { 0, 162943}, + { 5, 124820}, + {10, 96520}, + {15, 75306}, + {20, 59254}, + {25, 47000}, + {30, 37566}, + {35, 30245}, + {40, 24520}, + {45, 20010}, + {50, 16432}, + {55, 13576}, + {60, 11280}, + {65, 9425}, +}; + +static const struct abx500_battery_type bat_type[] = { + [BATTERY_UNKNOWN] = { + /* First element always represent the UNKNOWN battery */ + .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, + .resis_high = 0, + .resis_low = 0, + .battery_resistance = 300, + .charge_full_design = 612, + .nominal_voltage = 3700, + .termination_vol = 4050, + .termination_curr = 200, + .recharge_vol = 3990, + .normal_cur_lvl = 400, + .normal_vol_lvl = 4100, + .maint_a_cur_lvl = 400, + .maint_a_vol_lvl = 4050, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 400, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + +#ifdef CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 70000, + .resis_low = 8200, + .battery_resistance = 300, + .charge_full_design = 1500, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_type1), + .r_to_t_tbl = temp_tbl_type1, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_type1), + .v_to_cap_tbl = cap_tbl_type1, + + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 165418, + .resis_low = 82869, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_B), + .r_to_t_tbl = temp_tbl_B, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_B), + .v_to_cap_tbl = cap_tbl_B, + }, +#else +/* + * These are the batteries that doesn't have an internal NTC resistor to measure + * its temperature. The temperature in this case is measure with a NTC placed + * near the battery but on the PCB. + */ + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 76000, + .resis_low = 53000, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 30000, + .resis_low = 10000, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 95000, + .resis_low = 76001, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, +#endif +}; + +static char *ab5500_charger_supplied_to[] = { + "abx500_chargalg", + "ab5500_fg", + "ab5500_btemp", +}; + +static char *ab5500_btemp_supplied_to[] = { + "abx500_chargalg", + "ab5500_fg", +}; + +static char *ab5500_fg_supplied_to[] = { + "abx500_chargalg", +}; + +static char *abx500_chargalg_supplied_to[] = { + "ab5500_fg", +}; + +struct abx500_charger_platform_data ab5500_charger_plat_data = { + .supplied_to = ab5500_charger_supplied_to, + .num_supplicants = ARRAY_SIZE(ab5500_charger_supplied_to), +}; + +struct abx500_btemp_platform_data ab5500_btemp_plat_data = { + .supplied_to = ab5500_btemp_supplied_to, + .num_supplicants = ARRAY_SIZE(ab5500_btemp_supplied_to), +}; + +struct abx500_fg_platform_data ab5500_fg_plat_data = { + .supplied_to = ab5500_fg_supplied_to, + .num_supplicants = ARRAY_SIZE(ab5500_fg_supplied_to), +}; + +struct abx500_chargalg_platform_data abx500_chargalg_plat_data = { + .supplied_to = abx500_chargalg_supplied_to, + .num_supplicants = ARRAY_SIZE(abx500_chargalg_supplied_to), +}; + +static const struct abx500_bm_capacity_levels cap_levels = { + .critical = 2, + .low = 10, + .normal = 70, + .high = 95, + .full = 100, +}; + +static const struct abx500_fg_parameters fg = { + .recovery_sleep_timer = 10, + .recovery_total_time = 100, + .init_timer = 1, + .init_discard_time = 5, + .init_total_time = 40, + .high_curr_time = 60, + .accu_charging = 30, + .accu_high_curr = 30, + .high_curr_threshold = 50, + .lowbat_threshold = 3560, + .overbat_threshold = 4400, +}; + +static const struct abx500_maxim_parameters maxi_params = { + .ena_maxi = true, + .chg_curr = 910, + .wait_cycles = 10, + .charger_curr_step = 100, +}; + +static const struct abx500_bm_charger_parameters chg = { + .usb_volt_max = 5500, + .usb_curr_max = 1500, + .ac_volt_max = 7500, + .ac_curr_max = 1500, +}; + +struct abx500_bm_data ab5500_bm_data = { + .temp_under = 3, + .temp_low = 8, + /* TODO: Need to verify the temp values */ + .temp_high = 155, + .temp_over = 160, + .main_safety_tmr_h = 4, + .usb_safety_tmr_h = 4, + .bkup_bat_v = 0x00, + .bkup_bat_i = 0x00, + .no_maintenance = true, +#ifdef CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL + .adc_therm = ABx500_ADC_THERM_BATCTRL, +#else + .adc_therm = ABx500_ADC_THERM_BATTEMP, +#endif + .chg_unknown_bat = false, + .enable_overshoot = false, + .auto_trig = true, + .fg_res = 200, + .cap_levels = &cap_levels, + .bat_type = bat_type, + .n_btypes = ARRAY_SIZE(bat_type), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .maxi = &maxi_params, + .chg_params = &chg, + .fg_params = &fg, +}; + +/* ab5500 energy management platform data */ +struct abx500_bm_plat_data abx500_bm_pt_data = { + .battery = &ab5500_bm_data, + .charger = &ab5500_charger_plat_data, + .btemp = &ab5500_btemp_plat_data, + .fg = &ab5500_fg_plat_data, + .chargalg = &abx500_chargalg_plat_data, +}; diff --git a/arch/arm/mach-ux500/board-u5500-bm.h b/arch/arm/mach-ux500/board-u5500-bm.h new file mode 100644 index 00000000000..a6346905911 --- /dev/null +++ b/arch/arm/mach-ux500/board-u5500-bm.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U5500 board specific charger and battery initialization parameters. + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + */ + +#ifndef __BOARD_U5500_BM_H +#define __BOARD_U5500_BM_H + +#include <linux/mfd/abx500/ab5500-bm.h> + +extern struct abx500_charger_platform_data ab5500_charger_plat_data; +extern struct abx500_btemp_platform_data ab5500_btemp_plat_data; +extern struct abx500_fg_platform_data ab5500_fg_plat_data; +extern struct abx500_chargalg_platform_data abx500_chargalg_plat_data; +extern struct abx500_bm_data ab5500_bm_data; +extern struct abx500_bm_plat_data abx500_bm_pt_data; + +#endif diff --git a/arch/arm/mach-ux500/board-u5500-regulators.c b/arch/arm/mach-ux500/board-u5500-regulators.c new file mode 100644 index 00000000000..9e343259e53 --- /dev/null +++ b/arch/arm/mach-ux500/board-u5500-regulators.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/regulator/fixed.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/ab5500.h> + +#include "regulator-u5500.h" +#include "board-u5500.h" + +/* + * AB5500 + */ + +static struct regulator_consumer_supply ab5500_ldo_g_consumers[] = { + REGULATOR_SUPPLY("vmmc", "sdi1"), +}; + +static struct regulator_consumer_supply ab5500_ldo_h_consumers[] = { + REGULATOR_SUPPLY("vddi", "mcde_disp_sony_acx424akp.0"), + REGULATOR_SUPPLY("vdd", "1-004b"), /* Synaptics */ + REGULATOR_SUPPLY("vin", "2-0036"), /* LM3530 */ + REGULATOR_SUPPLY("vcpin", "spi1.0"), + REGULATOR_SUPPLY("v-ana", "mmio_camera"), + REGULATOR_SUPPLY("vdd", "lsm303dlh.0"), + REGULATOR_SUPPLY("vdd", "lsm303dlh.1"), +}; + +static struct regulator_consumer_supply ab5500_ldo_k_consumers[] = { + REGULATOR_SUPPLY("v-mmio-camera", "mmio_camera"), +}; + +static struct regulator_consumer_supply ab5500_ldo_h_consumers_pre_r3a[] = { + REGULATOR_SUPPLY("vddi", "mcde_disp_sony_acx424akp.0"), + REGULATOR_SUPPLY("vdd", "1-004b"), /* Synaptics */ + REGULATOR_SUPPLY("vin", "2-0036"), /* LM3530 */ + REGULATOR_SUPPLY("vcpin", "spi1.0"), + REGULATOR_SUPPLY("v-ana", "mmio_camera"), +}; + +static struct regulator_consumer_supply ab5500_ldo_k_consumers_pre_r3a[] = { + REGULATOR_SUPPLY("vdd", "lsm303dlh.0"), + REGULATOR_SUPPLY("vdd", "lsm303dlh.1"), + REGULATOR_SUPPLY("v-mmio-camera", "mmio_camera"), +}; + +static struct regulator_consumer_supply ab5500_ldo_l_consumers[] = { + REGULATOR_SUPPLY("vmmc", "sdi0"), + REGULATOR_SUPPLY("vmmc", "sdi2"), +}; + +static struct regulator_consumer_supply ab5500_ldo_vdigmic_consumers[] = { + REGULATOR_SUPPLY("vdigmic", "ab5500-codec.0"), +}; + +static struct regulator_consumer_supply ab5500_ldo_sim_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.5"), +}; + +static struct regulator_consumer_supply ab5500_bias2_consumers[] = { + REGULATOR_SUPPLY("v-amic", NULL), +}; + +static struct regulator_init_data +ab5500_regulator_init_data[AB5500_NUM_REGULATORS] = { + /* SD Card */ + [AB5500_LDO_G] = { + .constraints = { + .min_uV = 1200000, + .max_uV = 2910000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, + }, + .consumer_supplies = ab5500_ldo_g_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_ldo_g_consumers), + }, + /* Display */ + [AB5500_LDO_H] = { + .constraints = { + .min_uV = 2790000, + .max_uV = 2790000, + .apply_uV = 1, + .boot_on = 1, /* display on during boot */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = ab5500_ldo_h_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_ldo_h_consumers), + }, + /* Camera */ + [AB5500_LDO_K] = { + .constraints = { + .min_uV = 2790000, + .max_uV = 2790000, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = ab5500_ldo_k_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_ldo_k_consumers), + }, + /* External eMMC */ + [AB5500_LDO_L] = { + .constraints = { + .min_uV = 1200000, + .max_uV = 2910000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, + }, + .consumer_supplies = ab5500_ldo_l_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_ldo_l_consumers), + }, + [AB5500_LDO_VDIGMIC] = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = ab5500_ldo_vdigmic_consumers, + .num_consumer_supplies = + ARRAY_SIZE(ab5500_ldo_vdigmic_consumers), + }, + [AB5500_LDO_SIM] = { + .constraints = { + .min_uV = 1875000, + .max_uV = 2900000, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = ab5500_ldo_sim_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_ldo_sim_consumers), + }, + [AB5500_BIAS2] = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = ab5500_bias2_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_bias2_consumers), + }, +}; + +static struct ab5500_regulator_data +ab5500_regulator_data[AB5500_NUM_REGULATORS] = { + [AB5500_LDO_H] = { + /* + * The sub camera on the dev boards needs both supplies to be + * on to avoid high leakage. + */ + .off_is_lowpower = true, + }, +}; + +struct ab5500_regulator_platform_data u5500_ab5500_regulator_data = { + .regulator = ab5500_regulator_init_data, + .data = ab5500_regulator_data, + .num_regulator = ARRAY_SIZE(ab5500_regulator_init_data), +}; + + +static void __init u5500_regulators_init_debug(void) +{ + const char data[] = "debug"; + int i; + + for (i = 0; i < 6; i++) + platform_device_register_data(NULL, "reg-virt-consumer", i, + data, sizeof(data)); +} + +static struct regulator_consumer_supply u5500_vio_consumers[] = { + REGULATOR_SUPPLY("gbf_1v8", "cg2900-uart.0"), +}; + +static struct regulator_init_data u5500_vio_init_data = { + .constraints.always_on = 1, + .consumer_supplies = u5500_vio_consumers, + .num_consumer_supplies = ARRAY_SIZE(u5500_vio_consumers), +}; + +static struct fixed_voltage_config u5500_vio_pdata __initdata = { + .supply_name = "vio_1v8", + .microvolts = 1800000, + .init_data = &u5500_vio_init_data, + .gpio = -EINVAL, +}; + +void __init u5500_regulators_init(void) +{ + if (u5500_board_is_pre_r3a()) { + struct regulator_init_data *rid = ab5500_regulator_init_data; + + rid[AB5500_LDO_K].consumer_supplies + = ab5500_ldo_k_consumers_pre_r3a; + rid[AB5500_LDO_K].num_consumer_supplies + = ARRAY_SIZE(ab5500_ldo_k_consumers_pre_r3a); + + rid[AB5500_LDO_H].consumer_supplies + = ab5500_ldo_h_consumers_pre_r3a; + rid[AB5500_LDO_H].num_consumer_supplies + = ARRAY_SIZE(ab5500_ldo_h_consumers_pre_r3a); + } + + u5500_regulators_init_debug(); + + platform_device_register_data(NULL, "reg-fixed-voltage", -1, + &u5500_vio_pdata, + sizeof(u5500_vio_pdata)); + + regulator_has_full_constraints(); +} diff --git a/arch/arm/mach-ux500/clock-db5500.c b/arch/arm/mach-ux500/clock-db5500.c new file mode 100644 index 00000000000..aa61b1129e6 --- /dev/null +++ b/arch/arm/mach-ux500/clock-db5500.c @@ -0,0 +1,745 @@ +/* + * Copyright (C) 2009 ST-Ericsson SA + * Copyright (C) 2009 STMicroelectronics + * + * 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/init.h> +#include <linux/list.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/workqueue.h> +#include <linux/gpio/nomadik.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <plat/pincfg.h> + +#include <mach/hardware.h> + +#include "clock.h" +#include "pins-db5500.h" + +static DEFINE_MUTEX(sysclk_mutex); +static DEFINE_MUTEX(pll_mutex); + +/* SysClk operations. */ +static int sysclk_enable(struct clk *clk) +{ + return prcmu_request_clock(PRCMU_SYSCLK, true); +} + +static void sysclk_disable(struct clk *clk) +{ + + prcmu_request_clock(PRCMU_SYSCLK, false); + return; +} + +static struct clkops sysclk_ops = { + .enable = sysclk_enable, + .disable = sysclk_disable, +}; + +static int rtc_clk_enable(struct clk *clk) +{ + return ab5500_clock_rtc_enable(clk->cg_sel, true); +} + +static void rtc_clk_disable(struct clk *clk) +{ + int ret = ab5500_clock_rtc_enable(clk->cg_sel, false); + + if (ret) + pr_err("clock: %s failed to disable: %d\n", clk->name, ret); +} + +static struct clkops rtc_clk_ops = { + .enable = rtc_clk_enable, + .disable = rtc_clk_disable, +}; + +static pin_cfg_t clkout0_pins[] = { + GPIO161_CLKOUT_0 | PIN_OUTPUT_LOW, +}; + +static pin_cfg_t clkout1_pins[] = { + GPIO162_CLKOUT_1 | PIN_OUTPUT_LOW, +}; + +static int clkout0_enable(struct clk *clk) +{ + return nmk_config_pins(clkout0_pins, ARRAY_SIZE(clkout0_pins)); +} + +static void clkout0_disable(struct clk *clk) +{ + int r; + + r = nmk_config_pins_sleep(clkout0_pins, ARRAY_SIZE(clkout0_pins)); + if (!r) + return; + + pr_err("clock: failed to disable %s.\n", clk->name); +} + +static int clkout1_enable(struct clk *clk) +{ + return nmk_config_pins(clkout1_pins, ARRAY_SIZE(clkout0_pins)); +} + +static void clkout1_disable(struct clk *clk) +{ + int r; + + r = nmk_config_pins_sleep(clkout1_pins, ARRAY_SIZE(clkout1_pins)); + if (!r) + return; + + pr_err("clock: failed to disable %s.\n", clk->name); +} + +static struct clkops clkout0_ops = { + .enable = clkout0_enable, + .disable = clkout0_disable, +}; + +static struct clkops clkout1_ops = { + .enable = clkout1_enable, + .disable = clkout1_disable, +}; + +#define PRCM_CLKOCR2 0x58C +#define PRCM_CLKOCR2_REFCLK (1 << 0) +#define PRCM_CLKOCR2_STATIC0 (1 << 2) + +static int clkout2_enable(struct clk *clk) +{ + prcmu_write(PRCM_CLKOCR2, PRCM_CLKOCR2_REFCLK); + return 0; +} + +static void clkout2_disable(struct clk *clk) +{ + prcmu_write(PRCM_CLKOCR2, PRCM_CLKOCR2_STATIC0); +} + +static struct clkops clkout2_ops = { + .enable = clkout2_enable, + .disable = clkout2_disable, +}; + +#define DEF_PER1_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U5500_CLKRST1_BASE, _cg_bit, &per1clk) +#define DEF_PER2_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U5500_CLKRST2_BASE, _cg_bit, &per2clk) +#define DEF_PER3_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U5500_CLKRST3_BASE, _cg_bit, &per3clk) +#define DEF_PER5_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U5500_CLKRST5_BASE, _cg_bit, &per5clk) +#define DEF_PER6_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U5500_CLKRST6_BASE, _cg_bit, &per6clk) + +#define DEF_PER1_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U5500_CLKRST1_BASE, _cg_bit, _parent, &per1clk) +#define DEF_PER2_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U5500_CLKRST2_BASE, _cg_bit, _parent, &per2clk) +#define DEF_PER3_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U5500_CLKRST3_BASE, _cg_bit, _parent, &per3clk) +#define DEF_PER5_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U5500_CLKRST5_BASE, _cg_bit, _parent, &per5clk) +#define DEF_PER6_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U5500_CLKRST6_BASE, _cg_bit, _parent, &per6clk) + +/* Clock sources. */ + +static struct clk soc0_pll = { + .name = "soc0_pll", + .ops = &prcmu_clk_ops, + .mutex = &pll_mutex, + .cg_sel = PRCMU_PLLSOC0, +}; + +static struct clk soc1_pll = { + .name = "soc1_pll", + .ops = &prcmu_clk_ops, + .mutex = &pll_mutex, + .cg_sel = PRCMU_PLLSOC1, +}; + +static struct clk ddr_pll = { + .name = "ddr_pll", + .ops = &prcmu_clk_ops, + .mutex = &pll_mutex, + .cg_sel = PRCMU_PLLDDR, +}; + +static struct clk sysclk = { + .name = "sysclk", + .ops = &sysclk_ops, + .rate = 26000000, + .mutex = &sysclk_mutex, +}; + +static struct clk rtc32k = { + .name = "rtc32k", + .rate = 32768, +}; + +static struct clk kbd32k = { + .name = "kbd32k", + .rate = 32768, +}; + +static struct clk clk_dummy = { + .name = "dummy", +}; + +static struct clk rtc_clk1 = { + .name = "rtc_clk1", + .ops = &rtc_clk_ops, + .cg_sel = 1, + .mutex = &sysclk_mutex, +}; + +static struct clk clkout0 = { + .name = "clkout0", + .ops = &clkout0_ops, + .parent = &sysclk, + .mutex = &sysclk_mutex, +}; + +static struct clk clkout1 = { + .name = "clkout1", + .ops = &clkout1_ops, + .parent = &sysclk, + .mutex = &sysclk_mutex, +}; + +static struct clk clkout2 = { + .name = "clkout2", + .ops = &clkout2_ops, + .parent = &sysclk, + .mutex = &sysclk_mutex, +}; + +static DEFINE_MUTEX(parented_prcmu_mutex); + +#define DEF_PRCMU_CLK_PARENT(_name, _cg_sel, _rate, _parent) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_clk_ops, \ + .cg_sel = _cg_sel, \ + .rate = _rate, \ + .parent = _parent, \ + .mutex = &parented_prcmu_mutex, \ + } + +static DEFINE_MUTEX(prcmu_client_mutex); + +#define DEF_PRCMU_CLIENT_CLK(_name, _cg_sel, _rate) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_clk_ops, \ + .cg_sel = _cg_sel, \ + .rate = _rate, \ + .mutex = &prcmu_client_mutex, \ + } + +static DEF_PRCMU_CLK(dmaclk, PRCMU_DMACLK, 200000000); +static DEF_PRCMU_CLK(b2r2clk, PRCMU_B2R2CLK, 200000000); +static DEF_PRCMU_CLK(sgaclk, PRCMU_SGACLK, 199900000); +static DEF_PRCMU_CLK(uartclk, PRCMU_UARTCLK, 36360000); +static DEF_PRCMU_CLK(msp02clk, PRCMU_MSP02CLK, 13000000); +static DEF_PRCMU_CLIENT_CLK(msp1clk, PRCMU_MSP1CLK, 26000000); +static DEF_PRCMU_CLIENT_CLK(cdclk, PRCMU_CDCLK, 26000000); +static DEF_PRCMU_CLK(i2cclk, PRCMU_I2CCLK, 24000000); +static DEF_PRCMU_CLK_PARENT(irdaclk, PRCMU_IRDACLK, 48000000, &soc1_pll); +static DEF_PRCMU_CLK_PARENT(irrcclk, PRCMU_IRRCCLK, 48000000, &soc1_pll); +static DEF_PRCMU_CLK(rngclk, PRCMU_RNGCLK, 26000000); +static DEF_PRCMU_CLK(pwmclk, PRCMU_PWMCLK, 26000000); +static DEF_PRCMU_CLK(sdmmcclk, PRCMU_SDMMCCLK, 50000000); +static DEF_PRCMU_CLK(spare1clk, PRCMU_SPARE1CLK, 50000000); +static DEF_PRCMU_CLK(per1clk, PRCMU_PER1CLK, 133330000); +static DEF_PRCMU_CLK(per2clk, PRCMU_PER2CLK, 133330000); +static DEF_PRCMU_CLK(per3clk, PRCMU_PER3CLK, 133330000); +static DEF_PRCMU_CLK(per5clk, PRCMU_PER5CLK, 133330000); +static DEF_PRCMU_CLK(per6clk, PRCMU_PER6CLK, 133330000); +static DEF_PRCMU_CLK(hdmiclk, PRCMU_HDMICLK, 26000000); +static DEF_PRCMU_CLK(apeatclk, PRCMU_APEATCLK, 200000000); +static DEF_PRCMU_CLK(apetraceclk, PRCMU_APETRACECLK, 266000000); +static DEF_PRCMU_CLK(mcdeclk, PRCMU_MCDECLK, 160000000); +static DEF_PRCMU_CLK(tvclk, PRCMU_TVCLK, 40000000); +static DEF_PRCMU_CLK(dsialtclk, PRCMU_DSIALTCLK, 400000000); +static DEF_PRCMU_CLK(timclk, PRCMU_TIMCLK, 3250000); +static DEF_PRCMU_CLK_PARENT(svaclk, PRCMU_SVACLK, 156000000, &soc1_pll); +static DEF_PRCMU_CLK(siaclk, PRCMU_SIACLK, 133330000); + +/* PRCC PClocks */ + +static DEF_PER1_PCLK(0, p1_pclk0); +static DEF_PER1_PCLK(1, p1_pclk1); +static DEF_PER1_PCLK(2, p1_pclk2); +static DEF_PER1_PCLK(3, p1_pclk3); +static DEF_PER1_PCLK(4, p1_pclk4); +static DEF_PER1_PCLK(5, p1_pclk5); +static DEF_PER1_PCLK(6, p1_pclk6); + +static DEF_PER2_PCLK(0, p2_pclk0); +static DEF_PER2_PCLK(1, p2_pclk1); + +static DEF_PER3_PCLK(0, p3_pclk0); +static DEF_PER3_PCLK(1, p3_pclk1); +static DEF_PER3_PCLK(2, p3_pclk2); + +static DEF_PER5_PCLK(0, p5_pclk0); +static DEF_PER5_PCLK(1, p5_pclk1); +static DEF_PER5_PCLK(2, p5_pclk2); +static DEF_PER5_PCLK(3, p5_pclk3); +static DEF_PER5_PCLK(4, p5_pclk4); +static DEF_PER5_PCLK(5, p5_pclk5); +static DEF_PER5_PCLK(6, p5_pclk6); +static DEF_PER5_PCLK(7, p5_pclk7); +static DEF_PER5_PCLK(8, p5_pclk8); +static DEF_PER5_PCLK(9, p5_pclk9); +static DEF_PER5_PCLK(10, p5_pclk10); +static DEF_PER5_PCLK(11, p5_pclk11); +static DEF_PER5_PCLK(12, p5_pclk12); +static DEF_PER5_PCLK(13, p5_pclk13); +static DEF_PER5_PCLK(14, p5_pclk14); +static DEF_PER5_PCLK(15, p5_pclk15); + +static DEF_PER6_PCLK(0, p6_pclk0); +static DEF_PER6_PCLK(1, p6_pclk1); +static DEF_PER6_PCLK(2, p6_pclk2); +static DEF_PER6_PCLK(3, p6_pclk3); +static DEF_PER6_PCLK(4, p6_pclk4); +static DEF_PER6_PCLK(5, p6_pclk5); +static DEF_PER6_PCLK(6, p6_pclk6); +static DEF_PER6_PCLK(7, p6_pclk7); + +/* MSP0 */ +static DEF_PER1_KCLK(0, p1_msp0_kclk, &msp02clk); +static DEF_PER_CLK(p1_msp0_clk, &p1_pclk0, &p1_msp0_kclk); + +/* SDI0 */ +static DEF_PER1_KCLK(1, p1_sdi0_kclk, &spare1clk); /* &sdmmcclk on v1 */ +static DEF_PER_CLK(p1_sdi0_clk, &p1_pclk1, &p1_sdi0_kclk); + +/* SDI2 */ +static DEF_PER1_KCLK(2, p1_sdi2_kclk, &sdmmcclk); +static DEF_PER_CLK(p1_sdi2_clk, &p1_pclk2, &p1_sdi2_kclk); + +/* UART0 */ +static DEF_PER1_KCLK(3, p1_uart0_kclk, &uartclk); +static DEF_PER_CLK(p1_uart0_clk, &p1_pclk3, &p1_uart0_kclk); + +/* I2C1 */ +static DEF_PER1_KCLK(4, p1_i2c1_kclk, &i2cclk); +static DEF_PER_CLK(p1_i2c1_clk, &p1_pclk4, &p1_i2c1_kclk); + +/* PWM */ +static DEF_PER3_KCLK(0, p3_pwm_kclk, &pwmclk); +static DEF_PER_CLK(p3_pwm_clk, &p3_pclk1, &p3_pwm_kclk); + +/* KEYPAD */ +static DEF_PER3_KCLK(0, p3_keypad_kclk, &kbd32k); +static DEF_PER_CLK(p3_keypad_clk, &p3_pclk0, &p3_keypad_kclk); + +/* MSP2 */ +static DEF_PER5_KCLK(0, p5_msp2_kclk, &msp02clk); +static DEF_PER_CLK(p5_msp2_clk, &p5_pclk0, &p5_msp2_kclk); + +/* UART1 */ +static DEF_PER5_KCLK(1, p5_uart1_kclk, &uartclk); +static DEF_PER_CLK(p5_uart1_clk, &p5_pclk1, &p5_uart1_kclk); + +/* UART2 */ +static DEF_PER5_KCLK(2, p5_uart2_kclk, &uartclk); +static DEF_PER_CLK(p5_uart2_clk, &p5_pclk2, &p5_uart2_kclk); + +/* UART3 */ +static DEF_PER5_KCLK(3, p5_uart3_kclk, &uartclk); +static DEF_PER_CLK(p5_uart3_clk, &p5_pclk3, &p5_uart3_kclk); + +/* SDI1 */ +static DEF_PER5_KCLK(4, p5_sdi1_kclk, &sdmmcclk); +static DEF_PER_CLK(p5_sdi1_clk, &p5_pclk4, &p5_sdi1_kclk); + +/* SDI3 */ +static DEF_PER5_KCLK(5, p5_sdi3_kclk, &sdmmcclk); +static DEF_PER_CLK(p5_sdi3_clk, &p5_pclk5, &p5_sdi3_kclk); + +/* SDI4 */ +static DEF_PER5_KCLK(6, p5_sdi4_kclk, &sdmmcclk); +static DEF_PER_CLK(p5_sdi4_clk, &p5_pclk6, &p5_sdi4_kclk); + +/* I2C2 */ +static DEF_PER5_KCLK(7, p5_i2c2_kclk, &i2cclk); +static DEF_PER_CLK(p5_i2c2_clk, &p5_pclk7, &p5_i2c2_kclk); + +/* I2C3 */ +static DEF_PER5_KCLK(8, p5_i2c3_kclk, &i2cclk); +static DEF_PER_CLK(p5_i2c3_clk, &p5_pclk8, &p5_i2c3_kclk); + +/* IRRC */ +static DEF_PER5_KCLK(9, p5_irrc_kclk, &irrcclk); +static DEF_PER_CLK(p5_irrc_clk, &p5_pclk9, &p5_irrc_kclk); + +/* IRDA */ +static DEF_PER5_KCLK(10, p5_irda_kclk, &irdaclk); +static DEF_PER_CLK(p5_irda_clk, &p5_pclk10, &p5_irda_kclk); + +/* RNG */ +static DEF_PER6_KCLK(0, p6_rng_kclk, &rngclk); +static DEF_PER_CLK(p6_rng_clk, &p6_pclk0, &p6_rng_kclk); + +/* MTU:S */ + +/* MTU0 */ +static DEF_PER_CLK(p6_mtu0_clk, &p6_pclk6, &timclk); + +/* MTU1 */ +static DEF_PER_CLK(p6_mtu1_clk, &p6_pclk7, &timclk); + +static struct clk *db5500_dbg_clks[] __initdata = { + /* Clock sources */ + &soc0_pll, + &soc1_pll, + &ddr_pll, + &sysclk, + &rtc32k, + + /* PRCMU clocks */ + &sgaclk, + &siaclk, + &svaclk, + &uartclk, + &msp02clk, + &msp1clk, + &cdclk, + &i2cclk, + &irdaclk, + &irrcclk, + &sdmmcclk, + &spare1clk, + &per1clk, + &per2clk, + &per3clk, + &per5clk, + &per6clk, + &hdmiclk, + &apeatclk, + &apetraceclk, + &mcdeclk, + &dsialtclk, + &dmaclk, + &b2r2clk, + &tvclk, + &rngclk, + &pwmclk, + + /* PRCC clocks */ + &p1_pclk0, + &p1_pclk1, + &p1_pclk2, + &p1_pclk3, + &p1_pclk4, + &p1_pclk5, + &p1_pclk6, + + &p2_pclk0, + &p2_pclk1, + + &p3_pclk0, + &p3_pclk1, + &p3_pclk2, + + &p5_pclk0, + &p5_pclk1, + &p5_pclk2, + &p5_pclk3, + &p5_pclk4, + &p5_pclk5, + &p5_pclk6, + &p5_pclk7, + &p5_pclk8, + &p5_pclk9, + &p5_pclk10, + &p5_pclk11, + &p5_pclk12, + &p5_pclk13, + &p5_pclk14, + &p5_pclk15, + + &p6_pclk0, + &p6_pclk1, + &p6_pclk2, + &p6_pclk3, + &p6_pclk4, + &p6_pclk5, + &p6_pclk6, + &p6_pclk7, + + /* Clock sources */ + &clkout0, + &clkout1, + &clkout2, + &rtc_clk1, +}; + +static struct clk_lookup u8500_common_clock_sources[] = { + CLK_LOOKUP(soc0_pll, NULL, "soc0_pll"), + CLK_LOOKUP(soc1_pll, NULL, "soc1_pll"), + CLK_LOOKUP(ddr_pll, NULL, "ddr_pll"), + CLK_LOOKUP(sysclk, NULL, "sysclk"), + CLK_LOOKUP(rtc32k, NULL, "clk32k"), +}; + +static struct clk_lookup db5500_prcmu_clocks[] = { + CLK_LOOKUP(sgaclk, "mali", NULL), + CLK_LOOKUP(siaclk, "mmio_camera", "sia"), + CLK_LOOKUP(svaclk, "hva", NULL), + CLK_LOOKUP(uartclk, "UART", NULL), + CLK_LOOKUP(msp02clk, "MSP02", NULL), + CLK_LOOKUP(msp1clk, "ux500-msp-i2s.1", NULL), + CLK_LOOKUP(cdclk, "cable_detect.0", NULL), + CLK_LOOKUP(i2cclk, "I2C", NULL), + CLK_LOOKUP(sdmmcclk, "sdmmc", NULL), + CLK_LOOKUP(per1clk, "PERIPH1", NULL), + CLK_LOOKUP(per2clk, "PERIPH2", NULL), + CLK_LOOKUP(per3clk, "PERIPH3", NULL), + CLK_LOOKUP(per5clk, "PERIPH5", NULL), + CLK_LOOKUP(per6clk, "PERIPH6", NULL), + CLK_LOOKUP(hdmiclk, "mcde", "hdmi"), + CLK_LOOKUP(apeatclk, "apeat", NULL), + CLK_LOOKUP(apetraceclk, "apetrace", NULL), + CLK_LOOKUP(mcdeclk, "mcde", NULL), + CLK_LOOKUP(mcdeclk, "mcde", "mcde"), + CLK_LOOKUP(dmaclk, "dma40.0", NULL), + CLK_LOOKUP(b2r2clk, "b2r2", NULL), + CLK_LOOKUP(b2r2clk, "b2r2_bus", NULL), + CLK_LOOKUP(b2r2clk, "U8500-B2R2.0", NULL), + CLK_LOOKUP(tvclk, "tv", NULL), + CLK_LOOKUP(tvclk, "mcde", "tv"), +}; + +static struct clk_lookup db5500_prcc_clocks[] = { + CLK_LOOKUP(p1_msp0_clk, "ux500-msp-i2s.0", NULL), + CLK_LOOKUP(p1_sdi0_clk, "sdi0", NULL), + CLK_LOOKUP(p1_sdi2_clk, "sdi2", NULL), + CLK_LOOKUP(p1_uart0_clk, "uart0", NULL), + CLK_LOOKUP(p1_i2c1_clk, "nmk-i2c.1", NULL), + CLK_LOOKUP(p1_pclk5, "gpio.0", NULL), + CLK_LOOKUP(p1_pclk5, "gpio.1", NULL), + CLK_LOOKUP(p1_pclk6, "fsmc", NULL), + + CLK_LOOKUP(p2_pclk0, "musb-ux500.0", "usb"), + CLK_LOOKUP(p2_pclk1, "gpio.2", NULL), + + CLK_LOOKUP(p3_keypad_clk, "db5500-keypad", NULL), + CLK_LOOKUP(p3_pwm_clk, "pwm", NULL), + CLK_LOOKUP(p3_pclk2, "gpio.4", NULL), + + CLK_LOOKUP(p5_msp2_clk, "ux500-msp-i2s.2", NULL), + CLK_LOOKUP(p5_uart1_clk, "uart1", NULL), + CLK_LOOKUP(p5_uart2_clk, "uart2", NULL), + CLK_LOOKUP(p5_uart3_clk, "uart3", NULL), + CLK_LOOKUP(p5_sdi1_clk, "sdi1", NULL), + CLK_LOOKUP(p5_sdi3_clk, "sdi3", NULL), + CLK_LOOKUP(p5_sdi4_clk, "sdi4", NULL), + CLK_LOOKUP(p5_i2c2_clk, "nmk-i2c.2", NULL), + CLK_LOOKUP(p5_i2c3_clk, "nmk-i2c.3", NULL), + CLK_LOOKUP(p5_irrc_clk, "irrc", NULL), + CLK_LOOKUP(p5_irda_clk, "irda", NULL), + CLK_LOOKUP(p5_pclk11, "spi0", NULL), + CLK_LOOKUP(p5_pclk12, "spi1", NULL), + CLK_LOOKUP(p5_pclk13, "spi2", NULL), + CLK_LOOKUP(p5_pclk14, "spi3", NULL), + CLK_LOOKUP(p5_pclk15, "gpio.5", NULL), + CLK_LOOKUP(p5_pclk15, "gpio.6", NULL), + CLK_LOOKUP(p5_pclk15, "gpio.7", NULL), + + CLK_LOOKUP(p6_rng_clk, "rng", NULL), + CLK_LOOKUP(p6_pclk1, "cryp0", NULL), + CLK_LOOKUP(p6_pclk2, "hash0", NULL), + CLK_LOOKUP(p6_pclk3, "pka", NULL), + CLK_LOOKUP(p6_pclk4, "hash1", NULL), + CLK_LOOKUP(p6_pclk1, "cryp1", NULL), + CLK_LOOKUP(p6_pclk5, "cfgreg", NULL), + CLK_LOOKUP(p6_mtu0_clk, "mtu0", NULL), + CLK_LOOKUP(p6_mtu1_clk, "mtu1", NULL), + + /* + * Dummy clock sets up the GPIOs. + */ + CLK_LOOKUP(clk_dummy, "gpio.3", NULL), + CLK_LOOKUP(rtc32k, "rtc-pl031", NULL), +}; + +static struct clk_lookup db5500_clkouts[] = { + CLK_LOOKUP(clkout1, "mmio_camera", "primary-cam"), + CLK_LOOKUP(clkout1, "mmio_camera", "secondary-cam"), + CLK_LOOKUP(clkout2, "ab5500-usb.0", "sysclk"), + CLK_LOOKUP(clkout2, "ab5500-codec.0", "sysclk"), +}; + +static struct clk_lookup u5500_clocks[] = { + CLK_LOOKUP(rtc_clk1, "cg2900-uart.0", "lpoclk"), +}; + +static const char *db5500_boot_clk[] __initdata = { + "spi0", + "spi1", + "spi2", + "spi3", + "uart0", + "uart1", + "uart2", + "uart3", + "sdi0", + "sdi1", + "sdi2", + "sdi3", + "sdi4", +}; + +static struct clk *boot_clks[ARRAY_SIZE(db5500_boot_clk)] __initdata; + +static int __init db5500_boot_clk_disable(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(db5500_boot_clk); i++) { + clk_disable(boot_clks[i]); + clk_put(boot_clks[i]); + } + + return 0; +} +late_initcall_sync(db5500_boot_clk_disable); + +static void __init db5500_boot_clk_enable(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(db5500_boot_clk); i++) { + boot_clks[i] = clk_get_sys(db5500_boot_clk[i], NULL); + BUG_ON(IS_ERR(boot_clks[i])); + clk_enable(boot_clks[i]); + } +} + +static void configure_clkouts(void) +{ + /* div parameter does not matter for sel0 REF_CLK */ + WARN_ON(prcmu_config_clkout(DB5500_CLKOUT0, + DB5500_CLKOUT_REF_CLK_SEL0, 0)); + WARN_ON(prcmu_config_clkout(DB5500_CLKOUT1, + DB5500_CLKOUT_REF_CLK_SEL0, 0)); +} + +static struct clk *db5500_clks_tobe_disabled[] __initdata = { + &siaclk, + &sgaclk, + &sdmmcclk, + &p1_pclk0, + &p1_pclk6, + &p3_keypad_clk, + &p3_pclk1, + &p5_pclk0, + &p5_pclk11, + &p5_pclk12, + &p5_pclk13, + &p5_pclk14, + &p6_pclk4, + &p6_pclk5, + &p6_pclk7, + &p5_uart1_clk, + &p5_uart2_clk, + &p5_uart3_clk, + &p5_sdi1_clk, + &p5_sdi3_clk, + &p5_sdi4_clk, + &p5_i2c3_clk, + &pwmclk, + &svaclk, + &cdclk, + &clkout2, +}; + +static int __init init_clock_states(void) +{ + int i = 0; + /* + * The following clks are shared with secure world. + * Currently this leads to a limitation where we need to + * enable them at all times. + */ + clk_enable(&p6_pclk1); + clk_enable(&p6_pclk2); + clk_enable(&p6_pclk3); + clk_enable(&p6_rng_clk); + + /* + * Disable clocks that are on at boot, but should be off. + */ + for (i = 0; i < ARRAY_SIZE(db5500_clks_tobe_disabled); i++) { + if (!clk_enable(db5500_clks_tobe_disabled[i])) + clk_disable(db5500_clks_tobe_disabled[i]); + } + return 0; +} +late_initcall(init_clock_states); + +int __init db5500_clk_init(void) +{ + if (ux500_is_svp()) { + prcmu_clk_ops.enable = NULL; + prcmu_clk_ops.disable = NULL; + prcc_pclk_ops.enable = NULL; + prcc_pclk_ops.disable = NULL; + prcc_kclk_ops.enable = NULL; + prcc_kclk_ops.disable = NULL; + } + prcmu_clk_ops.get_rate = NULL; + + clkdev_add_table(u8500_common_clock_sources, + ARRAY_SIZE(u8500_common_clock_sources)); + + clkdev_add_table(db5500_prcmu_clocks, ARRAY_SIZE(db5500_prcmu_clocks)); + clkdev_add_table(db5500_prcc_clocks, ARRAY_SIZE(db5500_prcc_clocks)); + clkdev_add_table(db5500_clkouts, ARRAY_SIZE(db5500_clkouts)); + clkdev_add_table(u5500_clocks, ARRAY_SIZE(u5500_clocks)); + + db5500_boot_clk_enable(); + + /* + * The following clks are shared with secure world. + * Currently this leads to a limitation where we need to + * enable them at all times. + */ + clk_enable(&p6_pclk1); + clk_enable(&p6_pclk2); + clk_enable(&p6_pclk3); + clk_enable(&p6_rng_clk); + + configure_clkouts(); + + return 0; +} + +int __init db5500_clk_debug_init(void) +{ + return dbx500_clk_debug_init(db5500_dbg_clks, + ARRAY_SIZE(db5500_dbg_clks)); +} diff --git a/arch/arm/mach-ux500/clock-db8500.c b/arch/arm/mach-ux500/clock-db8500.c new file mode 100644 index 00000000000..d41a22dd041 --- /dev/null +++ b/arch/arm/mach-ux500/clock-db8500.c @@ -0,0 +1,1171 @@ +/* + * Copyright (C) 2009-2011 ST-Ericsson SA + * Copyright (C) 2009 STMicroelectronics + * + * 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/init.h> +#include <linux/list.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/gpio/nomadik.h> +#include <linux/mfd/abx500/ab8500-sysctrl.h> +#include <linux/workqueue.h> +#include <linux/regulator/consumer.h> + +#include <plat/pincfg.h> + +#include <mach/hardware.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include "clock.h" +#include "pins-db8500.h" +#include "product.h" +#include "prcc.h" + +static DEFINE_MUTEX(soc0_pll_mutex); +static DEFINE_MUTEX(soc1_pll_mutex); +static DEFINE_MUTEX(sysclk_mutex); +static DEFINE_MUTEX(ab_ulpclk_mutex); +static DEFINE_MUTEX(ab_intclk_mutex); +static DEFINE_MUTEX(clkout0_mutex); +static DEFINE_MUTEX(dsi_pll_mutex); + +static struct delayed_work sysclk_disable_work; + +/* PLL operations. */ + +static unsigned long pll_get_rate(struct clk *clk) +{ + return prcmu_clock_rate(clk->cg_sel); +} + +static struct clkops pll_ops = { + .get_rate = pll_get_rate, +}; + +/* SysClk operations. */ + +static int request_sysclk(bool enable) +{ + static int requests; + + if ((enable && (requests++ == 0)) || (!enable && (--requests == 0))) + return prcmu_request_clock(PRCMU_SYSCLK, enable); + return 0; +} + +static int sysclk_enable(struct clk *clk) +{ + static bool swat_enable; + int r; + + if (!swat_enable) { + r = ab8500_sysctrl_set(AB8500_SWATCTRL, + AB8500_SWATCTRL_SWATENABLE); + if (r) + return r; + + swat_enable = true; + } + + r = request_sysclk(true); + if (r) + return r; + + if (clk->cg_sel) { + r = ab8500_sysctrl_set(AB8500_SYSULPCLKCTRL1, (u8)clk->cg_sel); + if (r) + (void)request_sysclk(false); + } + return r; +} + +static void sysclk_disable(struct clk *clk) +{ + int r; + + if (clk->cg_sel) { + r = ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + (u8)clk->cg_sel); + if (r) + goto disable_failed; + } + r = request_sysclk(false); + if (r) + goto disable_failed; + return; + +disable_failed: + pr_err("clock: failed to disable %s.\n", clk->name); +} + +static struct clkops sysclk_ops = { + .enable = sysclk_enable, + .disable = sysclk_disable, +}; + +/* AB8500 UlpClk operations */ + +static int ab_ulpclk_enable(struct clk *clk) +{ + int err; + + if (clk->regulator == NULL) { + struct regulator *reg; + + reg = regulator_get(NULL, "v-intcore"); + if (IS_ERR(reg)) + return PTR_ERR(reg); + clk->regulator = reg; + } + err = regulator_set_optimum_mode(clk->regulator, 1500); + if (unlikely(err < 0)) + goto regulator_enable_error; + err = regulator_enable(clk->regulator); + if (unlikely(err)) + goto regulator_enable_error; + err = ab8500_sysctrl_clear(AB8500_SYSULPCLKCONF, + AB8500_SYSULPCLKCONF_ULPCLKCONF_MASK); + if (unlikely(err)) + goto enable_error; + err = ab8500_sysctrl_set(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_ULPCLKREQ); + if (unlikely(err)) + goto enable_error; + /* Unknown/undocumented PLL locking time => wait 1 ms. */ + mdelay(1); + return 0; + +enable_error: + (void)regulator_disable(clk->regulator); +regulator_enable_error: + return err; +} + +static void ab_ulpclk_disable(struct clk *clk) +{ + int err; + + err = ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_ULPCLKREQ); + if (unlikely(regulator_disable(clk->regulator) || err)) + goto out_err; + + regulator_set_optimum_mode(clk->regulator, 0); + + return; + +out_err: + pr_err("clock: %s failed to disable %s.\n", __func__, clk->name); +} + +static struct clkops ab_ulpclk_ops = { + .enable = ab_ulpclk_enable, + .disable = ab_ulpclk_disable, +}; + +/* AB8500 intclk operations */ + +enum ab_intclk_parent { + AB_INTCLK_PARENT_SYSCLK, + AB_INTCLK_PARENT_ULPCLK, + AB_INTCLK_PARENTS_END, + NUM_AB_INTCLK_PARENTS +}; + +static int ab_intclk_enable(struct clk *clk) +{ + if (clk->parent == clk->parents[AB_INTCLK_PARENT_ULPCLK]) { + return ab8500_sysctrl_write(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_MASK, + (1 << AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_SHIFT)); + } + return 0; +} + +static void ab_intclk_disable(struct clk *clk) +{ + if (clk->parent == clk->parents[AB_INTCLK_PARENT_SYSCLK]) + return; + + if (ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_MASK)) { + pr_err("clock: %s failed to disable %s.\n", __func__, + clk->name); + } +} + +static int ab_intclk_set_parent(struct clk *clk, struct clk *parent) +{ + int err; + + if (!clk->enabled) + return 0; + + err = __clk_enable(parent, clk->mutex); + + if (unlikely(err)) + goto parent_enable_error; + + if (parent == clk->parents[AB_INTCLK_PARENT_ULPCLK]) { + err = ab8500_sysctrl_write(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_MASK, + (1 << AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_SHIFT)); + } else { + err = ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_MASK); + } + if (unlikely(err)) + goto config_error; + + __clk_disable(clk->parent, clk->mutex); + + return 0; + +config_error: + __clk_disable(parent, clk->mutex); +parent_enable_error: + return err; +} + +static struct clkops ab_intclk_ops = { + .enable = ab_intclk_enable, + .disable = ab_intclk_disable, + .set_parent = ab_intclk_set_parent, +}; + +/* AB8500 audio clock operations */ + +static int audioclk_enable(struct clk *clk) +{ + return ab8500_sysctrl_set(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_AUDIOCLKENA); +} + +static void audioclk_disable(struct clk *clk) +{ + if (ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_AUDIOCLKENA)) { + pr_err("clock: %s failed to disable %s.\n", __func__, + clk->name); + } +} + +static struct clkops audioclk_ops = { + .enable = audioclk_enable, + .disable = audioclk_disable, +}; + +/* Primary camera clock operations */ +static int clkout0_enable(struct clk *clk) +{ + int r; + + if (clk->regulator == NULL) { + struct regulator *reg; + + reg = regulator_get(NULL, "v-ape"); + if (IS_ERR(reg)) + return PTR_ERR(reg); + clk->regulator = reg; + } + r = regulator_enable(clk->regulator); + if (r) + goto regulator_failed; + r = prcmu_config_clkout(0, PRCMU_CLKSRC_CLK38M, 4); + if (r) + goto config_failed; + r = nmk_config_pin(GPIO227_CLKOUT1, false); + if (r) + goto gpio_failed; + return r; + +gpio_failed: + (void)prcmu_config_clkout(0, PRCMU_CLKSRC_CLK38M, 0); +config_failed: + (void)regulator_disable(clk->regulator); +regulator_failed: + return r; +} + +static void clkout0_disable(struct clk *clk) +{ + int r; + + r = nmk_config_pin((GPIO227_GPIO | PIN_OUTPUT_LOW), false); + if (r) + goto disable_failed; + (void)prcmu_config_clkout(0, PRCMU_CLKSRC_CLK38M, 0); + (void)regulator_disable(clk->regulator); + return; + +disable_failed: + pr_err("clock: failed to disable %s.\n", clk->name); +} + +/* Touch screen/secondary camera clock operations. */ +static int clkout1_enable(struct clk *clk) +{ + int r; + + if (clk->regulator == NULL) { + struct regulator *reg; + + reg = regulator_get(NULL, "v-ape"); + if (IS_ERR(reg)) + return PTR_ERR(reg); + clk->regulator = reg; + } + r = regulator_enable(clk->regulator); + if (r) + goto regulator_failed; + r = prcmu_config_clkout(1, PRCMU_CLKSRC_SYSCLK, 4); + if (r) + goto config_failed; + r = nmk_config_pin(GPIO228_CLKOUT2, false); + if (r) + goto gpio_failed; + return r; + +gpio_failed: + (void)prcmu_config_clkout(1, PRCMU_CLKSRC_SYSCLK, 0); +config_failed: + (void)regulator_disable(clk->regulator); +regulator_failed: + return r; +} + +static void clkout1_disable(struct clk *clk) +{ + int r; + + r = nmk_config_pin((GPIO228_GPIO | PIN_OUTPUT_LOW), false); + if (r) + goto disable_failed; + (void)prcmu_config_clkout(1, PRCMU_CLKSRC_SYSCLK, 0); + (void)regulator_disable(clk->regulator); + return; + +disable_failed: + pr_err("clock: failed to disable %s.\n", clk->name); +} + +static struct clkops clkout0_ops = { + .enable = clkout0_enable, + .disable = clkout0_disable, +}; + +static struct clkops clkout1_ops = { + .enable = clkout1_enable, + .disable = clkout1_disable, +}; + +#define DEF_PER1_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST1_BASE, _cg_bit, &per1clk) +#define DEF_PER2_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST2_BASE, _cg_bit, &per2clk) +#define DEF_PER3_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST3_BASE, _cg_bit, &per3clk) +#define DEF_PER5_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST5_BASE, _cg_bit, &per5clk) +#define DEF_PER6_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST6_BASE, _cg_bit, &per6clk) + +#define DEF_PER1_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST1_BASE, _cg_bit, _parent, &per1clk) +#define DEF_PER2_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST2_BASE, _cg_bit, _parent, &per2clk) +#define DEF_PER3_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST3_BASE, _cg_bit, _parent, &per3clk) +#define DEF_PER5_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST5_BASE, _cg_bit, _parent, &per5clk) +#define DEF_PER6_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST6_BASE, _cg_bit, _parent, &per6clk) + +/* Clock sources. */ + +static struct clk soc0_pll = { + .name = "soc0_pll", + .ops = &prcmu_clk_ops, + .cg_sel = PRCMU_PLLSOC0, + .mutex = &soc0_pll_mutex, +}; + +static struct clk soc1_pll = { + .name = "soc1_pll", + .ops = &prcmu_clk_ops, + .cg_sel = PRCMU_PLLSOC1, + .mutex = &soc1_pll_mutex, +}; + +static struct clk ddr_pll = { + .name = "ddr_pll", + .ops = &pll_ops, + .cg_sel = PRCMU_PLLDDR, +}; + +static struct clk ulp38m4 = { + .name = "ulp38m4", + .rate = 38400000, +}; + +static struct clk sysclk = { + .name = "sysclk", + .ops = &sysclk_ops, + .rate = 38400000, + .mutex = &sysclk_mutex, +}; + +static struct clk sysclk2 = { + .name = "sysclk2", + .ops = &sysclk_ops, + .cg_sel = AB8500_SYSULPCLKCTRL1_SYSCLKBUF2REQ, + .rate = 38400000, + .mutex = &sysclk_mutex, +}; + +static struct clk sysclk3 = { + .name = "sysclk3", + .ops = &sysclk_ops, + .cg_sel = AB8500_SYSULPCLKCTRL1_SYSCLKBUF3REQ, + .rate = 38400000, + .mutex = &sysclk_mutex, +}; + +static struct clk sysclk4 = { + .name = "sysclk4", + .ops = &sysclk_ops, + .cg_sel = AB8500_SYSULPCLKCTRL1_SYSCLKBUF4REQ, + .rate = 38400000, + .mutex = &sysclk_mutex, +}; + +static struct clk rtc32k = { + .name = "rtc32k", + .rate = 32768, +}; + +static struct clk clkout0 = { + .name = "clkout0", + .ops = &clkout0_ops, + .parent = &ulp38m4, + .rate = 9600000, + .mutex = &clkout0_mutex, +}; + +static struct clk clkout1 = { + .name = "clkout1", + .ops = &clkout1_ops, + .parent = &sysclk, + .rate = 9600000, + .mutex = &sysclk_mutex, +}; + +static struct clk ab_ulpclk = { + .name = "ab_ulpclk", + .ops = &ab_ulpclk_ops, + .rate = 38400000, + .mutex = &ab_ulpclk_mutex, +}; + +static struct clk *ab_intclk_parents[NUM_AB_INTCLK_PARENTS] = { + [AB_INTCLK_PARENT_SYSCLK] = &sysclk, + [AB_INTCLK_PARENT_ULPCLK] = &ab_ulpclk, + [AB_INTCLK_PARENTS_END] = NULL, +}; + +static struct clk ab_intclk = { + .name = "ab_intclk", + .ops = &ab_intclk_ops, + .mutex = &ab_intclk_mutex, + .parent = &sysclk, + .parents = ab_intclk_parents, +}; + +static struct clk audioclk = { + .name = "audioclk", + .ops = &audioclk_ops, + .mutex = &ab_intclk_mutex, + .parent = &ab_intclk, +}; + +static DEF_PRCMU_CLK(sgaclk, PRCMU_SGACLK, 320000000); +static DEF_PRCMU_CLK(uartclk, PRCMU_UARTCLK, 38400000); +static DEF_PRCMU_CLK(msp02clk, PRCMU_MSP02CLK, 19200000); +static DEF_PRCMU_CLK(msp1clk, PRCMU_MSP1CLK, 19200000); +static DEF_PRCMU_CLK(i2cclk, PRCMU_I2CCLK, 24000000); +static DEF_PRCMU_CLK(slimclk, PRCMU_SLIMCLK, 19200000); +static DEF_PRCMU_CLK(per1clk, PRCMU_PER1CLK, 133330000); +static DEF_PRCMU_CLK(per2clk, PRCMU_PER2CLK, 133330000); +static DEF_PRCMU_CLK(per3clk, PRCMU_PER3CLK, 133330000); +static DEF_PRCMU_CLK(per5clk, PRCMU_PER5CLK, 133330000); +static DEF_PRCMU_CLK(per6clk, PRCMU_PER6CLK, 133330000); +static DEF_PRCMU_CLK(per7clk, PRCMU_PER7CLK, 100000000); +static DEF_PRCMU_SCALABLE_CLK(lcdclk, PRCMU_LCDCLK); +static DEF_PRCMU_OPP100_CLK(bmlclk, PRCMU_BMLCLK, 200000000); +static DEF_PRCMU_SCALABLE_CLK(hsitxclk, PRCMU_HSITXCLK); +static DEF_PRCMU_SCALABLE_CLK(hsirxclk, PRCMU_HSIRXCLK); +static DEF_PRCMU_SCALABLE_CLK(hdmiclk, PRCMU_HDMICLK); +static DEF_PRCMU_CLK(apeatclk, PRCMU_APEATCLK, 160000000); +static DEF_PRCMU_CLK(apetraceclk, PRCMU_APETRACECLK, 160000000); +static DEF_PRCMU_CLK(mcdeclk, PRCMU_MCDECLK, 160000000); +static DEF_PRCMU_OPP100_CLK(ipi2cclk, PRCMU_IPI2CCLK, 24000000); +static DEF_PRCMU_CLK(dsialtclk, PRCMU_DSIALTCLK, 384000000); +static DEF_PRCMU_CLK(dmaclk, PRCMU_DMACLK, 200000000); +static DEF_PRCMU_CLK(b2r2clk, PRCMU_B2R2CLK, 200000000); +static DEF_PRCMU_SCALABLE_CLK(tvclk, PRCMU_TVCLK); +/* TODO: For SSPCLK, the spec says 24MHz, while the old driver says 48MHz. */ +static DEF_PRCMU_CLK(sspclk, PRCMU_SSPCLK, 24000000); +static DEF_PRCMU_CLK(rngclk, PRCMU_RNGCLK, 19200000); +static DEF_PRCMU_CLK(uiccclk, PRCMU_UICCCLK, 48000000); +static DEF_PRCMU_CLK(timclk, PRCMU_TIMCLK, 2400000); +static DEF_PRCMU_CLK(sdmmcclk, PRCMU_SDMMCCLK, 50000000); + +static struct clk dsi_pll = { + .name = "dsi_pll", + .ops = &prcmu_scalable_clk_ops, + .cg_sel = PRCMU_PLLDSI, + .parent = &hdmiclk, + .mutex = &dsi_pll_mutex, +}; + +static struct clk dsi0clk = { + .name = "dsi0clk", + .ops = &prcmu_scalable_clk_ops, + .cg_sel = PRCMU_DSI0CLK, + .parent = &dsi_pll, + .mutex = &dsi_pll_mutex, +}; + +static struct clk dsi1clk = { + .name = "dsi1clk", + .ops = &prcmu_scalable_clk_ops, + .cg_sel = PRCMU_DSI1CLK, + .parent = &dsi_pll, + .mutex = &dsi_pll_mutex, +}; + +static struct clk dsi0escclk = { + .name = "dsi0escclk", + .ops = &prcmu_scalable_clk_ops, + .cg_sel = PRCMU_DSI0ESCCLK, + .parent = &tvclk, +}; + +static struct clk dsi1escclk = { + .name = "dsi1escclk", + .ops = &prcmu_scalable_clk_ops, + .cg_sel = PRCMU_DSI1ESCCLK, + .parent = &tvclk, +}; + +static struct clk dsi2escclk = { + .name = "dsi2escclk", + .ops = &prcmu_scalable_clk_ops, + .cg_sel = PRCMU_DSI2ESCCLK, + .parent = &tvclk, +}; + +/* PRCC PClocks */ + +static DEF_PER1_PCLK(0, p1_pclk0); +static DEF_PER1_PCLK(1, p1_pclk1); +static DEF_PER1_PCLK(2, p1_pclk2); +static DEF_PER1_PCLK(3, p1_pclk3); +static DEF_PER1_PCLK(4, p1_pclk4); +static DEF_PER1_PCLK(5, p1_pclk5); +static DEF_PER1_PCLK(6, p1_pclk6); +static DEF_PER1_PCLK(7, p1_pclk7); +static DEF_PER1_PCLK(8, p1_pclk8); +static DEF_PER1_PCLK(9, p1_pclk9); +static DEF_PER1_PCLK(10, p1_pclk10); +static DEF_PER1_PCLK(11, p1_pclk11); + +static DEF_PER2_PCLK(0, p2_pclk0); +static DEF_PER2_PCLK(1, p2_pclk1); +static DEF_PER2_PCLK(2, p2_pclk2); +static DEF_PER2_PCLK(3, p2_pclk3); +static DEF_PER2_PCLK(4, p2_pclk4); +static DEF_PER2_PCLK(5, p2_pclk5); +static DEF_PER2_PCLK(6, p2_pclk6); +static DEF_PER2_PCLK(7, p2_pclk7); +static DEF_PER2_PCLK(8, p2_pclk8); +static DEF_PER2_PCLK(9, p2_pclk9); +static DEF_PER2_PCLK(10, p2_pclk10); +static DEF_PER2_PCLK(11, p2_pclk11); + +static DEF_PER3_PCLK(0, p3_pclk0); +static DEF_PER3_PCLK(1, p3_pclk1); +static DEF_PER3_PCLK(2, p3_pclk2); +static DEF_PER3_PCLK(3, p3_pclk3); +static DEF_PER3_PCLK(4, p3_pclk4); +static DEF_PER3_PCLK(5, p3_pclk5); +static DEF_PER3_PCLK(6, p3_pclk6); +static DEF_PER3_PCLK(7, p3_pclk7); +static DEF_PER3_PCLK(8, p3_pclk8); + +static DEF_PER5_PCLK(0, p5_pclk0); +static DEF_PER5_PCLK(1, p5_pclk1); + +static DEF_PER6_PCLK(0, p6_pclk0); +static DEF_PER6_PCLK(1, p6_pclk1); +static DEF_PER6_PCLK(2, p6_pclk2); +static DEF_PER6_PCLK(3, p6_pclk3); +static DEF_PER6_PCLK(4, p6_pclk4); +static DEF_PER6_PCLK(5, p6_pclk5); +static DEF_PER6_PCLK(6, p6_pclk6); +static DEF_PER6_PCLK(7, p6_pclk7); + +/* UART0 */ +static DEF_PER1_KCLK(0, p1_uart0_kclk, &uartclk); +static DEF_PER_CLK(p1_uart0_clk, &p1_pclk0, &p1_uart0_kclk); + +/* UART1 */ +static DEF_PER1_KCLK(1, p1_uart1_kclk, &uartclk); +static DEF_PER_CLK(p1_uart1_clk, &p1_pclk1, &p1_uart1_kclk); + +/* I2C1 */ +static DEF_PER1_KCLK(2, p1_i2c1_kclk, &i2cclk); +static DEF_PER_CLK(p1_i2c1_clk, &p1_pclk2, &p1_i2c1_kclk); + +/* MSP0 */ +static DEF_PER1_KCLK(3, p1_msp0_kclk, &msp02clk); +static DEF_PER_CLK(p1_msp0_clk, &p1_pclk3, &p1_msp0_kclk); + +/* MSP1 */ +static DEF_PER1_KCLK(4, p1_msp1_kclk, &msp1clk); +static DEF_PER_CLK(p1_msp1_clk, &p1_pclk4, &p1_msp1_kclk); + +/* SDI0 */ +static DEF_PER1_KCLK(5, p1_sdi0_kclk, &sdmmcclk); +static DEF_PER_CLK(p1_sdi0_clk, &p1_pclk5, &p1_sdi0_kclk); + +/* I2C2 */ +static DEF_PER1_KCLK(6, p1_i2c2_kclk, &i2cclk); +static DEF_PER_CLK(p1_i2c2_clk, &p1_pclk6, &p1_i2c2_kclk); + +/* SLIMBUS0 */ +static DEF_PER1_KCLK(3, p1_slimbus0_kclk, &slimclk); +static DEF_PER_CLK(p1_slimbus0_clk, &p1_pclk8, &p1_slimbus0_kclk); + +/* I2C4 */ +static DEF_PER1_KCLK(9, p1_i2c4_kclk, &i2cclk); +static DEF_PER_CLK(p1_i2c4_clk, &p1_pclk10, &p1_i2c4_kclk); + +/* MSP3 */ +static DEF_PER1_KCLK(10, p1_msp3_kclk, &msp1clk); +static DEF_PER_CLK(p1_msp3_clk, &p1_pclk11, &p1_msp3_kclk); + +/* I2C3 */ +static DEF_PER2_KCLK(0, p2_i2c3_kclk, &i2cclk); +static DEF_PER_CLK(p2_i2c3_clk, &p2_pclk0, &p2_i2c3_kclk); + +/* SDI4 */ +static DEF_PER2_KCLK(2, p2_sdi4_kclk, &sdmmcclk); +static DEF_PER_CLK(p2_sdi4_clk, &p2_pclk4, &p2_sdi4_kclk); + +/* MSP2 */ +static DEF_PER2_KCLK(3, p2_msp2_kclk, &msp02clk); +static DEF_PER_CLK(p2_msp2_clk, &p2_pclk5, &p2_msp2_kclk); + +/* SDI1 */ +static DEF_PER2_KCLK(4, p2_sdi1_kclk, &sdmmcclk); +static DEF_PER_CLK(p2_sdi1_clk, &p2_pclk6, &p2_sdi1_kclk); + +/* SDI3 */ +static DEF_PER2_KCLK(5, p2_sdi3_kclk, &sdmmcclk); +static DEF_PER_CLK(p2_sdi3_clk, &p2_pclk7, &p2_sdi3_kclk); + +/* HSIR */ +static struct clk p2_ssirx_kclk = { + .name = "p2_ssirx_kclk", + .ops = &prcc_kclk_rec_ops, + .io_base = U8500_CLKRST2_BASE, + .cg_sel = BIT(6), + .parent = &hsirxclk, + .clock = &per2clk, +}; + +/* HSIT */ +static struct clk p2_ssitx_kclk = { + .name = "p2_ssitx_kclk", + .ops = &prcc_kclk_rec_ops, + .io_base = U8500_CLKRST2_BASE, + .cg_sel = BIT(7), + .parent = &hsitxclk, + .clock = &per2clk, +}; + +/* SSP0 */ +static DEF_PER3_KCLK(1, p3_ssp0_kclk, &sspclk); +static DEF_PER_CLK(p3_ssp0_clk, &p3_pclk1, &p3_ssp0_kclk); + +/* SSP1 */ +static DEF_PER3_KCLK(2, p3_ssp1_kclk, &sspclk); +static DEF_PER_CLK(p3_ssp1_clk, &p3_pclk2, &p3_ssp1_kclk); + +/* I2C0 */ +static DEF_PER3_KCLK(3, p3_i2c0_kclk, &i2cclk); +static DEF_PER_CLK(p3_i2c0_clk, &p3_pclk3, &p3_i2c0_kclk); + +/* SDI2 */ +static DEF_PER3_KCLK(4, p3_sdi2_kclk, &sdmmcclk); +static DEF_PER_CLK(p3_sdi2_clk, &p3_pclk4, &p3_sdi2_kclk); + +/* SKE */ +static DEF_PER3_KCLK(5, p3_ske_kclk, &rtc32k); +static DEF_PER_CLK(p3_ske_clk, &p3_pclk5, &p3_ske_kclk); + +/* UART2 */ +static DEF_PER3_KCLK(6, p3_uart2_kclk, &uartclk); +static DEF_PER_CLK(p3_uart2_clk, &p3_pclk6, &p3_uart2_kclk); + +/* SDI5 */ +static DEF_PER3_KCLK(7, p3_sdi5_kclk, &sdmmcclk); +static DEF_PER_CLK(p3_sdi5_clk, &p3_pclk7, &p3_sdi5_kclk); + +/* RNG */ +static DEF_PER6_KCLK(0, p6_rng_kclk, &rngclk); +static DEF_PER_CLK(p6_rng_clk, &p6_pclk0, &p6_rng_kclk); + +/* MTU:S */ + +/* MTU0 */ +static DEF_PER_CLK(p6_mtu0_clk, &p6_pclk6, &timclk); + +/* MTU1 */ +static DEF_PER_CLK(p6_mtu1_clk, &p6_pclk7, &timclk); + +/* + * TODO: Ensure names match with devices and then remove unnecessary entries + * when all drivers use the clk API. + */ + +static struct clk_lookup u8500_clocks[] = { + CLK_LOOKUP(soc0_pll, NULL, "soc0_pll"), + CLK_LOOKUP(soc1_pll, NULL, "soc1_pll"), + CLK_LOOKUP(ddr_pll, NULL, "ddr_pll"), + CLK_LOOKUP(ulp38m4, NULL, "ulp38m4"), + CLK_LOOKUP(sysclk, NULL, "sysclk"), + CLK_LOOKUP(rtc32k, NULL, "clk32k"), + CLK_LOOKUP(sysclk, "ab8500-usb.0", "sysclk"), + CLK_LOOKUP(sysclk, "ab8500-codec.0", "sysclk"), + CLK_LOOKUP(ab_ulpclk, "ab8500-codec.0", "ulpclk"), + CLK_LOOKUP(ab_intclk, "ab8500-codec.0", "intclk"), + CLK_LOOKUP(audioclk, "ab8500-codec.0", "audioclk"), + CLK_LOOKUP(ab_intclk, "ab8500-pwm.1", NULL), + + CLK_LOOKUP(clkout0, "pri-cam", NULL), + CLK_LOOKUP(clkout1, "3-005c", NULL), + CLK_LOOKUP(clkout1, "3-005d", NULL), + CLK_LOOKUP(clkout1, "sec-cam", NULL), + + /* prcmu */ + CLK_LOOKUP(sgaclk, "mali", NULL), + CLK_LOOKUP(uartclk, "UART", NULL), + CLK_LOOKUP(msp02clk, "MSP02", NULL), + CLK_LOOKUP(i2cclk, "I2C", NULL), + CLK_LOOKUP(sdmmcclk, "sdmmc", NULL), + CLK_LOOKUP(slimclk, "slim", NULL), + CLK_LOOKUP(per1clk, "PERIPH1", NULL), + CLK_LOOKUP(per2clk, "PERIPH2", NULL), + CLK_LOOKUP(per3clk, "PERIPH3", NULL), + CLK_LOOKUP(per5clk, "PERIPH5", NULL), + CLK_LOOKUP(per6clk, "PERIPH6", NULL), + CLK_LOOKUP(per7clk, "PERIPH7", NULL), + CLK_LOOKUP(lcdclk, "lcd", NULL), + CLK_LOOKUP(bmlclk, "bml", NULL), + CLK_LOOKUP(p2_ssitx_kclk, "ste_hsi.0", "hsit_hsitxclk"), + CLK_LOOKUP(p2_ssirx_kclk, "ste_hsi.0", "hsir_hsirxclk"), + CLK_LOOKUP(lcdclk, "mcde", "lcd"), + CLK_LOOKUP(hdmiclk, "hdmi", NULL), + CLK_LOOKUP(hdmiclk, "mcde", "hdmi"), + CLK_LOOKUP(apeatclk, "apeat", NULL), + CLK_LOOKUP(apetraceclk, "apetrace", NULL), + CLK_LOOKUP(mcdeclk, "mcde", NULL), + CLK_LOOKUP(mcdeclk, "mcde", "mcde"), + CLK_LOOKUP(ipi2cclk, "ipi2", NULL), + CLK_LOOKUP(dmaclk, "dma40.0", NULL), + CLK_LOOKUP(b2r2clk, "b2r2", NULL), + CLK_LOOKUP(b2r2clk, "b2r2_core", NULL), + CLK_LOOKUP(b2r2clk, "U8500-B2R2.0", NULL), + CLK_LOOKUP(tvclk, "tv", NULL), + CLK_LOOKUP(tvclk, "mcde", "tv"), + CLK_LOOKUP(msp1clk, "MSP1", NULL), + CLK_LOOKUP(dsialtclk, "dsialt", NULL), + CLK_LOOKUP(sspclk, "SSP", NULL), + CLK_LOOKUP(rngclk, "rngclk", NULL), + CLK_LOOKUP(uiccclk, "uicc", NULL), + CLK_LOOKUP(dsi0clk, "mcde", "dsihs0"), + CLK_LOOKUP(dsi1clk, "mcde", "dsihs1"), + CLK_LOOKUP(dsi_pll, "mcde", "dsihs2"), + CLK_LOOKUP(dsi0escclk, "mcde", "dsilp0"), + CLK_LOOKUP(dsi1escclk, "mcde", "dsilp1"), + CLK_LOOKUP(dsi2escclk, "mcde", "dsilp2"), + + /* PERIPH 1 */ + CLK_LOOKUP(p1_msp3_clk, "msp3", NULL), + CLK_LOOKUP(p1_msp3_clk, "ux500-msp-i2s.3", NULL), + CLK_LOOKUP(p1_msp3_kclk, "ab8500-codec.0", "msp3-kernel"), + CLK_LOOKUP(p1_pclk11, "ab8500-codec.0", "msp3-bus"), + CLK_LOOKUP(p1_uart0_clk, "uart0", NULL), + CLK_LOOKUP(p1_uart1_clk, "uart1", NULL), + CLK_LOOKUP(p1_i2c1_clk, "nmk-i2c.1", NULL), + CLK_LOOKUP(p1_msp0_clk, "msp0", NULL), + CLK_LOOKUP(p1_msp0_clk, "ux500-msp-i2s.0", NULL), + CLK_LOOKUP(p1_sdi0_clk, "sdi0", NULL), + CLK_LOOKUP(p1_i2c2_clk, "nmk-i2c.2", NULL), + CLK_LOOKUP(p1_slimbus0_clk, "slimbus0", NULL), + CLK_LOOKUP(p1_pclk9, "gpio.0", NULL), + CLK_LOOKUP(p1_pclk9, "gpio.1", NULL), + CLK_LOOKUP(p1_pclk9, "gpioblock0", NULL), + CLK_LOOKUP(p1_msp1_clk, "msp1", NULL), + CLK_LOOKUP(p1_msp1_clk, "ux500-msp-i2s.1", NULL), + CLK_LOOKUP(p1_msp1_kclk, "ab8500-codec.0", "msp1-kernel"), + CLK_LOOKUP(p1_pclk4, "ab8500-codec.0", "msp1-bus"), + CLK_LOOKUP(p1_pclk7, "spi3", NULL), + CLK_LOOKUP(p1_i2c4_clk, "nmk-i2c.4", NULL), + + /* PERIPH 2 */ + CLK_LOOKUP(p2_i2c3_clk, "nmk-i2c.3", NULL), + CLK_LOOKUP(p2_pclk1, "spi2", NULL), + CLK_LOOKUP(p2_pclk2, "spi1", NULL), + CLK_LOOKUP(p2_pclk3, "pwl", NULL), + CLK_LOOKUP(p2_sdi4_clk, "sdi4", NULL), + CLK_LOOKUP(p2_msp2_clk, "msp2", NULL), + CLK_LOOKUP(p2_msp2_clk, "ux500-msp-i2s.2", NULL), + CLK_LOOKUP(p2_sdi1_clk, "sdi1", NULL), + CLK_LOOKUP(p2_sdi3_clk, "sdi3", NULL), + CLK_LOOKUP(p2_pclk8, "spi0", NULL), + CLK_LOOKUP(p2_pclk9, "ste_hsi.0", "hsir_hclk"), + CLK_LOOKUP(p2_pclk10, "ste_hsi.0", "hsit_hclk"), + CLK_LOOKUP(p2_pclk11, "gpio.6", NULL), + CLK_LOOKUP(p2_pclk11, "gpio.7", NULL), + CLK_LOOKUP(p2_pclk11, "gpioblock1", NULL), + + /* PERIPH 3 */ + CLK_LOOKUP(p3_pclk0, NULL, "fsmc"), + CLK_LOOKUP(p3_i2c0_clk, "nmk-i2c.0", NULL), + CLK_LOOKUP(p3_sdi2_clk, "sdi2", NULL), + CLK_LOOKUP(p3_ske_clk, "ske", NULL), + CLK_LOOKUP(p3_ske_clk, "nmk-ske-keypad", NULL), + CLK_LOOKUP(p3_uart2_clk, "uart2", NULL), + CLK_LOOKUP(p3_sdi5_clk, "sdi5", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.2", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.3", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.4", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.5", NULL), + CLK_LOOKUP(p3_pclk8, "gpioblock2", NULL), + CLK_LOOKUP(p3_ssp0_clk, "ssp0", NULL), + CLK_LOOKUP(p3_ssp1_clk, "ssp1", NULL), + + /* PERIPH 5 */ + CLK_LOOKUP(p5_pclk1, "gpio.8", NULL), + CLK_LOOKUP(p5_pclk1, "gpioblock3", NULL), + CLK_LOOKUP(p5_pclk0, "musb-ux500.0", "usb"), + + /* PERIPH 6 */ + CLK_LOOKUP(p6_pclk1, "cryp0", NULL), + CLK_LOOKUP(p6_pclk2, "hash0", NULL), + CLK_LOOKUP(p6_pclk3, "pka", NULL), + CLK_LOOKUP(p6_pclk5, "cfgreg", NULL), + CLK_LOOKUP(p6_mtu0_clk, "mtu0", NULL), + CLK_LOOKUP(p6_mtu1_clk, "mtu1", NULL), + CLK_LOOKUP(p6_pclk4, "hash1", NULL), + CLK_LOOKUP(p6_pclk1, "cryp1", NULL), + CLK_LOOKUP(p6_rng_clk, "rng", NULL), + +}; + +static struct clk_lookup u8500_v2_sysclks[] = { + CLK_LOOKUP(sysclk2, NULL, "sysclk2"), + CLK_LOOKUP(sysclk3, NULL, "sysclk3"), + CLK_LOOKUP(sysclk4, NULL, "sysclk4"), +}; + +static void sysclk_init_disable(struct work_struct *not_used) +{ + int i; + + mutex_lock(&sysclk_mutex); + + /* Enable SWAT */ + if (ab8500_sysctrl_set(AB8500_SWATCTRL, AB8500_SWATCTRL_SWATENABLE)) + goto err_swat; + + for (i = 0; i < ARRAY_SIZE(u8500_v2_sysclks); i++) { + struct clk *clk = u8500_v2_sysclks[i].clk; + + /* Disable sysclks */ + if (!clk->enabled && clk->cg_sel) { + if (ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + (u8)clk->cg_sel)) + goto err_sysclk; + } + } + goto unlock_and_exit; + +err_sysclk: + pr_err("clock: Disable %s failed", u8500_v2_sysclks[i].clk->name); + ab8500_sysctrl_clear(AB8500_SWATCTRL, AB8500_SWATCTRL_SWATENABLE); + goto unlock_and_exit; + +err_swat: + pr_err("clock: Enable SWAT failed"); + +unlock_and_exit: + mutex_unlock(&sysclk_mutex); +} + +static struct clk *db8500_dbg_clks[] __initdata = { + /* Clock sources */ + &soc0_pll, + &soc1_pll, + &ddr_pll, + &ulp38m4, + &sysclk, + &rtc32k, + /* PRCMU clocks */ + &sgaclk, + &uartclk, + &msp02clk, + &msp1clk, + &i2cclk, + &sdmmcclk, + &slimclk, + &per1clk, + &per2clk, + &per3clk, + &per5clk, + &per6clk, + &per7clk, + &lcdclk, + &bmlclk, + &hsitxclk, + &hsirxclk, + &hdmiclk, + &apeatclk, + &apetraceclk, + &mcdeclk, + &ipi2cclk, + &dsialtclk, + &dsi_pll, + &dsi0clk, + &dsi1clk, + &dsi0escclk, + &dsi1escclk, + &dsi2escclk, + &dmaclk, + &b2r2clk, + &tvclk, + &sspclk, + &rngclk, + &uiccclk, + &sysclk2, + &clkout0, + &clkout1, + &p1_pclk0, + &p1_pclk1, + &p1_pclk2, + &p1_pclk3, + &p1_pclk4, + &p1_pclk5, + &p1_pclk6, + &p1_pclk7, + &p1_pclk8, + &p1_pclk9, + &p1_pclk10, + &p1_pclk11, + &p2_pclk0, + &p2_pclk1, + &p2_pclk2, + &p2_pclk3, + &p2_pclk4, + &p2_pclk5, + &p2_pclk6, + &p2_pclk7, + &p2_pclk8, + &p2_pclk9, + &p2_pclk10, + &p2_pclk11, + &p3_pclk0, + &p3_pclk1, + &p3_pclk2, + &p3_pclk3, + &p3_pclk4, + &p3_pclk5, + &p3_pclk6, + &p3_pclk7, + &p3_pclk8, + &p5_pclk0, + &p5_pclk1, + &p6_pclk0, + &p6_pclk1, + &p6_pclk2, + &p6_pclk3, + &p6_pclk4, + &p6_pclk5, + &p6_pclk6, + &p6_pclk7, +}; + +/* List of clocks which might be enabled from the bootloader */ + +/* + * SOC settings enable bus + kernel clocks of all periphs without + * properly configuring the parents of the kernel clocks for all units. + * Enable and Disable them all to get them into a known and working state. + */ +static struct clk *loader_enabled_clk[] __initdata = { + /* periph 1 */ + &p1_uart0_clk, + &p1_uart1_clk, + &p1_i2c1_clk, + &p1_msp0_clk, + &p1_msp1_clk, + &p1_sdi0_clk, + &p1_i2c2_clk, + &p1_pclk7, /* spi3 */ + &p1_pclk9, /* gpioctrl */ + &p1_i2c4_clk, + + /* periph 2 */ + &p2_i2c3_clk, + &p2_pclk1, /* spi2 */ + &p2_pclk2, /* spi1 */ + /* pwl has an unknown kclk parent, ignore it */ + &p2_sdi4_clk, + &p2_msp2_clk, + &p2_sdi1_clk, + &p2_sdi3_clk, + &p2_pclk8, /* spi0 */ + &p2_ssirx_kclk, /* hsir kernel */ + &p2_ssitx_kclk, /* hsit kernel */ + &p2_pclk9, /* hsir bus */ + &p2_pclk10, /* hsit bus */ + &p2_pclk11, /* gpioctrl */ + /* periph 3 */ + &p3_pclk0, /* fsmc */ + &p3_ssp0_clk, + &p3_ssp1_clk, + &p3_i2c0_clk, + &p3_sdi2_clk, + &p3_ske_clk, + &p3_uart2_clk, + &p3_sdi5_clk, + &p3_pclk8, /* gpio */ + /* periph 5 */ + &p5_pclk0, /* usb */ + &p5_pclk1, /* gpio */ + /* periph 6 */ + /* Leave out rng, cryp0, hash0 and pka */ + &p6_pclk4, /* hash1 */ + &p6_pclk5, /* cr */ + &p6_mtu0_clk, + &p6_mtu1_clk, + /* periph 7 */ + &per7clk, /* PERIPH7 */ + + &bmlclk, /* BML */ + &dsialtclk, /* dsialt */ + &hsirxclk, /* hsirx */ + &hsitxclk, /* hsitx */ + &ipi2cclk, /* ipi2 */ + &lcdclk, /* mcde */ + &b2r2clk, /* b2r2_bus */ +}; + +static int __init init_clock_states(void) +{ + unsigned int i; + /* + * Disable peripheral clocks enabled by bootloader/default + * but without drivers + */ + for (i = 0; i < ARRAY_SIZE(loader_enabled_clk); i++) + if (!clk_enable(loader_enabled_clk[i])) + clk_disable(loader_enabled_clk[i]); + + /* + * APEATCLK and APETRACECLK are enabled at boot and needed + * in order to debug with Lauterbach + */ + if (!clk_enable(&apeatclk)) { + if (!ux500_jtag_enabled()) + clk_disable(&apeatclk); + } + if (!clk_enable(&apetraceclk)) { + if (!ux500_jtag_enabled()) + clk_disable(&apetraceclk); + } + + INIT_DELAYED_WORK(&sysclk_disable_work, sysclk_init_disable); + schedule_delayed_work(&sysclk_disable_work, 10 * HZ); + + return 0; +} +late_initcall(init_clock_states); + +static void __init configure_c2_clocks(void) +{ + sgaclk.parent = &soc0_pll; + sgaclk.mutex = &soc0_pll_mutex; +} + +int __init db8500_clk_init(void) +{ + struct prcmu_fw_version *fw_version; + + /* + * Disable pwl's and slimbus' bus and kernel clocks without touching + * any parents. Because for slimbus, the prcmu fw has not correctly + * configured the clocks at boot and for pwl the kclk parent + * is unknown. + */ + + /* slimbus' bus and kernel clocks */ + writel(1 << 8, __io_address(U8500_CLKRST1_BASE) + PRCC_PCKDIS); + writel(1 << 8, __io_address(U8500_CLKRST1_BASE) + PRCC_KCKDIS); + /* pwl's bus and kernel clocks */ + writel(1 << 3, __io_address(U8500_CLKRST2_BASE) + PRCC_PCKDIS); + writel(1 << 1, __io_address(U8500_CLKRST2_BASE) + PRCC_KCKDIS); + + fw_version = prcmu_get_fw_version(); + if (fw_version != NULL) + switch (fw_version->project) { + case PRCMU_FW_PROJECT_U8500_C2: + case PRCMU_FW_PROJECT_U9500_C2: + case PRCMU_FW_PROJECT_U8520: + configure_c2_clocks(); + break; + default: + break; + } + clkdev_add_table(u8500_v2_sysclks, + ARRAY_SIZE(u8500_v2_sysclks)); + clkdev_add_table(u8500_clocks, + ARRAY_SIZE(u8500_clocks)); +#ifdef CONFIG_DEBUG_FS + clk_debugfs_add_table(u8500_v2_sysclks, ARRAY_SIZE(u8500_v2_sysclks)); + clk_debugfs_add_table(u8500_clocks, ARRAY_SIZE(u8500_clocks)); +#endif + return 0; +} + +int __init db8500_clk_debug_init(void) +{ + return dbx500_clk_debug_init(db8500_dbg_clks, + ARRAY_SIZE(db8500_dbg_clks)); +} diff --git a/arch/arm/mach-ux500/clock-debug.c b/arch/arm/mach-ux500/clock-debug.c new file mode 100644 index 00000000000..1ebc69fe061 --- /dev/null +++ b/arch/arm/mach-ux500/clock-debug.c @@ -0,0 +1,237 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + * Author: Mattias Nilsson <mattias.i.nilsson@stericsson.com> for ST-Ericsson + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/clk.h> +#include <mach/hardware.h> + +#include "clock.h" + +struct clk_debug_info { + struct clk *clk; + struct dentry *dir; + struct dentry *enable; + struct dentry *requests; + int enabled; +}; + +#ifdef CONFIG_DEBUG_FS + +static struct dentry *clk_dir; +static struct dentry *clk_show; +static struct dentry *clk_show_enabled_only; + +static struct clk_debug_info *cdi; +static int num_clks; + +static int clk_show_print(struct seq_file *s, void *p) +{ + int i; + int enabled_only = (int)s->private; + + seq_printf(s, "\n%-20s %10s %s\n", "name", "rate", + "enabled (kernel + debug)"); + for (i = 0; i < num_clks; i++) { + if (enabled_only && !cdi[i].clk->enabled) + continue; + seq_printf(s, + "%-20s %10lu %5d + %d\n", + cdi[i].clk->name, + clk_get_rate(cdi[i].clk), + cdi[i].clk->enabled - cdi[i].enabled, + cdi[i].enabled); + } + + return 0; +} + +static int clk_show_open(struct inode *inode, struct file *file) +{ + return single_open(file, clk_show_print, inode->i_private); +} + +static int clk_enable_print(struct seq_file *s, void *p) +{ + struct clk_debug_info *cdi = s->private; + + return seq_printf(s, "%d\n", cdi->enabled); +} + +static int clk_enable_open(struct inode *inode, struct file *file) +{ + return single_open(file, clk_enable_print, inode->i_private); +} + +static ssize_t clk_enable_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct clk_debug_info *cdi; + long user_val; + int err; + + cdi = ((struct seq_file *)(file->private_data))->private; + + err = kstrtol_from_user(user_buf, count, 0, &user_val); + + if (err) + return err; + + if ((user_val > 0) && (!cdi->enabled)) { + err = clk_enable(cdi->clk); + if (err) { + pr_err("clock: clk_enable(%s) failed.\n", + cdi->clk->name); + return -EFAULT; + } + cdi->enabled = 1; + } else if ((user_val <= 0) && (cdi->enabled)) { + clk_disable(cdi->clk); + cdi->enabled = 0; + } + return count; +} + +static int clk_requests_print(struct seq_file *s, void *p) +{ + struct clk_debug_info *cdi = s->private; + + return seq_printf(s, "%d\n", cdi->clk->enabled); +} + +static int clk_requests_open(struct inode *inode, struct file *file) +{ + return single_open(file, clk_requests_print, inode->i_private); +} + +static const struct file_operations clk_enable_fops = { + .open = clk_enable_open, + .write = clk_enable_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations clk_requests_fops = { + .open = clk_requests_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations clk_show_fops = { + .open = clk_show_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int create_clk_dirs(struct clk_debug_info *cdi, int size) +{ + int i; + + for (i = 0; i < size; i++) { + cdi[i].dir = debugfs_create_dir(cdi[i].clk->name, clk_dir); + if (!cdi[i].dir) + goto no_dir; + } + + for (i = 0; i < size; i++) { + cdi[i].enable = debugfs_create_file("enable", + (S_IRUGO | S_IWUGO), + cdi[i].dir, &cdi[i], + &clk_enable_fops); + if (!cdi[i].enable) + goto no_enable; + } + for (i = 0; i < size; i++) { + cdi[i].requests = debugfs_create_file("requests", S_IRUGO, + cdi[i].dir, &cdi[i], + &clk_requests_fops); + if (!cdi[i].requests) + goto no_requests; + } + return 0; + +no_requests: + while (i--) + debugfs_remove(cdi[i].requests); + i = size; +no_enable: + while (i--) + debugfs_remove(cdi[i].enable); + i = size; +no_dir: + while (i--) + debugfs_remove(cdi[i].dir); + + return -ENOMEM; +} + +int __init dbx500_clk_debug_init(struct clk **clks, int num) +{ + int i; + + cdi = kcalloc(sizeof(struct clk_debug_info), num, GFP_KERNEL); + if (!cdi) + return -ENOMEM; + + for (i = 0; i < num; i++) + cdi[i].clk = clks[i]; + + num_clks = num; + + clk_dir = debugfs_create_dir("clk", NULL); + if (!clk_dir) + goto no_dir; + + clk_show = debugfs_create_file("show", S_IRUGO, clk_dir, (void *)0, + &clk_show_fops); + if (!clk_show) + goto no_show; + + clk_show_enabled_only = debugfs_create_file("show-enabled-only", + S_IRUGO, clk_dir, (void *)1, + &clk_show_fops); + if (!clk_show_enabled_only) + goto no_enabled_only; + + if (create_clk_dirs(cdi, num)) + goto no_clks; + + return 0; + +no_clks: + debugfs_remove(clk_show_enabled_only); +no_enabled_only: + debugfs_remove(clk_show); +no_show: + debugfs_remove(clk_dir); +no_dir: + kfree(cdi); + return -ENOMEM; +} + +static int __init clk_debug_init(void) +{ + if (cpu_is_u8500()) + db8500_clk_debug_init(); + else if (cpu_is_u5500()) + db5500_clk_debug_init(); + + return 0; +} +module_init(clk_debug_init); + +#endif /* CONFIG_DEBUG_FS */ diff --git a/arch/arm/mach-ux500/clock.c b/arch/arm/mach-ux500/clock.c index ec35f0aa566..b8bac2d6966 100644 --- a/arch/arm/mach-ux500/clock.c +++ b/arch/arm/mach-ux500/clock.c @@ -7,18 +7,11 @@ * published by the Free Software Foundation. */ #include <linux/module.h> -#include <linux/kernel.h> -#include <linux/list.h> #include <linux/errno.h> -#include <linux/err.h> -#include <linux/clk.h> #include <linux/io.h> -#include <linux/clkdev.h> -#include <linux/cpufreq.h> - -#include <plat/mtu.h> -#include <mach/hardware.h> -#include "clock.h" +#include <linux/spinlock.h> +#include <linux/mfd/abx500/ab8500-sysctrl.h> +#include <linux/mfd/dbx500-prcmu.h> #ifdef CONFIG_DEBUG_FS #include <linux/debugfs.h> @@ -26,477 +19,495 @@ static LIST_HEAD(clk_list); #endif -#define PRCC_PCKEN 0x00 -#define PRCC_PCKDIS 0x04 -#define PRCC_KCKEN 0x08 -#define PRCC_KCKDIS 0x0C - -#define PRCM_YYCLKEN0_MGT_SET 0x510 -#define PRCM_YYCLKEN1_MGT_SET 0x514 -#define PRCM_YYCLKEN0_MGT_CLR 0x518 -#define PRCM_YYCLKEN1_MGT_CLR 0x51C -#define PRCM_YYCLKEN0_MGT_VAL 0x520 -#define PRCM_YYCLKEN1_MGT_VAL 0x524 - -#define PRCM_SVAMMDSPCLK_MGT 0x008 -#define PRCM_SIAMMDSPCLK_MGT 0x00C -#define PRCM_SGACLK_MGT 0x014 -#define PRCM_UARTCLK_MGT 0x018 -#define PRCM_MSP02CLK_MGT 0x01C -#define PRCM_MSP1CLK_MGT 0x288 -#define PRCM_I2CCLK_MGT 0x020 -#define PRCM_SDMMCCLK_MGT 0x024 -#define PRCM_SLIMCLK_MGT 0x028 -#define PRCM_PER1CLK_MGT 0x02C -#define PRCM_PER2CLK_MGT 0x030 -#define PRCM_PER3CLK_MGT 0x034 -#define PRCM_PER5CLK_MGT 0x038 -#define PRCM_PER6CLK_MGT 0x03C -#define PRCM_PER7CLK_MGT 0x040 -#define PRCM_LCDCLK_MGT 0x044 -#define PRCM_BMLCLK_MGT 0x04C -#define PRCM_HSITXCLK_MGT 0x050 -#define PRCM_HSIRXCLK_MGT 0x054 -#define PRCM_HDMICLK_MGT 0x058 -#define PRCM_APEATCLK_MGT 0x05C -#define PRCM_APETRACECLK_MGT 0x060 -#define PRCM_MCDECLK_MGT 0x064 -#define PRCM_IPI2CCLK_MGT 0x068 -#define PRCM_DSIALTCLK_MGT 0x06C -#define PRCM_DMACLK_MGT 0x074 -#define PRCM_B2R2CLK_MGT 0x078 -#define PRCM_TVCLK_MGT 0x07C -#define PRCM_TCR 0x1C8 -#define PRCM_TCR_STOPPED (1 << 16) -#define PRCM_TCR_DOZE_MODE (1 << 17) -#define PRCM_UNIPROCLK_MGT 0x278 -#define PRCM_SSPCLK_MGT 0x280 -#define PRCM_RNGCLK_MGT 0x284 -#define PRCM_UICCCLK_MGT 0x27C - -#define PRCM_MGT_ENABLE (1 << 8) - -static DEFINE_SPINLOCK(clocks_lock); - -static void __clk_enable(struct clk *clk) -{ - if (clk->enabled++ == 0) { - if (clk->parent_cluster) - __clk_enable(clk->parent_cluster); - - if (clk->parent_periph) - __clk_enable(clk->parent_periph); - - if (clk->ops && clk->ops->enable) - clk->ops->enable(clk); +#include "clock.h" +#include "prcc.h" + +DEFINE_MUTEX(clk_opp100_mutex); +static DEFINE_SPINLOCK(clk_spin_lock); +#define NO_LOCK &clk_spin_lock + +static void __iomem *prcmu_base; + +static void __clk_lock(struct clk *clk, void *last_lock, unsigned long *flags) +{ + if (clk->mutex != last_lock) { + if (clk->mutex == NULL) + spin_lock_irqsave(&clk_spin_lock, *flags); + else + mutex_lock(clk->mutex); } } -int clk_enable(struct clk *clk) +static void __clk_unlock(struct clk *clk, void *last_lock, unsigned long flags) +{ + if (clk->mutex != last_lock) { + if (clk->mutex == NULL) + spin_unlock_irqrestore(&clk_spin_lock, flags); + else + mutex_unlock(clk->mutex); + } +} + +void __clk_disable(struct clk *clk, void *current_lock) { unsigned long flags; - spin_lock_irqsave(&clocks_lock, flags); - __clk_enable(clk); - spin_unlock_irqrestore(&clocks_lock, flags); + if (clk == NULL) + return; - return 0; + __clk_lock(clk, current_lock, &flags); + + if (clk->enabled && (--clk->enabled == 0)) { + if ((clk->ops != NULL) && (clk->ops->disable != NULL)) + clk->ops->disable(clk); + __clk_disable(clk->parent, clk->mutex); + __clk_disable(clk->bus_parent, clk->mutex); + } + + __clk_unlock(clk, current_lock, flags); + + return; } -EXPORT_SYMBOL(clk_enable); -static void __clk_disable(struct clk *clk) +int __clk_enable(struct clk *clk, void *current_lock) { - if (--clk->enabled == 0) { - if (clk->ops && clk->ops->disable) - clk->ops->disable(clk); + int err; + unsigned long flags; - if (clk->parent_periph) - __clk_disable(clk->parent_periph); + if (clk == NULL) + return 0; - if (clk->parent_cluster) - __clk_disable(clk->parent_cluster); + __clk_lock(clk, current_lock, &flags); + + if (!clk->enabled) { + err = __clk_enable(clk->bus_parent, clk->mutex); + if (unlikely(err)) + goto bus_parent_error; + + err = __clk_enable(clk->parent, clk->mutex); + if (unlikely(err)) + goto parent_error; + + if ((clk->ops != NULL) && (clk->ops->enable != NULL)) { + err = clk->ops->enable(clk); + if (unlikely(err)) + goto enable_error; + } } + clk->enabled++; + + __clk_unlock(clk, current_lock, flags); + + return 0; + +enable_error: + __clk_disable(clk->parent, clk->mutex); +parent_error: + __clk_disable(clk->bus_parent, clk->mutex); +bus_parent_error: + + __clk_unlock(clk, current_lock, flags); + + return err; } -void clk_disable(struct clk *clk) +unsigned long __clk_get_rate(struct clk *clk, void *current_lock) { + unsigned long rate; unsigned long flags; - WARN_ON(!clk->enabled); + if (clk == NULL) + return 0; + + __clk_lock(clk, current_lock, &flags); - spin_lock_irqsave(&clocks_lock, flags); - __clk_disable(clk); - spin_unlock_irqrestore(&clocks_lock, flags); + if ((clk->ops != NULL) && (clk->ops->get_rate != NULL)) + rate = clk->ops->get_rate(clk); + else if (clk->rate) + rate = clk->rate; + else + rate = __clk_get_rate(clk->parent, clk->mutex); + + __clk_unlock(clk, current_lock, flags); + + return rate; } -EXPORT_SYMBOL(clk_disable); -/* - * The MTU has a separate, rather complex muxing setup - * with alternative parents (peripheral cluster or - * ULP or fixed 32768 Hz) depending on settings - */ -static unsigned long clk_mtu_get_rate(struct clk *clk) +static long __clk_round_rate(struct clk *clk, unsigned long rate) { - void __iomem *addr; - u32 tcr; - int mtu = (int) clk->data; - /* - * One of these is selected eventually - * TODO: Replace the constant with a reference - * to the ULP source once this is modeled. - */ - unsigned long clk32k = 32768; - unsigned long mturate; - unsigned long retclk; - - if (cpu_is_u5500()) - addr = __io_address(U5500_PRCMU_BASE); - else if (cpu_is_u8500()) - addr = __io_address(U8500_PRCMU_BASE); - else - ux500_unknown_soc(); + if ((clk->ops != NULL) && (clk->ops->round_rate != NULL)) + return clk->ops->round_rate(clk, rate); - /* - * On a startup, always conifgure the TCR to the doze mode; - * bootloaders do it for us. Do this in the kernel too. - */ - writel(PRCM_TCR_DOZE_MODE, addr + PRCM_TCR); + return -ENOSYS; +} - tcr = readl(addr + PRCM_TCR); +static int __clk_set_rate(struct clk *clk, unsigned long rate) +{ + if ((clk->ops != NULL) && (clk->ops->set_rate != NULL)) + return clk->ops->set_rate(clk, rate); - /* Get the rate from the parent as a default */ - if (clk->parent_periph) - mturate = clk_get_rate(clk->parent_periph); - else if (clk->parent_cluster) - mturate = clk_get_rate(clk->parent_cluster); - else - /* We need to be connected SOMEWHERE */ - BUG(); + return -ENOSYS; +} - /* Return the clock selected for this MTU */ - if (tcr & (1 << mtu)) - retclk = clk32k; - else - retclk = mturate; +int clk_enable(struct clk *clk) +{ + if (clk == NULL) + return -EINVAL; - pr_info("MTU%d clock rate: %lu Hz\n", mtu, retclk); - return retclk; + return __clk_enable(clk, NO_LOCK); } +EXPORT_SYMBOL(clk_enable); -unsigned long clk_get_rate(struct clk *clk) +void clk_disable(struct clk *clk) { - unsigned long rate; - /* - * If there is a custom getrate callback for this clock, - * it will take precedence. - */ - if (clk->get_rate) - return clk->get_rate(clk); - - if (clk->ops && clk->ops->get_rate) - return clk->ops->get_rate(clk); - - rate = clk->rate; - if (!rate) { - if (clk->parent_periph) - rate = clk_get_rate(clk->parent_periph); - else if (clk->parent_cluster) - rate = clk_get_rate(clk->parent_cluster); - } + if (clk == NULL) + return; - return rate; + WARN_ON(!clk->enabled); + __clk_disable(clk, NO_LOCK); +} +EXPORT_SYMBOL(clk_disable); + +unsigned long clk_get_rate(struct clk *clk) +{ + if (clk == NULL) + return 0; + + return __clk_get_rate(clk, NO_LOCK); } EXPORT_SYMBOL(clk_get_rate); long clk_round_rate(struct clk *clk, unsigned long rate) { - /*TODO*/ - return rate; + long rounded_rate; + unsigned long flags; + + if (clk == NULL) + return -EINVAL; + + __clk_lock(clk, NO_LOCK, &flags); + + rounded_rate = __clk_round_rate(clk, rate); + + __clk_unlock(clk, NO_LOCK, flags); + + return rounded_rate; } EXPORT_SYMBOL(clk_round_rate); +long clk_round_rate_rec(struct clk *clk, unsigned long rate) +{ + long rounded_rate; + unsigned long flags; + + if ((clk == NULL) || (clk->parent == NULL)) + return -EINVAL; + + __clk_lock(clk->parent, clk->mutex, &flags); + + rounded_rate = __clk_round_rate(clk->parent, rate); + + __clk_unlock(clk->parent, clk->mutex, flags); + + return rounded_rate; +} + +static void lock_parent_rate(struct clk *clk) +{ + unsigned long flags; + + if (clk->parent == NULL) + return; + + __clk_lock(clk->parent, clk->mutex, &flags); + + lock_parent_rate(clk->parent); + clk->parent->rate_locked++; + + __clk_unlock(clk->parent, clk->mutex, flags); +} + +static void unlock_parent_rate(struct clk *clk) +{ + unsigned long flags; + + if (clk->parent == NULL) + return; + + __clk_lock(clk->parent, clk->mutex, &flags); + + unlock_parent_rate(clk->parent); + clk->parent->rate_locked--; + + __clk_unlock(clk->parent, clk->mutex, flags); +} + int clk_set_rate(struct clk *clk, unsigned long rate) { - clk->rate = rate; - return 0; + int err; + unsigned long flags; + + if (clk == NULL) + return -EINVAL; + + __clk_lock(clk, NO_LOCK, &flags); + + if (clk->enabled) { + err = -EBUSY; + goto unlock_and_return; + } + if (clk->rate_locked) { + err = -EAGAIN; + goto unlock_and_return; + } + + lock_parent_rate(clk); + err = __clk_set_rate(clk, rate); + unlock_parent_rate(clk); + +unlock_and_return: + __clk_unlock(clk, NO_LOCK, flags); + + return err; } EXPORT_SYMBOL(clk_set_rate); +int clk_set_rate_rec(struct clk *clk, unsigned long rate) +{ + int err; + unsigned long flags; + + if ((clk == NULL) || (clk->parent == NULL)) + return -EINVAL; + + __clk_lock(clk->parent, clk->mutex, &flags); + + if (clk->parent->enabled) { + err = -EBUSY; + goto unlock_and_return; + } + if (clk->parent->rate_locked != 1) { + err = -EAGAIN; + goto unlock_and_return; + } + err = __clk_set_rate(clk->parent, rate); + +unlock_and_return: + __clk_unlock(clk->parent, clk->mutex, flags); + + return err; +} + int clk_set_parent(struct clk *clk, struct clk *parent) { - /*TODO*/ - return -ENOSYS; + int err = 0; + unsigned long flags; + struct clk **p; + + if ((clk == NULL) || (clk->parents == NULL)) + return -EINVAL; + for (p = clk->parents; *p != parent; p++) { + if (*p == NULL) /* invalid parent */ + return -EINVAL; + } + + __clk_lock(clk, NO_LOCK, &flags); + + if ((clk->ops != NULL) && (clk->ops->set_parent != NULL)) { + err = clk->ops->set_parent(clk, parent); + if (err) + goto unlock_and_return; + } else if (clk->enabled) { + err = __clk_enable(parent, clk->mutex); + if (err) + goto unlock_and_return; + __clk_disable(clk->parent, clk->mutex); + } + + clk->parent = parent; + +unlock_and_return: + __clk_unlock(clk, NO_LOCK, flags); + + return err; } -EXPORT_SYMBOL(clk_set_parent); -static void clk_prcmu_enable(struct clk *clk) +/* PRCMU clock operations. */ + +static int prcmu_clk_enable(struct clk *clk) { - void __iomem *cg_set_reg = __io_address(U8500_PRCMU_BASE) - + PRCM_YYCLKEN0_MGT_SET + clk->prcmu_cg_off; + return prcmu_request_clock(clk->cg_sel, true); +} + +static void prcmu_clk_disable(struct clk *clk) +{ + if (prcmu_request_clock(clk->cg_sel, false)) { + pr_err("clock: %s failed to disable %s.\n", __func__, + clk->name); + } +} + +static int request_ape_opp100(bool enable) +{ + static unsigned int requests; + + if (enable) { + if (0 == requests++) { + return prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, + "clock", 100); + } + } else if (1 == requests--) { + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "clock"); + } + return 0; +} + +static int prcmu_opp100_clk_enable(struct clk *clk) +{ + int r; - writel(1 << clk->prcmu_cg_bit, cg_set_reg); + r = request_ape_opp100(true); + if (r) { + pr_err("clock: %s failed to request APE OPP 100%% for %s.\n", + __func__, clk->name); + return r; + } + return prcmu_request_clock(clk->cg_sel, true); +} + +static void prcmu_opp100_clk_disable(struct clk *clk) +{ + if (prcmu_request_clock(clk->cg_sel, false)) + goto out_error; + if (request_ape_opp100(false)) + goto out_error; + return; + +out_error: + pr_err("clock: %s failed to disable %s.\n", __func__, clk->name); +} + +static unsigned long prcmu_clk_get_rate(struct clk *clk) +{ + return prcmu_clock_rate(clk->cg_sel); } -static void clk_prcmu_disable(struct clk *clk) +static long prcmu_clk_round_rate(struct clk *clk, unsigned long rate) { - void __iomem *cg_clr_reg = __io_address(U8500_PRCMU_BASE) - + PRCM_YYCLKEN0_MGT_CLR + clk->prcmu_cg_off; + return prcmu_round_clock_rate(clk->cg_sel, rate); +} - writel(1 << clk->prcmu_cg_bit, cg_clr_reg); +static int prcmu_clk_set_rate(struct clk *clk, unsigned long rate) +{ + return prcmu_set_clock_rate(clk->cg_sel, rate); } -static struct clkops clk_prcmu_ops = { - .enable = clk_prcmu_enable, - .disable = clk_prcmu_disable, +struct clkops prcmu_clk_ops = { + .enable = prcmu_clk_enable, + .disable = prcmu_clk_disable, + .get_rate = prcmu_clk_get_rate, +}; + +struct clkops prcmu_scalable_clk_ops = { + .enable = prcmu_clk_enable, + .disable = prcmu_clk_disable, + .get_rate = prcmu_clk_get_rate, + .round_rate = prcmu_clk_round_rate, + .set_rate = prcmu_clk_set_rate, }; -static unsigned int clkrst_base[] = { - [1] = U8500_CLKRST1_BASE, - [2] = U8500_CLKRST2_BASE, - [3] = U8500_CLKRST3_BASE, - [5] = U8500_CLKRST5_BASE, - [6] = U8500_CLKRST6_BASE, +struct clkops prcmu_opp100_clk_ops = { + .enable = prcmu_opp100_clk_enable, + .disable = prcmu_opp100_clk_disable, + .get_rate = prcmu_clk_get_rate, }; -static void clk_prcc_enable(struct clk *clk) +/* PRCC clock operations. */ + +static int prcc_pclk_enable(struct clk *clk) { - void __iomem *addr = __io_address(clkrst_base[clk->cluster]); + void __iomem *io_base = __io_address(clk->io_base); + + writel(clk->cg_sel, (io_base + PRCC_PCKEN)); + while (!(readl(io_base + PRCC_PCKSR) & clk->cg_sel)) + cpu_relax(); + return 0; +} - if (clk->prcc_kernel != -1) - writel(1 << clk->prcc_kernel, addr + PRCC_KCKEN); +static void prcc_pclk_disable(struct clk *clk) +{ + void __iomem *io_base = __io_address(clk->io_base); - if (clk->prcc_bus != -1) - writel(1 << clk->prcc_bus, addr + PRCC_PCKEN); + writel(clk->cg_sel, (io_base + PRCC_PCKDIS)); } -static void clk_prcc_disable(struct clk *clk) +struct clkops prcc_pclk_ops = { + .enable = prcc_pclk_enable, + .disable = prcc_pclk_disable, +}; + +static int prcc_kclk_enable(struct clk *clk) { - void __iomem *addr = __io_address(clkrst_base[clk->cluster]); + int err; + void __iomem *io_base = __io_address(clk->io_base); + + err = __clk_enable(clk->clock, clk->mutex); + if (err) + return err; - if (clk->prcc_bus != -1) - writel(1 << clk->prcc_bus, addr + PRCC_PCKDIS); + writel(clk->cg_sel, (io_base + PRCC_KCKEN)); + while (!(readl(io_base + PRCC_KCKSR) & clk->cg_sel)) + cpu_relax(); - if (clk->prcc_kernel != -1) - writel(1 << clk->prcc_kernel, addr + PRCC_KCKDIS); + __clk_disable(clk->clock, clk->mutex); + + return 0; } -static struct clkops clk_prcc_ops = { - .enable = clk_prcc_enable, - .disable = clk_prcc_disable, +static void prcc_kclk_disable(struct clk *clk) +{ + void __iomem *io_base = __io_address(clk->io_base); + + (void)__clk_enable(clk->clock, clk->mutex); + writel(clk->cg_sel, (io_base + PRCC_KCKDIS)); + __clk_disable(clk->clock, clk->mutex); +} + +struct clkops prcc_kclk_ops = { + .enable = prcc_kclk_enable, + .disable = prcc_kclk_disable, }; -static struct clk clk_32khz = { - .name = "clk_32khz", - .rate = 32000, +struct clkops prcc_kclk_rec_ops = { + .enable = prcc_kclk_enable, + .disable = prcc_kclk_disable, + .round_rate = clk_round_rate_rec, + .set_rate = clk_set_rate_rec, }; -/* - * PRCMU level clock gating - */ +#ifdef CONFIG_CPU_FREQ +extern unsigned long dbx500_cpufreq_getfreq(void); -/* Bank 0 */ -static DEFINE_PRCMU_CLK(svaclk, 0x0, 2, SVAMMDSPCLK); -static DEFINE_PRCMU_CLK(siaclk, 0x0, 3, SIAMMDSPCLK); -static DEFINE_PRCMU_CLK(sgaclk, 0x0, 4, SGACLK); -static DEFINE_PRCMU_CLK_RATE(uartclk, 0x0, 5, UARTCLK, 38400000); -static DEFINE_PRCMU_CLK(msp02clk, 0x0, 6, MSP02CLK); -static DEFINE_PRCMU_CLK(msp1clk, 0x0, 7, MSP1CLK); /* v1 */ -static DEFINE_PRCMU_CLK_RATE(i2cclk, 0x0, 8, I2CCLK, 48000000); -static DEFINE_PRCMU_CLK_RATE(sdmmcclk, 0x0, 9, SDMMCCLK, 100000000); -static DEFINE_PRCMU_CLK(slimclk, 0x0, 10, SLIMCLK); -static DEFINE_PRCMU_CLK(per1clk, 0x0, 11, PER1CLK); -static DEFINE_PRCMU_CLK(per2clk, 0x0, 12, PER2CLK); -static DEFINE_PRCMU_CLK(per3clk, 0x0, 13, PER3CLK); -static DEFINE_PRCMU_CLK(per5clk, 0x0, 14, PER5CLK); -static DEFINE_PRCMU_CLK_RATE(per6clk, 0x0, 15, PER6CLK, 133330000); -static DEFINE_PRCMU_CLK(lcdclk, 0x0, 17, LCDCLK); -static DEFINE_PRCMU_CLK(bmlclk, 0x0, 18, BMLCLK); -static DEFINE_PRCMU_CLK(hsitxclk, 0x0, 19, HSITXCLK); -static DEFINE_PRCMU_CLK(hsirxclk, 0x0, 20, HSIRXCLK); -static DEFINE_PRCMU_CLK(hdmiclk, 0x0, 21, HDMICLK); -static DEFINE_PRCMU_CLK(apeatclk, 0x0, 22, APEATCLK); -static DEFINE_PRCMU_CLK(apetraceclk, 0x0, 23, APETRACECLK); -static DEFINE_PRCMU_CLK(mcdeclk, 0x0, 24, MCDECLK); -static DEFINE_PRCMU_CLK(ipi2clk, 0x0, 25, IPI2CCLK); -static DEFINE_PRCMU_CLK(dsialtclk, 0x0, 26, DSIALTCLK); /* v1 */ -static DEFINE_PRCMU_CLK(dmaclk, 0x0, 27, DMACLK); -static DEFINE_PRCMU_CLK(b2r2clk, 0x0, 28, B2R2CLK); -static DEFINE_PRCMU_CLK(tvclk, 0x0, 29, TVCLK); -static DEFINE_PRCMU_CLK(uniproclk, 0x0, 30, UNIPROCLK); /* v1 */ -static DEFINE_PRCMU_CLK_RATE(sspclk, 0x0, 31, SSPCLK, 48000000); /* v1 */ - -/* Bank 1 */ -static DEFINE_PRCMU_CLK(rngclk, 0x4, 0, RNGCLK); /* v1 */ -static DEFINE_PRCMU_CLK(uiccclk, 0x4, 1, UICCCLK); /* v1 */ +unsigned long clk_smp_twd_get_rate(struct clk *clk) +{ + return dbx500_cpufreq_getfreq() / 2; +} -/* - * PRCC level clock gating - * Format: per#, clk, PCKEN bit, KCKEN bit, parent - */ +static struct clkops clk_smp_twd_ops = { + .get_rate = clk_smp_twd_get_rate, +}; -/* Peripheral Cluster #1 */ -static DEFINE_PRCC_CLK(1, i2c4, 10, 9, &clk_i2cclk); -static DEFINE_PRCC_CLK(1, gpio0, 9, -1, NULL); -static DEFINE_PRCC_CLK(1, slimbus0, 8, 8, &clk_slimclk); -static DEFINE_PRCC_CLK(1, spi3, 7, -1, NULL); -static DEFINE_PRCC_CLK(1, i2c2, 6, 6, &clk_i2cclk); -static DEFINE_PRCC_CLK(1, sdi0, 5, 5, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(1, msp1, 4, 4, &clk_msp1clk); -static DEFINE_PRCC_CLK(1, msp0, 3, 3, &clk_msp02clk); -static DEFINE_PRCC_CLK(1, i2c1, 2, 2, &clk_i2cclk); -static DEFINE_PRCC_CLK(1, uart1, 1, 1, &clk_uartclk); -static DEFINE_PRCC_CLK(1, uart0, 0, 0, &clk_uartclk); - -/* Peripheral Cluster #2 */ -static DEFINE_PRCC_CLK(2, gpio1, 11, -1, NULL); -static DEFINE_PRCC_CLK(2, ssitx, 10, 7, NULL); -static DEFINE_PRCC_CLK(2, ssirx, 9, 6, NULL); -static DEFINE_PRCC_CLK(2, spi0, 8, -1, NULL); -static DEFINE_PRCC_CLK(2, sdi3, 7, 5, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, sdi1, 6, 4, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, msp2, 5, 3, &clk_msp02clk); -static DEFINE_PRCC_CLK(2, sdi4, 4, 2, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, pwl, 3, 1, NULL); -static DEFINE_PRCC_CLK(2, spi1, 2, -1, NULL); -static DEFINE_PRCC_CLK(2, spi2, 1, -1, NULL); -static DEFINE_PRCC_CLK(2, i2c3, 0, 0, &clk_i2cclk); - -/* Peripheral Cluster #3 */ -static DEFINE_PRCC_CLK(3, gpio2, 8, -1, NULL); -static DEFINE_PRCC_CLK(3, sdi5, 7, 7, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(3, uart2, 6, 6, &clk_uartclk); -static DEFINE_PRCC_CLK(3, ske, 5, 5, &clk_32khz); -static DEFINE_PRCC_CLK(3, sdi2, 4, 4, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(3, i2c0, 3, 3, &clk_i2cclk); -static DEFINE_PRCC_CLK(3, ssp1, 2, 2, &clk_sspclk); -static DEFINE_PRCC_CLK(3, ssp0, 1, 1, &clk_sspclk); -static DEFINE_PRCC_CLK(3, fsmc, 0, -1, NULL); - -/* Peripheral Cluster #4 is in the always on domain */ - -/* Peripheral Cluster #5 */ -static DEFINE_PRCC_CLK(5, gpio3, 1, -1, NULL); -static DEFINE_PRCC_CLK(5, usb, 0, 0, NULL); - -/* Peripheral Cluster #6 */ - -/* MTU ID in data */ -static DEFINE_PRCC_CLK_CUSTOM(6, mtu1, 8, -1, NULL, clk_mtu_get_rate, 1); -static DEFINE_PRCC_CLK_CUSTOM(6, mtu0, 7, -1, NULL, clk_mtu_get_rate, 0); -static DEFINE_PRCC_CLK(6, cfgreg, 6, 6, NULL); -static DEFINE_PRCC_CLK(6, hash1, 5, -1, NULL); -static DEFINE_PRCC_CLK(6, unipro, 4, 1, &clk_uniproclk); -static DEFINE_PRCC_CLK(6, pka, 3, -1, NULL); -static DEFINE_PRCC_CLK(6, hash0, 2, -1, NULL); -static DEFINE_PRCC_CLK(6, cryp0, 1, -1, NULL); -static DEFINE_PRCC_CLK(6, rng, 0, 0, &clk_rngclk); - -static struct clk clk_dummy_apb_pclk = { - .name = "apb_pclk", +static struct clk clk_smp_twd = { + .name = "smp_twd", + .ops = &clk_smp_twd_ops, }; -static struct clk_lookup u8500_clks[] = { - CLK(dummy_apb_pclk, NULL, "apb_pclk"), - - /* Peripheral Cluster #1 */ - CLK(gpio0, "gpio.0", NULL), - CLK(gpio0, "gpio.1", NULL), - CLK(slimbus0, "slimbus0", NULL), - CLK(i2c2, "nmk-i2c.2", NULL), - CLK(sdi0, "sdi0", NULL), - CLK(msp0, "msp0", NULL), - CLK(i2c1, "nmk-i2c.1", NULL), - CLK(uart1, "uart1", NULL), - CLK(uart0, "uart0", NULL), - - /* Peripheral Cluster #3 */ - CLK(gpio2, "gpio.2", NULL), - CLK(gpio2, "gpio.3", NULL), - CLK(gpio2, "gpio.4", NULL), - CLK(gpio2, "gpio.5", NULL), - CLK(sdi5, "sdi5", NULL), - CLK(uart2, "uart2", NULL), - CLK(ske, "ske", NULL), - CLK(ske, "nmk-ske-keypad", NULL), - CLK(sdi2, "sdi2", NULL), - CLK(i2c0, "nmk-i2c.0", NULL), - CLK(fsmc, "fsmc", NULL), - - /* Peripheral Cluster #5 */ - CLK(gpio3, "gpio.8", NULL), - - /* Peripheral Cluster #6 */ - CLK(hash1, "hash1", NULL), - CLK(pka, "pka", NULL), - CLK(hash0, "hash0", NULL), - CLK(cryp0, "cryp0", NULL), - - /* PRCMU level clock gating */ - - /* Bank 0 */ - CLK(svaclk, "sva", NULL), - CLK(siaclk, "sia", NULL), - CLK(sgaclk, "sga", NULL), - CLK(slimclk, "slim", NULL), - CLK(lcdclk, "lcd", NULL), - CLK(bmlclk, "bml", NULL), - CLK(hsitxclk, "stm-hsi.0", NULL), - CLK(hsirxclk, "stm-hsi.1", NULL), - CLK(hdmiclk, "hdmi", NULL), - CLK(apeatclk, "apeat", NULL), - CLK(apetraceclk, "apetrace", NULL), - CLK(mcdeclk, "mcde", NULL), - CLK(ipi2clk, "ipi2", NULL), - CLK(dmaclk, "dma40.0", NULL), - CLK(b2r2clk, "b2r2", NULL), - CLK(tvclk, "tv", NULL), - - /* Peripheral Cluster #1 */ - CLK(i2c4, "nmk-i2c.4", NULL), - CLK(spi3, "spi3", NULL), - CLK(msp1, "msp1", NULL), - - /* Peripheral Cluster #2 */ - CLK(gpio1, "gpio.6", NULL), - CLK(gpio1, "gpio.7", NULL), - CLK(ssitx, "ssitx", NULL), - CLK(ssirx, "ssirx", NULL), - CLK(spi0, "spi0", NULL), - CLK(sdi3, "sdi3", NULL), - CLK(sdi1, "sdi1", NULL), - CLK(msp2, "msp2", NULL), - CLK(sdi4, "sdi4", NULL), - CLK(pwl, "pwl", NULL), - CLK(spi1, "spi1", NULL), - CLK(spi2, "spi2", NULL), - CLK(i2c3, "nmk-i2c.3", NULL), - - /* Peripheral Cluster #3 */ - CLK(ssp1, "ssp1", NULL), - CLK(ssp0, "ssp0", NULL), - - /* Peripheral Cluster #5 */ - CLK(usb, "musb-ux500.0", "usb"), - - /* Peripheral Cluster #6 */ - CLK(mtu1, "mtu1", NULL), - CLK(mtu0, "mtu0", NULL), - CLK(cfgreg, "cfgreg", NULL), - CLK(hash1, "hash1", NULL), - CLK(unipro, "unipro", NULL), - CLK(rng, "rng", NULL), - - /* PRCMU level clock gating */ - - /* Bank 0 */ - CLK(uniproclk, "uniproclk", NULL), - CLK(dsialtclk, "dsialt", NULL), - - /* Bank 1 */ - CLK(rngclk, "rng", NULL), - CLK(uiccclk, "uicc", NULL), +static struct clk_lookup clk_smp_twd_lookup = { + .clk = &clk_smp_twd, + .dev_id = "smp_twd", }; +#endif #ifdef CONFIG_DEBUG_FS /* @@ -585,8 +596,8 @@ err_out: static int clk_debugfs_register_one(struct clk *c) { - struct clk *pa = c->parent_periph; - struct clk *bpa = c->parent_cluster; + struct clk *pa = c->parent; + struct clk *bpa = c->bus_parent; if (!(bpa && !pa)) { c->dent = clk_debugfs_register_dir(c, @@ -610,8 +621,8 @@ static int clk_debugfs_register_one(struct clk *c) static int clk_debugfs_register(struct clk *c) { int err; - struct clk *pa = c->parent_periph; - struct clk *bpa = c->parent_cluster; + struct clk *pa = c->parent; + struct clk *bpa = c->bus_parent; if (pa && (!pa->dent && !pa->dent_bus)) { err = clk_debugfs_register(pa); @@ -658,66 +669,25 @@ err_out: late_initcall(clk_debugfs_init); #endif /* defined(CONFIG_DEBUG_FS) */ -unsigned long clk_smp_twd_rate = 500000000; - -unsigned long clk_smp_twd_get_rate(struct clk *clk) -{ - return clk_smp_twd_rate; -} - -static struct clk clk_smp_twd = { - .get_rate = clk_smp_twd_get_rate, - .name = "smp_twd", -}; - -static struct clk_lookup clk_smp_twd_lookup = { - .dev_id = "smp_twd", - .clk = &clk_smp_twd, -}; - -#ifdef CONFIG_CPU_FREQ - -static int clk_twd_cpufreq_transition(struct notifier_block *nb, - unsigned long state, void *data) -{ - struct cpufreq_freqs *f = data; - - if (state == CPUFREQ_PRECHANGE) { - /* Save frequency in simple Hz */ - clk_smp_twd_rate = (f->new * 1000) / 2; - } - - return NOTIFY_OK; -} - -static struct notifier_block clk_twd_cpufreq_nb = { - .notifier_call = clk_twd_cpufreq_transition, -}; - -static int clk_init_smp_twd_cpufreq(void) -{ - return cpufreq_register_notifier(&clk_twd_cpufreq_nb, - CPUFREQ_TRANSITION_NOTIFIER); -} -late_initcall(clk_init_smp_twd_cpufreq); - -#endif - int __init clk_init(void) { - if (cpu_is_u5500()) { - /* Clock tree for U5500 not implemented yet */ - clk_prcc_ops.enable = clk_prcc_ops.disable = NULL; - clk_prcmu_ops.enable = clk_prcmu_ops.disable = NULL; - clk_uartclk.rate = 36360000; - clk_sdmmcclk.rate = 99900000; + if (cpu_is_u8500()) { + prcmu_base = __io_address(U8500_PRCMU_BASE); + } else if (cpu_is_u5500()) { + prcmu_base = __io_address(U5500_PRCMU_BASE); + } else { + pr_err("clock: Unknown DB Asic.\n"); + return -EIO; } - clkdev_add_table(u8500_clks, ARRAY_SIZE(u8500_clks)); - clkdev_add(&clk_smp_twd_lookup); + if (cpu_is_u8500()) + db8500_clk_init(); + else if (cpu_is_u5500()) + db5500_clk_init(); -#ifdef CONFIG_DEBUG_FS - clk_debugfs_add_table(u8500_clks, ARRAY_SIZE(u8500_clks)); +#ifdef CONFIG_CPU_FREQ + clkdev_add(&clk_smp_twd_lookup); #endif + return 0; } diff --git a/arch/arm/mach-ux500/clock.h b/arch/arm/mach-ux500/clock.h index d776ada08db..2403e51dc7f 100644 --- a/arch/arm/mach-ux500/clock.h +++ b/arch/arm/mach-ux500/clock.h @@ -1,11 +1,61 @@ /* - * Copyright (C) 2010 ST-Ericsson + * Copyright (C) 2010 ST-Ericsson SA * Copyright (C) 2009 STMicroelectronics * * 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. */ +#ifndef UX500_CLOCK_H +#define UX500_CLOCK_H + +#include <linux/clkdev.h> + +/** + * struct clk + * @ops: The hardware specific operations defined for the clock. + * @name: The name of the clock. + * @mutex: The mutex to lock when operating on the clock. %NULL means that + * the common clock spinlock will be used. + * @enabled: A reference counter of the enable requests for the clock. + * @rate_locked: A rate lock counter used by clk_set_rate(). + * @opp100: A flag saying whether the clock is requested to run at the + * OPP 100%% frequency. + * @rate: The frequency of the clock. For scalable and scaling clocks, + * this is the OPP 100%% frequency. + * @io_base: An IO memory base address, meaningful only when considered + * together with the defined @ops. + * @cg_sel: Clock gate selector, meaningful only when considered together + * with the specified @ops. + * @parent: The current (or only) parent clock of the clock. + * @bus_parent: The (optional) auxiliary bus clock "parent" of the clock. + * @parents: A list of the possible parents the clock can have. This should + * be a %NULL-terminated &struct_clk array. Present if and only + * if clk_set_parent() is implemented for the clock. + * @regulator: The regulator needed to have the clock functional, if any. + * @clock: The clock needed to control the clock, if any. + */ +struct clk { + const struct clkops *ops; + const char *name; + struct mutex *mutex; + unsigned int enabled; + unsigned int rate_locked; + bool opp100; + unsigned long rate; + unsigned int io_base; + u32 cg_sel; + struct clk *parent; + struct clk *bus_parent; + struct clk **parents; + struct regulator *regulator; + struct clk *clock; + struct list_head list; +#if defined(CONFIG_DEBUG_FS) + struct dentry *dent; /* For visible tree hierarchy */ + struct dentry *dent_bus; /* For visible tree hierarchy */ +#endif +}; /** * struct clkops - ux500 clock operations @@ -18,135 +68,120 @@ * NULL, the rate in the struct clk will be used. */ struct clkops { - void (*enable) (struct clk *); - void (*disable) (struct clk *); - unsigned long (*get_rate) (struct clk *); + int (*enable)(struct clk *); + void (*disable)(struct clk *); + unsigned long (*get_rate)(struct clk *); + int (*set_rate)(struct clk *, unsigned long); + long (*round_rate)(struct clk *, unsigned long); int (*set_parent)(struct clk *, struct clk *); }; -/** - * struct clk - ux500 clock structure - * @ops: pointer to clkops struct used to control this clock - * @name: name, for debugging - * @enabled: refcount. positive if enabled, zero if disabled - * @get_rate: custom callback for getting the clock rate - * @data: custom per-clock data for example for the get_rate - * callback - * @rate: fixed rate for clocks which don't implement - * ops->getrate - * @prcmu_cg_off: address offset of the combined enable/disable register - * (used on u8500v1) - * @prcmu_cg_bit: bit in the combined enable/disable register (used on - * u8500v1) - * @prcmu_cg_mgt: address of the enable/disable register (used on - * u8500ed) - * @cluster: peripheral cluster number - * @prcc_bus: bit for the bus clock in the peripheral's CLKRST - * @prcc_kernel: bit for the kernel clock in the peripheral's CLKRST. - * -1 if no kernel clock exists. - * @parent_cluster: pointer to parent's cluster clk struct - * @parent_periph: pointer to parent's peripheral clk struct - * - * Peripherals are organised into clusters, and each cluster has an associated - * bus clock. Some peripherals also have a parent peripheral clock. - * - * In order to enable a clock for a peripheral, we need to enable: - * (1) the parent cluster (bus) clock at the PRCMU level - * (2) the parent peripheral clock (if any) at the PRCMU level - * (3) the peripheral's bus & kernel clock at the PRCC level - * - * (1) and (2) are handled by defining clk structs (DEFINE_PRCMU_CLK) for each - * of the cluster and peripheral clocks, and hooking these as the parents of - * the individual peripheral clocks. - * - * (3) is handled by specifying the bits in the PRCC control registers required - * to enable these clocks and modifying them in the ->enable and - * ->disable callbacks of the peripheral clocks (DEFINE_PRCC_CLK). - * - * This structure describes both the PRCMU-level clocks and PRCC-level clocks. - * The prcmu_* fields are only used for the PRCMU clocks, and the cluster, - * prcc, and parent pointers are only used for the PRCC-level clocks. - */ -struct clk { - const struct clkops *ops; - const char *name; - unsigned int enabled; - unsigned long (*get_rate)(struct clk *); - void *data; - - unsigned long rate; - struct list_head list; +extern struct clkops prcmu_clk_ops; +extern struct clkops prcmu_scalable_clk_ops; +extern struct clkops prcmu_opp100_clk_ops; +extern struct mutex clk_opp100_mutex; +extern struct clkops prcc_pclk_ops; +extern struct clkops prcc_kclk_ops; +extern struct clkops prcc_kclk_rec_ops; +extern struct clkops sga_clk_ops; - /* These three are only for PRCMU clks */ +#define CLK_LOOKUP(_clk, _dev_id, _con_id) \ + { .dev_id = _dev_id, .con_id = _con_id, .clk = &_clk } - unsigned int prcmu_cg_off; - unsigned int prcmu_cg_bit; - unsigned int prcmu_cg_mgt; - - /* The rest are only for PRCC clks */ - - int cluster; - unsigned int prcc_bus; - unsigned int prcc_kernel; - - struct clk *parent_cluster; - struct clk *parent_periph; -#if defined(CONFIG_DEBUG_FS) - struct dentry *dent; /* For visible tree hierarchy */ - struct dentry *dent_bus; /* For visible tree hierarchy */ -#endif -}; +/* Define PRCMU Clock */ +#define DEF_PRCMU_CLK(_name, _cg_sel, _rate) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_clk_ops, \ + .cg_sel = _cg_sel, \ + .rate = _rate, \ + } -#define DEFINE_PRCMU_CLK(_name, _cg_off, _cg_bit, _reg) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcmu_ops, \ - .prcmu_cg_off = _cg_off, \ - .prcmu_cg_bit = _cg_bit, \ - .prcmu_cg_mgt = PRCM_##_reg##_MGT \ +#define DEF_PRCMU_SCALABLE_CLK(_name, _cg_sel) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_scalable_clk_ops, \ + .cg_sel = _cg_sel, \ } -#define DEFINE_PRCMU_CLK_RATE(_name, _cg_off, _cg_bit, _reg, _rate) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcmu_ops, \ - .prcmu_cg_off = _cg_off, \ - .prcmu_cg_bit = _cg_bit, \ - .rate = _rate, \ - .prcmu_cg_mgt = PRCM_##_reg##_MGT \ +/* Use this for clocks that are only defined at OPP 100%. */ +#define DEF_PRCMU_OPP100_CLK(_name, _cg_sel, _rate) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_opp100_clk_ops, \ + .cg_sel = _cg_sel, \ + .rate = _rate, \ + .mutex = &clk_opp100_mutex, \ } -#define DEFINE_PRCC_CLK(_pclust, _name, _bus_en, _kernel_en, _kernclk) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcc_ops, \ - .cluster = _pclust, \ - .prcc_bus = _bus_en, \ - .prcc_kernel = _kernel_en, \ - .parent_cluster = &clk_per##_pclust##clk, \ - .parent_periph = _kernclk \ +/* Define PRCC clock */ +#define DEF_PRCC_PCLK(_name, _io_base, _cg_bit, _parent) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcc_pclk_ops, \ + .io_base = _io_base, \ + .cg_sel = BIT(_cg_bit), \ + .parent = _parent, \ } -#define DEFINE_PRCC_CLK_CUSTOM(_pclust, _name, _bus_en, _kernel_en, _kernclk, _callback, _data) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcc_ops, \ - .cluster = _pclust, \ - .prcc_bus = _bus_en, \ - .prcc_kernel = _kernel_en, \ - .parent_cluster = &clk_per##_pclust##clk, \ - .parent_periph = _kernclk, \ - .get_rate = _callback, \ - .data = (void *) _data \ +#define DEF_PRCC_KCLK(_name, _io_base, _cg_bit, _parent, _clock) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcc_kclk_ops, \ + .io_base = _io_base, \ + .cg_sel = BIT(_cg_bit), \ + .parent = _parent, \ + .clock = _clock, \ } +#define DEF_PER_CLK(_name, _bus_parent, _parent) \ + struct clk _name = { \ + .name = #_name, \ + .parent = _parent, \ + .bus_parent = _bus_parent, \ + } -#define CLK(_clk, _devname, _conname) \ - { \ - .clk = &clk_##_clk, \ - .dev_id = _devname, \ - .con_id = _conname, \ +#define DEF_MTU_CLK(_cg_sel, _name, _bus_parent) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &mtu_clk_ops, \ + .cg_sel = _cg_sel, \ + .bus_parent = _bus_parent, \ } -int __init clk_db8500_ed_fixup(void); +/* Functions defined in clock.c */ int __init clk_init(void); +void clks_register(struct clk_lookup *clks, size_t num); +int __clk_enable(struct clk *clk, void *current_lock); +void __clk_disable(struct clk *clk, void *current_lock); +unsigned long __clk_get_rate(struct clk *clk, void *current_lock); +long clk_round_rate_rec(struct clk *clk, unsigned long rate); +int clk_set_rate_rec(struct clk *clk, unsigned long rate); + +#ifdef CONFIG_DEBUG_FS +int dbx500_clk_debug_init(struct clk **clks, int num); +void clk_debugfs_add_table(struct clk_lookup *cl, size_t num); +#else +static inline int dbx500_clk_debug_init(struct clk **clks, int num) +{ + return 0; +} +#endif + +#ifdef CONFIG_UX500_SOC_DB8500 +int __init db8500_clk_init(void); +int __init db8500_clk_debug_init(void); +#else +static inline int db8500_clk_init(void) { return 0; } +static inline int db8500_clk_debug_init(void) { return 0; } +#endif + +#ifdef CONFIG_UX500_SOC_DB5500 +int __init db5500_clk_init(void); +int __init db5500_clk_debug_init(void); +#else +static inline int db5500_clk_init(void) { return 0; } +static inline int db5500_clk_debug_init(void) { return 0; } +#endif + +#endif diff --git a/arch/arm/mach-ux500/cpuidle.c b/arch/arm/mach-ux500/cpuidle.c new file mode 100644 index 00000000000..b54884bd254 --- /dev/null +++ b/arch/arm/mach-ux500/cpuidle.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2012 Linaro : Daniel Lezcano <daniel.lezcano@linaro.org> (IBM) + * + * Based on the work of Rickard Andersson <rickard.andersson@stericsson.com> + * and Jonas Aaberg <jonas.aberg@stericsson.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/module.h> +#include <linux/cpuidle.h> +#include <linux/clockchips.h> +#include <linux/spinlock.h> +#include <linux/atomic.h> +#include <linux/smp.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <asm/cpuidle.h> +#include <asm/proc-fns.h> + +static atomic_t master = ATOMIC_INIT(0); +static DEFINE_SPINLOCK(master_lock); +static DEFINE_PER_CPU(struct cpuidle_device, ux500_cpuidle_device); + +static inline int ux500_enter_idle(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + int this_cpu = smp_processor_id(); + bool recouple = false; + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &this_cpu); + + if (atomic_inc_return(&master) == num_online_cpus()) { + + /* With this lock, we prevent the other cpu to exit and enter + * this function again and become the master */ + if (!spin_trylock(&master_lock)) + goto wfi; + + /* decouple the gic from the A9 cores */ + if (prcmu_gic_decouple()) + goto out; + + /* If an error occur, we will have to recouple the gic + * manually */ + recouple = true; + + /* At this state, as the gic is decoupled, if the other + * cpu is in WFI, we have the guarantee it won't be wake + * up, so we can safely go to retention */ + if (!prcmu_is_cpu_in_wfi(this_cpu ? 0 : 1)) + goto out; + + /* The prcmu will be in charge of watching the interrupts + * and wake up the cpus */ + if (prcmu_copy_gic_settings()) + goto out; + + /* Check in the meantime an interrupt did + * not occur on the gic ... */ + if (prcmu_gic_pending_irq()) + goto out; + + /* ... and the prcmu */ + if (prcmu_pending_irq()) + goto out; + + /* Go to the retention state, the prcmu will wait for the + * cpu to go WFI and this is what happens after exiting this + * 'master' critical section */ + if (prcmu_set_power_state(PRCMU_AP_IDLE, true, true)) + goto out; + + /* When we switch to retention, the prcmu is in charge + * of recoupling the gic automatically */ + recouple = false; + + spin_unlock(&master_lock); + } +wfi: + cpu_do_idle(); +out: + atomic_dec(&master); + + if (recouple) { + prcmu_gic_recouple(); + spin_unlock(&master_lock); + } + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &this_cpu); + + return index; +} + +static struct cpuidle_driver ux500_idle_driver = { + .name = "ux500_idle", + .owner = THIS_MODULE, + .en_core_tk_irqen = 1, + .states = { + ARM_CPUIDLE_WFI_STATE, + { + .enter = ux500_enter_idle, + .exit_latency = 70, + .target_residency = 260, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "ApIdle", + .desc = "ARM Retention", + }, + }, + .safe_state_index = 0, + .state_count = 2, +}; + +/* + * For each cpu, setup the broadcast timer because we will + * need to migrate the timers for the states >= ApIdle. + */ +static void ux500_setup_broadcast_timer(void *arg) +{ + int cpu = smp_processor_id(); + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ON, &cpu); +} + +int __init ux500_idle_init(void) +{ + int ret, cpu; + struct cpuidle_device *device; + + /* Configure wake up reasons */ + prcmu_enable_wakeups(PRCMU_WAKEUP(ARM) | PRCMU_WAKEUP(RTC) | + PRCMU_WAKEUP(ABB)); + + /* + * Configure the timer broadcast for each cpu, that must + * be done from the cpu context, so we use a smp cross + * call with 'on_each_cpu'. + */ + on_each_cpu(ux500_setup_broadcast_timer, NULL, 1); + + ret = cpuidle_register_driver(&ux500_idle_driver); + if (ret) { + printk(KERN_ERR "failed to register ux500 idle driver\n"); + return ret; + } + + for_each_online_cpu(cpu) { + device = &per_cpu(ux500_cpuidle_device, cpu); + device->cpu = cpu; + ret = cpuidle_register_device(device); + if (ret) { + printk(KERN_ERR "Failed to register cpuidle " + "device for cpu%d\n", cpu); + goto out_unregister; + } + } +out: + return ret; + +out_unregister: + for_each_online_cpu(cpu) { + device = &per_cpu(ux500_cpuidle_device, cpu); + cpuidle_unregister_device(device); + } + + cpuidle_unregister_driver(&ux500_idle_driver); + goto out; +} + +device_initcall(ux500_idle_init); diff --git a/arch/arm/mach-ux500/hotplug.c b/arch/arm/mach-ux500/hotplug.c index c76f0f456f0..bf7e81705d2 100644 --- a/arch/arm/mach-ux500/hotplug.c +++ b/arch/arm/mach-ux500/hotplug.c @@ -11,20 +11,31 @@ #include <linux/kernel.h> #include <linux/errno.h> #include <linux/smp.h> +#include <linux/completion.h> #include <asm/cacheflush.h> #include <asm/smp_plat.h> +#include <mach/context.h> + extern volatile int pen_release; +static DECLARE_COMPLETION(cpu_killed); + static inline void platform_do_lowpower(unsigned int cpu) { flush_cache_all(); - /* we put the platform to just WFI */ for (;;) { - __asm__ __volatile__("dsb\n\t" "wfi\n\t" - : : : "memory"); + + context_varm_save_core(); + context_save_cpu_registers(); + + context_save_to_sram_and_wfi(false); + + context_restore_cpu_registers(); + context_varm_restore_core(); + if (pen_release == cpu_logical_map(cpu)) { /* * OK, proper wakeup, we're done @@ -36,7 +47,7 @@ static inline void platform_do_lowpower(unsigned int cpu) int platform_cpu_kill(unsigned int cpu) { - return 1; + return wait_for_completion_timeout(&cpu_killed, 5000); } /* @@ -46,6 +57,19 @@ int platform_cpu_kill(unsigned int cpu) */ void platform_cpu_die(unsigned int cpu) { +#ifdef DEBUG + unsigned int this_cpu = hard_smp_processor_id(); + + if (cpu != this_cpu) { + printk(KERN_CRIT "Eek! platform_cpu_die running on %u, should be %u\n", + this_cpu, cpu); + BUG(); + } +#endif + + printk(KERN_NOTICE "CPU%u: shutdown\n", cpu); + complete(&cpu_killed); + /* directly enter low power state, skipping secure registers */ platform_do_lowpower(cpu); } diff --git a/arch/arm/mach-ux500/include/mach/context.h b/arch/arm/mach-ux500/include/mach/context.h new file mode 100644 index 00000000000..a3490121b67 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/context.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> + * Rickard Andersson <rickard.andersson@stericsson.com> for + * ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ +#ifndef CONTEXT_H +#define CONTEXT_H + +#include <linux/notifier.h> + +#ifdef CONFIG_UX500_CONTEXT + +/* Defines to be with + * context_ape_notifier_register + */ +#define CONTEXT_APE_SAVE 0 /* APE save */ +#define CONTEXT_APE_RESTORE 1 /* APE restore */ + +/* Defines to be with + * context_arm_notifier_register + */ +#define CONTEXT_ARM_CORE_SAVE 0 /* Called for each ARM core */ +#define CONTEXT_ARM_CORE_RESTORE 1 /* Called for each ARM core */ +#define CONTEXT_ARM_COMMON_SAVE 2 /* Called when ARM common is saved */ +#define CONTEXT_ARM_COMMON_RESTORE 3 /* Called when ARM common is restored */ + +int context_ape_notifier_register(struct notifier_block *nb); +int context_ape_notifier_unregister(struct notifier_block *nb); + +int context_arm_notifier_register(struct notifier_block *nb); +int context_arm_notifier_unregister(struct notifier_block *nb); + +void context_vape_save(void); +void context_vape_restore(void); + +void context_fsmc_save(void); +void context_fsmc_restore(void); + +void context_gpio_save(void); +void context_gpio_restore(void); +void context_gpio_restore_mux(void); +void context_gpio_mux_safe_switch(bool begin); + +void context_gic_dist_disable_unneeded_irqs(void); + +void context_varm_save_common(void); +void context_varm_restore_common(void); + +void context_varm_save_core(void); +void context_varm_restore_core(void); + +void context_save_cpu_registers(void); +void context_restore_cpu_registers(void); + +void context_save_to_sram_and_wfi(bool cleanL2cache); + +void context_clean_l1_cache_all(void); +void context_save_arm_registers(u32 **backup_stack); +void context_restore_arm_registers(u32 **backup_stack); + +void context_save_cp15_registers(u32 **backup_stack); +void context_restore_cp15_registers(u32 **backup_stack); + +void context_save_to_sram_and_wfi_internal(u32 backup_sram_storage, + bool cleanL2cache); + +/* DB specific functions in either context-db8500 or context-db5500 */ +void u8500_context_save_icn(void); +void u8500_context_restore_icn(void); +void u8500_context_init(void); + +void u5500_context_save_icn(void); +void u5500_context_restore_icn(void); +void u5500_context_init(void); + +void u9540_context_save_icn(void); +void u9540_context_restore_icn(void); +void u9540_context_init(void); +#else + +static inline void context_varm_save_core(void) {} +static inline void context_save_cpu_registers(void) {} +static inline void context_save_to_sram_and_wfi(bool cleanL2cache) {} +static inline void context_restore_cpu_registers(void) {} +static inline void context_varm_restore_core(void) {} + +#endif + +#endif diff --git a/arch/arm/mach-ux500/include/mach/pm-timer.h b/arch/arm/mach-ux500/include/mach/pm-timer.h new file mode 100644 index 00000000000..f5fafbbaa77 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/pm-timer.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + * + * License Terms: GNU General Public License v2 + * + */ + +#ifndef PM_TIMER_H +#define PM_TIMER_H + +#include <linux/ktime.h> + +#ifdef CONFIG_UX500_CPUIDLE_DEBUG +ktime_t u8500_rtc_exit_latency_get(void); +void ux500_rtcrtt_measure_latency(bool enable); +#else +static inline ktime_t u8500_rtc_exit_latency_get(void) +{ + return ktime_set(0, 0); +} +static inline void ux500_rtcrtt_measure_latency(bool enable) { } + +#endif + +void ux500_rtcrtt_off(void); +void ux500_rtcrtt_next(u32 time_us); + +#endif diff --git a/arch/arm/mach-ux500/include/mach/pm.h b/arch/arm/mach-ux500/include/mach/pm.h new file mode 100644 index 00000000000..c6f1b0adca5 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/pm.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Rickard Andersson <rickard.andersson@stericsson.com> for + * ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#ifndef PM_COMMON_H +#define PM_COMMON_H + +#ifdef CONFIG_PM +#include <linux/mfd/dbx500-prcmu.h> + +/** + * ux500_pm_gic_decouple() + * + * Decouple GIC from the interrupt bus. + */ +void ux500_pm_gic_decouple(void); + +/** + * ux500_pm_gic_recouple() + * + * Recouple GIC with the interrupt bus. + */ +void ux500_pm_gic_recouple(void); + +/** + * ux500_pm_gic_pending_interrupt() + * + * returns true, if there are pending interrupts. + */ +bool ux500_pm_gic_pending_interrupt(void); + +/** + * ux500_pm_prcmu_pending_interrupt() + * + * returns true, if there are pending interrupts. + */ +bool ux500_pm_prcmu_pending_interrupt(void); + +/** + * ux500_pm_prcmu_set_ioforce() + * + * @enable: Enable/disable + * + * Enable/disable the gpio-ring + */ +void ux500_pm_prcmu_set_ioforce(bool enable); + +/** + * ux500_pm_prcmu_copy_gic_settings() + * + * This function copies all the gic interrupt settings to the prcmu. + * This is needed for the system to catch interrupts in ApIdle + */ +void ux500_pm_prcmu_copy_gic_settings(void); + +/** + * ux500_pm_gpio_save_wake_up_status() + * + * This function is called when the prcmu has woken the ARM + * but before ioforce is disabled. + */ +void ux500_pm_gpio_save_wake_up_status(void); + +/** + * ux500_pm_gpio_read_wake_up_status() + * + * @bank_number: The gpio bank. + * + * Returns the WKS register settings for given bank number. + * The WKS register is cleared when ioforce is released therefore + * this function is needed. + */ +u32 ux500_pm_gpio_read_wake_up_status(unsigned int bank_number); + +/** + * ux500_pm_other_cpu_wfi() + * + * Returns true if the other CPU is in WFI. + */ +bool ux500_pm_other_cpu_wfi(void); + +struct dev_pm_domain; +extern struct dev_pm_domain ux500_dev_power_domain; +extern struct dev_pm_domain ux500_amba_dev_power_domain; + +#else +u32 ux500_pm_gpio_read_wake_up_status(unsigned int bank_number) +{ + return 0; +} + +/** + * ux500_pm_prcmu_set_ioforce() + * + * @enable: Enable/disable + * + * Enable/disable the gpio-ring + */ +static inline void ux500_pm_prcmu_set_ioforce(bool enable) { } + +#endif + +extern int ux500_console_uart_gpio_pin; + +#endif diff --git a/arch/arm/mach-ux500/include/mach/prcmu-debug.h b/arch/arm/mach-ux500/include/mach/prcmu-debug.h new file mode 100644 index 00000000000..38f5ad94864 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/prcmu-debug.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Martin Persson for ST-Ericsson + * Etienne Carriere <etienne.carriere@stericsson.com> for ST-Ericsson + * + */ + +#ifndef PRCMU_DEBUG_H +#define PRCMU_DEBUG_H + +#ifdef CONFIG_DBX500_PRCMU_DEBUG +void prcmu_debug_ape_opp_log(u8 opp); +void prcmu_debug_ddr_opp_log(u8 opp); +void prcmu_debug_arm_opp_log(u32 value); +void prcmu_debug_dump_data_mem(void); +void prcmu_debug_dump_regs(void); +void prcmu_debug_register_interrupt(u32 mailbox); +void prcmu_debug_register_mbox0_event(u32 ev, u32 mask); +#else +static inline void prcmu_debug_ape_opp_log(u8 opp) {} +static inline void prcmu_debug_ddr_opp_log(u8 opp) {} +static inline void prcmu_debug_arm_opp_log(u32 value) {} +static inline void prcmu_debug_dump_data_mem(void) {} +static inline void prcmu_debug_dump_regs(void) {} +static inline void prcmu_debug_register_interrupt(u32 mailbox) {} +static inline void prcmu_debug_register_mbox0_event(u32 ev, u32 mask) {} +#endif +#endif diff --git a/arch/arm/mach-ux500/include/mach/suspend.h b/arch/arm/mach-ux500/include/mach/suspend.h new file mode 100644 index 00000000000..5a8df72be2e --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/suspend.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License terms: GNU General Public License (GPL) version 2 + */ +#ifndef __MACH_SUSPEND_H +#define __MACH_SUSPEND_H + +#ifdef CONFIG_UX500_SUSPEND +void suspend_block_sleep(void); +void suspend_unblock_sleep(void); +void suspend_set_pins_force_fn(void (*force)(void), void (*force_mux)(void)); +#else +static inline void suspend_block_sleep(void) { } +static inline void suspend_unblock_sleep(void) { } +static inline void suspend_set_pins_force_fn(void (*force)(void), + void (*force_mux)(void)) { } +#endif + +#endif /* __MACH_SUSPEND_H */ diff --git a/arch/arm/mach-ux500/pm/Kconfig b/arch/arm/mach-ux500/pm/Kconfig new file mode 100644 index 00000000000..12004ba9858 --- /dev/null +++ b/arch/arm/mach-ux500/pm/Kconfig @@ -0,0 +1,70 @@ +config DBX500_PRCMU_QOS_POWER + bool "DBX500 PRCMU power QoS support" + depends on (MFD_DB5500_PRCMU || MFD_DB8500_PRCMU) + default y + help + Add support for PRCMU power Quality of Service + +config UX500_CONTEXT + bool "Context save/restore support for UX500" + depends on (UX500_SOC_DB8500 || UX500_SOC_DB5500) && PM + help + This is needed for ApSleep and deeper sleep states. + +config UX500_PM_PERFORMANCE + bool "Performance supervision" + depends on DBX500_PRCMU_QOS_POWER + default y + help + Enable supervision of events which may require a boost + of platform performance. + +config UX500_CONSOLE_UART_GPIO_PIN + int "The pin number of the console UART GPIO pin" + default 29 + depends on UX500_SUSPEND_DBG_WAKE_ON_UART || UX500_CPUIDLE_DEBUG + help + GPIO pin number of the GPIO pin connected to the console UART RX line. + + Board-specific code can change this. + +config UX500_SUSPEND + bool "Suspend to mem and standby support" + depends on (UX500_SOC_DB8500 || UX500_SOC_DB5500) && PM && SUSPEND + select UX500_CONTEXT + help + Add support for suspend. + +config UX500_SUSPEND_STANDBY + bool "Suspend Standby goes to ApSleep" + depends on UX500_SUSPEND + help + If yes, echo standby > /sys/power/state puts the system into ApSleep. + +config UX500_SUSPEND_MEM + bool "Suspend Mem goes to ApDeepSleep" + depends on UX500_SUSPEND + help + If yes, echo mem > /sys/power/state puts the system into ApDeepSleep else + it will do the same as echo standby > /sys/power/state. + +config UX500_SUSPEND_DBG + bool "Suspend debug" + depends on UX500_SUSPEND && DEBUG_FS + help + Add debug support for suspend. + +config UX500_SUSPEND_DBG_WAKE_ON_UART + bool "Suspend wakes on console UART" + depends on UX500_SUSPEND_DBG + help + Wake up on uart interrupts. Makes it possible for the console to wake up system. + +config UX500_USECASE_GOVERNOR + bool "UX500 use-case governor" + depends on (UX500_SOC_DB8500 || UX500_SOC_DB5500) && \ + (CPU_FREQ && CPU_IDLE && HOTPLUG_CPU && \ + EARLYSUSPEND && UX500_L2X0_PREFETCH_CTRL && PM) + default y + help + Adjusts CPU_IDLE, CPU_FREQ, HOTPLUG_CPU and L2 cache parameters diff --git a/arch/arm/mach-ux500/pm/Makefile b/arch/arm/mach-ux500/pm/Makefile new file mode 100644 index 00000000000..ef33bdf0d4e --- /dev/null +++ b/arch/arm/mach-ux500/pm/Makefile @@ -0,0 +1,12 @@ +# +# Power save related files +# +obj-y := pm.o runtime.o + +obj-$(CONFIG_DBX500_PRCMU_QOS_POWER) += prcmu-qos-power.o +obj-$(CONFIG_UX500_CONTEXT) += context.o context_arm.o context-db8500.o context-db5500.o context-db9540.o +obj-$(CONFIG_UX500_CPUIDLE) += timer.o +obj-$(CONFIG_UX500_SUSPEND) += suspend.o +obj-$(CONFIG_UX500_SUSPEND_DBG) += suspend_dbg.o +obj-$(CONFIG_UX500_PM_PERFORMANCE) += performance.o +obj-$(CONFIG_UX500_USECASE_GOVERNOR) += usecase_gov.o diff --git a/arch/arm/mach-ux500/pm/context-db5500.c b/arch/arm/mach-ux500/pm/context-db5500.c new file mode 100644 index 00000000000..9842785c05a --- /dev/null +++ b/arch/arm/mach-ux500/pm/context-db5500.c @@ -0,0 +1,407 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com>, + * Rickard Andersson <rickard.andersson@stericsson.com>, + * Sundar Iyer <sundar.iyer@stericsson.com>, + * ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#include <linux/io.h> + +#include <mach/hardware.h> +#include <mach/context.h> + +/* These registers are DB5500 specific */ +#define NODE_HIBW1_ESRAM_IN_0_PRIORITY 0x0 +#define NODE_HIBW1_ESRAM_IN_1_PRIORITY 0x4 + +#define NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT 0x18 +#define NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT 0x1C +#define NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT 0x20 + +#define NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT 0x24 +#define NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT 0x28 +#define NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT 0x2C + +#define NODE_HIBW1_DDR_IN_0_PRIORITY 0x400 +#define NODE_HIBW1_DDR_IN_1_PRIORITY 0x404 +#define NODE_HIBW1_DDR_IN_2_PRIORITY 0x408 + +#define NODE_HIBW1_DDR_IN_0_LIMIT 0x424 +#define NODE_HIBW1_DDR_IN_1_LIMIT 0x428 +#define NODE_HIBW1_DDR_IN_2_LIMIT 0x42C + +#define NODE_HIBW1_DDR_OUT_0_PRIORITY 0x430 + +#define NODE_HIBW2_ESRAM_IN_0_PRIORITY 0x800 +#define NODE_HIBW2_ESRAM_IN_1_PRIORITY 0x804 + +#define NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT 0x818 +#define NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT 0x81C +#define NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT 0x820 + +#define NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT 0x824 +#define NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT 0x828 +#define NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT 0x82C + +#define NODE_HIBW2_DDR_IN_0_PRIORITY 0xC00 +#define NODE_HIBW2_DDR_IN_1_PRIORITY 0xC04 +#define NODE_HIBW2_DDR_IN_2_PRIORITY 0xC08 +#define NODE_HIBW2_DDR_IN_3_PRIORITY 0xC0C + +#define NODE_HIBW2_DDR_IN_0_LIMIT 0xC30 +#define NODE_HIBW2_DDR_IN_1_LIMIT 0xC34 +#define NODE_HIBW2_DDR_IN_2_LIMIT 0xC38 +#define NODE_HIBW2_DDR_IN_3_LIMIT 0xC3C + +#define NODE_HIBW2_DDR_OUT_0_PRIORITY 0xC40 + +#define NODE_ESRAM0_IN_0_PRIORITY 0x1000 +#define NODE_ESRAM0_IN_1_PRIORITY 0x1004 +#define NODE_ESRAM0_IN_2_PRIORITY 0x1008 + +#define NODE_ESRAM0_IN_0_LIMIT 0x1024 +#define NODE_ESRAM0_IN_1_LIMIT 0x1028 +#define NODE_ESRAM0_IN_2_LIMIT 0x102C +#define NODE_ESRAM0_OUT_0_PRIORITY 0x1030 + +#define NODE_ESRAM1_2_IN_0_PRIORITY 0x1400 +#define NODE_ESRAM1_2_IN_1_PRIORITY 0x1404 +#define NODE_ESRAM1_2_IN_2_PRIORITY 0x1408 + +#define NODE_ESRAM1_2_IN_0_ARB_1_LIMIT 0x1424 +#define NODE_ESRAM1_2_IN_1_ARB_1_LIMIT 0x1428 +#define NODE_ESRAM1_2_IN_2_ARB_1_LIMIT 0x142C +#define NODE_ESRAM1_2_OUT_0_PRIORITY 0x1430 + +#define NODE_ESRAM3_4_IN_0_PRIORITY 0x1800 +#define NODE_ESRAM3_4_IN_1_PRIORITY 0x1804 +#define NODE_ESRAM3_4_IN_2_PRIORITY 0x1808 + +#define NODE_ESRAM3_4_IN_0_ARB_1_LIMIT 0x1824 +#define NODE_ESRAM3_4_IN_1_ARB_1_LIMIT 0x1828 +#define NODE_ESRAM3_4_IN_2_ARB_1_LIMIT 0x182C +#define NODE_ESRAM3_4_OUT_0_PRIORITY 0x1830 + +/* + * Save ICN (Interconnect or Interconnect nodes) configuration registers + * TODO: This can be optimized, for example if we have + * a static ICN configuration. + */ + +static struct { + void __iomem *base; + u32 hibw1_esram_in_pri[2]; + u32 hibw1_esram_in0_arb[3]; + u32 hibw1_esram_in1_arb[3]; + u32 hibw1_ddr_in_prio[3]; + u32 hibw1_ddr_in_limit[3]; + u32 hibw1_ddr_out_prio_reg; + + /* HiBw2 node registers */ + u32 hibw2_esram_in_pri[2]; + u32 hibw2_esram_in0_arblimit[3]; + u32 hibw2_esram_in1_arblimit[3]; + u32 hibw2_ddr_in_prio[4]; + u32 hibw2_ddr_in_limit[4]; + u32 hibw2_ddr_out_prio_reg; + + /* ESRAM node registers */ + u32 esram_in_prio[3]; + u32 esram_in_lim[3]; + u32 esram_out_prio_reg; + + u32 esram12_in_prio[3]; + u32 esram12_in_arb_lim[3]; + u32 esram12_out_prio_reg; + + u32 esram34_in_prio[3]; + u32 esram34_in_arb_lim[3]; + u32 esram34_out_prio; +} context_icn; + + +void u5500_context_save_icn(void) +{ + void __iomem *base = context_icn.base; + + /* hibw1 */ + context_icn.hibw1_esram_in_pri[0] = + readl_relaxed(base + NODE_HIBW1_ESRAM_IN_0_PRIORITY); + context_icn.hibw1_esram_in_pri[1] = + readl_relaxed(base + NODE_HIBW1_ESRAM_IN_1_PRIORITY); + + context_icn.hibw1_esram_in0_arb[0] = + readl_relaxed(base + NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT); + context_icn.hibw1_esram_in0_arb[1] = + readl_relaxed(base + NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT); + context_icn.hibw1_esram_in0_arb[2] = + readl_relaxed(base + NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT); + + context_icn.hibw1_esram_in1_arb[0] = + readl_relaxed(base + NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT); + context_icn.hibw1_esram_in1_arb[1] = + readl_relaxed(base + NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT); + context_icn.hibw1_esram_in1_arb[2] = + readl_relaxed(base + NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT); + + context_icn.hibw1_ddr_in_prio[0] = + readl_relaxed(base + NODE_HIBW1_DDR_IN_0_PRIORITY); + context_icn.hibw1_ddr_in_prio[1] = + readl_relaxed(base + NODE_HIBW1_DDR_IN_1_PRIORITY); + context_icn.hibw1_ddr_in_prio[2] = + readl_relaxed(base + NODE_HIBW1_DDR_IN_2_PRIORITY); + + context_icn.hibw1_ddr_in_limit[0] = + readl_relaxed(base + NODE_HIBW1_DDR_IN_0_LIMIT); + context_icn.hibw1_ddr_in_limit[1] = + readl_relaxed(base + NODE_HIBW1_DDR_IN_1_LIMIT); + context_icn.hibw1_ddr_in_limit[2] = + readl_relaxed(base + NODE_HIBW1_DDR_IN_2_LIMIT); + + context_icn.hibw1_ddr_out_prio_reg = + readl_relaxed(base + NODE_HIBW1_DDR_OUT_0_PRIORITY); + + /* hibw2 */ + context_icn.hibw2_esram_in_pri[0] = + readl_relaxed(base + NODE_HIBW2_ESRAM_IN_0_PRIORITY); + context_icn.hibw2_esram_in_pri[1] = + readl_relaxed(base + NODE_HIBW2_ESRAM_IN_1_PRIORITY); + + context_icn.hibw2_esram_in0_arblimit[0] = + readl_relaxed(base + NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT); + context_icn.hibw2_esram_in0_arblimit[1] = + readl_relaxed(base + NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT); + context_icn.hibw2_esram_in0_arblimit[2] = + readl_relaxed(base + NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT); + + context_icn.hibw2_esram_in1_arblimit[0] = + readl_relaxed(base + NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT); + context_icn.hibw2_esram_in1_arblimit[1] = + readl_relaxed(base + NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT); + context_icn.hibw2_esram_in1_arblimit[2] = + readl_relaxed(base + NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT); + + context_icn.hibw2_ddr_in_prio[0] = + readl_relaxed(base + NODE_HIBW2_DDR_IN_0_PRIORITY); + context_icn.hibw2_ddr_in_prio[1] = + readl_relaxed(base + NODE_HIBW2_DDR_IN_1_PRIORITY); + context_icn.hibw2_ddr_in_prio[2] = + readl_relaxed(base + NODE_HIBW2_DDR_IN_2_PRIORITY); + context_icn.hibw2_ddr_in_prio[3] = + readl_relaxed(base + NODE_HIBW2_DDR_IN_3_PRIORITY); + + context_icn.hibw2_ddr_in_limit[0] = + readl_relaxed(base + NODE_HIBW2_DDR_IN_0_LIMIT); + context_icn.hibw2_ddr_in_limit[1] = + readl_relaxed(base + NODE_HIBW2_DDR_IN_1_LIMIT); + context_icn.hibw2_ddr_in_limit[2] = + readl_relaxed(base + NODE_HIBW2_DDR_IN_2_LIMIT); + context_icn.hibw2_ddr_in_limit[3] = + readl_relaxed(base + NODE_HIBW2_DDR_IN_3_LIMIT); + + context_icn.hibw2_ddr_out_prio_reg = + readl_relaxed(base + NODE_HIBW2_DDR_OUT_0_PRIORITY); + + /* ESRAM0 */ + context_icn.esram_in_prio[0] = + readl_relaxed(base + NODE_ESRAM0_IN_0_PRIORITY); + context_icn.esram_in_prio[1] = + readl_relaxed(base + NODE_ESRAM0_IN_1_PRIORITY); + context_icn.esram_in_prio[2] = + readl_relaxed(base + NODE_ESRAM0_IN_2_PRIORITY); + + context_icn.esram_in_lim[0] = + readl_relaxed(base + NODE_ESRAM0_IN_0_LIMIT); + context_icn.esram_in_lim[1] = + readl_relaxed(base + NODE_ESRAM0_IN_1_LIMIT); + context_icn.esram_in_lim[2] = + readl_relaxed(base + NODE_ESRAM0_IN_2_LIMIT); + + context_icn.esram_out_prio_reg = + readl_relaxed(base + NODE_ESRAM0_OUT_0_PRIORITY); + + /* ESRAM1-2 */ + context_icn.esram12_in_prio[0] = + readl_relaxed(base + NODE_ESRAM1_2_IN_0_PRIORITY); + context_icn.esram12_in_prio[1] = + readl_relaxed(base + NODE_ESRAM1_2_IN_1_PRIORITY); + context_icn.esram12_in_prio[2] = + readl_relaxed(base + NODE_ESRAM1_2_IN_2_PRIORITY); + + context_icn.esram12_in_arb_lim[0] = + readl_relaxed(base + NODE_ESRAM1_2_IN_0_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[1] = + readl_relaxed(base + NODE_ESRAM1_2_IN_1_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[2] = + readl_relaxed(base + NODE_ESRAM1_2_IN_2_ARB_1_LIMIT); + + context_icn.esram12_out_prio_reg = + readl_relaxed(base + NODE_ESRAM1_2_OUT_0_PRIORITY); + + /* ESRAM3-4 */ + context_icn.esram34_in_prio[0] = + readl_relaxed(base + NODE_ESRAM3_4_IN_0_PRIORITY); + context_icn.esram34_in_prio[1] = + readl_relaxed(base + NODE_ESRAM3_4_IN_1_PRIORITY); + context_icn.esram34_in_prio[2] = + readl_relaxed(base + NODE_ESRAM3_4_IN_2_PRIORITY); + + context_icn.esram34_in_arb_lim[0] = + readl_relaxed(base + NODE_ESRAM3_4_IN_0_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[1] = + readl_relaxed(base + NODE_ESRAM3_4_IN_1_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[2] = + readl_relaxed(base + NODE_ESRAM3_4_IN_2_ARB_1_LIMIT); + + context_icn.esram34_out_prio = + readl_relaxed(base + NODE_ESRAM3_4_OUT_0_PRIORITY); +} + +/* + * Restore ICN configuration registers + */ +void u5500_context_restore_icn(void) +{ + void __iomem *base = context_icn.base; + + /* hibw1 */ + writel_relaxed(context_icn.hibw1_esram_in_pri[0], + base + NODE_HIBW1_ESRAM_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw1_esram_in_pri[1], + base + NODE_HIBW1_ESRAM_IN_1_PRIORITY); + + writel_relaxed(context_icn.hibw1_esram_in0_arb[0], + base + NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in0_arb[1], + base + NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in0_arb[2], + base + NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_esram_in1_arb[0], + base + NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in1_arb[1], + base + NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in1_arb[2], + base + NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_ddr_in_prio[0], + base + NODE_HIBW1_DDR_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw1_ddr_in_prio[1], + base + NODE_HIBW1_DDR_IN_1_PRIORITY); + writel_relaxed(context_icn.hibw1_ddr_in_prio[2], + base + NODE_HIBW1_DDR_IN_2_PRIORITY); + + writel_relaxed(context_icn.hibw1_ddr_in_limit[0], + base + NODE_HIBW1_DDR_IN_0_LIMIT); + writel_relaxed(context_icn.hibw1_ddr_in_limit[1], + base + NODE_HIBW1_DDR_IN_1_LIMIT); + writel_relaxed(context_icn.hibw1_ddr_in_limit[2], + base + NODE_HIBW1_DDR_IN_2_LIMIT); + + writel_relaxed(context_icn.hibw1_ddr_out_prio_reg, + base + NODE_HIBW1_DDR_OUT_0_PRIORITY); + + /* hibw2 */ + writel_relaxed(context_icn.hibw2_esram_in_pri[0], + base + NODE_HIBW2_ESRAM_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw2_esram_in_pri[1], + base + NODE_HIBW2_ESRAM_IN_1_PRIORITY); + + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[0], + base + NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[1], + base + NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[2], + base + NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[0], + base + NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[1], + base + NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[2], + base + NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw2_ddr_in_prio[0], + base + NODE_HIBW2_DDR_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw2_ddr_in_prio[1], + base + NODE_HIBW2_DDR_IN_1_PRIORITY); + writel_relaxed(context_icn.hibw2_ddr_in_prio[2], + base + NODE_HIBW2_DDR_IN_2_PRIORITY); + writel_relaxed(context_icn.hibw2_ddr_in_prio[3], + base + NODE_HIBW2_DDR_IN_3_PRIORITY); + + writel_relaxed(context_icn.hibw2_ddr_in_limit[0], + base + NODE_HIBW2_DDR_IN_0_LIMIT); + writel_relaxed(context_icn.hibw2_ddr_in_limit[1], + base + NODE_HIBW2_DDR_IN_1_LIMIT); + writel_relaxed(context_icn.hibw2_ddr_in_limit[2], + base + NODE_HIBW2_DDR_IN_2_LIMIT); + writel_relaxed(context_icn.hibw2_ddr_in_limit[3], + base + NODE_HIBW2_DDR_IN_3_LIMIT); + + writel_relaxed(context_icn.hibw2_ddr_out_prio_reg, + base + NODE_HIBW2_DDR_OUT_0_PRIORITY); + + /* ESRAM0 */ + writel_relaxed(context_icn.esram_in_prio[0], + base + NODE_ESRAM0_IN_0_PRIORITY); + writel_relaxed(context_icn.esram_in_prio[1], + base + NODE_ESRAM0_IN_1_PRIORITY); + writel_relaxed(context_icn.esram_in_prio[2], + base + NODE_ESRAM0_IN_2_PRIORITY); + + writel_relaxed(context_icn.esram_in_lim[0], + base + NODE_ESRAM0_IN_0_LIMIT); + writel_relaxed(context_icn.esram_in_lim[1], + base + NODE_ESRAM0_IN_1_LIMIT); + writel_relaxed(context_icn.esram_in_lim[2], + base + NODE_ESRAM0_IN_2_LIMIT); + + writel_relaxed(context_icn.esram_out_prio_reg, + base + NODE_ESRAM0_OUT_0_PRIORITY); + + /* ESRAM1-2 */ + writel_relaxed(context_icn.esram12_in_prio[0], + base + NODE_ESRAM1_2_IN_0_PRIORITY); + writel_relaxed(context_icn.esram12_in_prio[1], + base + NODE_ESRAM1_2_IN_1_PRIORITY); + writel_relaxed(context_icn.esram12_in_prio[2], + base + NODE_ESRAM1_2_IN_2_PRIORITY); + + writel_relaxed(context_icn.esram12_in_arb_lim[0], + base + NODE_ESRAM1_2_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[1], + base + NODE_ESRAM1_2_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[2], + base + NODE_ESRAM1_2_IN_2_ARB_1_LIMIT); + + writel_relaxed(context_icn.esram12_out_prio_reg, + base + NODE_ESRAM1_2_OUT_0_PRIORITY); + + /* ESRAM3-4 */ + writel_relaxed(context_icn.esram34_in_prio[0], + base + NODE_ESRAM3_4_IN_0_PRIORITY); + writel_relaxed(context_icn.esram34_in_prio[1], + base + NODE_ESRAM3_4_IN_1_PRIORITY); + writel_relaxed(context_icn.esram34_in_prio[2], + base + NODE_ESRAM3_4_IN_2_PRIORITY); + + writel_relaxed(context_icn.esram34_in_arb_lim[0], + base + NODE_ESRAM3_4_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[1], + base + NODE_ESRAM3_4_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[2], + base + NODE_ESRAM3_4_IN_2_ARB_1_LIMIT); + + writel_relaxed(context_icn.esram34_out_prio, + base + NODE_ESRAM3_4_OUT_0_PRIORITY); + +} + +void u5500_context_init(void) +{ + context_icn.base = ioremap(U5500_ICN_BASE, SZ_8K); +} diff --git a/arch/arm/mach-ux500/pm/context-db8500.c b/arch/arm/mach-ux500/pm/context-db8500.c new file mode 100644 index 00000000000..3ba73e51a6d --- /dev/null +++ b/arch/arm/mach-ux500/pm/context-db8500.c @@ -0,0 +1,456 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * Author: Sundar Iyer for ST-Ericsson + * + */ + +#include <linux/io.h> + +#include <mach/hardware.h> +#include <mach/context.h> + +/* + * ST-Interconnect context + */ + +/* priority, bw limiter register offsets */ +#define NODE_HIBW1_ESRAM_IN_0_PRIORITY 0x00 +#define NODE_HIBW1_ESRAM_IN_1_PRIORITY 0x04 +#define NODE_HIBW1_ESRAM_IN_2_PRIORITY 0x08 +#define NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT 0x24 +#define NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT 0x28 +#define NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT 0x2C +#define NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT 0x30 +#define NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT 0x34 +#define NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT 0x38 +#define NODE_HIBW1_ESRAM_IN_2_ARB_1_LIMIT 0x3C +#define NODE_HIBW1_ESRAM_IN_2_ARB_2_LIMIT 0x40 +#define NODE_HIBW1_ESRAM_IN_2_ARB_3_LIMIT 0x44 +#define NODE_HIBW1_DDR_IN_0_PRIORITY 0x400 +#define NODE_HIBW1_DDR_IN_1_PRIORITY 0x404 +#define NODE_HIBW1_DDR_IN_2_PRIORITY 0x408 +#define NODE_HIBW1_DDR_IN_0_LIMIT 0x424 +#define NODE_HIBW1_DDR_IN_1_LIMIT 0x428 +#define NODE_HIBW1_DDR_IN_2_LIMIT 0x42C +#define NODE_HIBW1_DDR_OUT_0_PRIORITY 0x430 +#define NODE_HIBW2_ESRAM_IN_0_PRIORITY 0x800 +#define NODE_HIBW2_ESRAM_IN_1_PRIORITY 0x804 +#define NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT 0x818 +#define NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT 0x81C +#define NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT 0x820 +#define NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT 0x824 +#define NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT 0x828 +#define NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT 0x82C +#define NODE_HIBW2_DDR_IN_0_PRIORITY 0xC00 +#define NODE_HIBW2_DDR_IN_1_PRIORITY 0xC04 +#define NODE_HIBW2_DDR_IN_2_PRIORITY 0xC08 + +#define NODE_HIBW2_DDR_IN_0_LIMIT 0xC24 +#define NODE_HIBW2_DDR_IN_1_LIMIT 0xC28 +#define NODE_HIBW2_DDR_IN_2_LIMIT 0xC2C +#define NODE_HIBW2_DDR_OUT_0_PRIORITY 0xC30 + +/* + * Note the following addresses are presented in + * db8500 design spec v3.1 and v3.3, table 10. + * But their addresses are not the same as in the + * description. The addresses in the description + * of each registers are correct. + * NODE_HIBW2_DDR_IN_3_LIMIT is only present in v1. + * + * Faulty registers addresses in table 10: + * NODE_HIBW2_DDR_IN_2_LIMIT 0xC38 + * NODE_HIBW2_DDR_IN_3_LIMIT 0xC3C + * NODE_HIBW2_DDR_OUT_0_PRIORITY 0xC40 + */ + +#define NODE_ESRAM0_IN_0_PRIORITY 0x1000 +#define NODE_ESRAM0_IN_1_PRIORITY 0x1004 +#define NODE_ESRAM0_IN_2_PRIORITY 0x1008 +#define NODE_ESRAM0_IN_3_PRIORITY 0x100C +#define NODE_ESRAM0_IN_0_LIMIT 0x1030 +#define NODE_ESRAM0_IN_1_LIMIT 0x1034 +#define NODE_ESRAM0_IN_2_LIMIT 0x1038 +#define NODE_ESRAM0_IN_3_LIMIT 0x103C +/* common */ +#define NODE_ESRAM1_2_IN_0_PRIORITY 0x1400 +#define NODE_ESRAM1_2_IN_1_PRIORITY 0x1404 +#define NODE_ESRAM1_2_IN_2_PRIORITY 0x1408 +#define NODE_ESRAM1_2_IN_3_PRIORITY 0x140C +#define NODE_ESRAM1_2_IN_0_ARB_1_LIMIT 0x1430 +#define NODE_ESRAM1_2_IN_0_ARB_2_LIMIT 0x1434 +#define NODE_ESRAM1_2_IN_1_ARB_1_LIMIT 0x1438 +#define NODE_ESRAM1_2_IN_1_ARB_2_LIMIT 0x143C +#define NODE_ESRAM1_2_IN_2_ARB_1_LIMIT 0x1440 +#define NODE_ESRAM1_2_IN_2_ARB_2_LIMIT 0x1444 +#define NODE_ESRAM1_2_IN_3_ARB_1_LIMIT 0x1448 +#define NODE_ESRAM1_2_IN_3_ARB_2_LIMIT 0x144C + +#define NODE_ESRAM3_4_IN_0_PRIORITY 0x1800 +#define NODE_ESRAM3_4_IN_1_PRIORITY 0x1804 +#define NODE_ESRAM3_4_IN_2_PRIORITY 0x1808 +#define NODE_ESRAM3_4_IN_3_PRIORITY 0x180C +#define NODE_ESRAM3_4_IN_0_ARB_1_LIMIT 0x1830 +#define NODE_ESRAM3_4_IN_0_ARB_2_LIMIT 0x1834 +#define NODE_ESRAM3_4_IN_1_ARB_1_LIMIT 0x1838 +#define NODE_ESRAM3_4_IN_1_ARB_2_LIMIT 0x183C +#define NODE_ESRAM3_4_IN_2_ARB_1_LIMIT 0x1840 +#define NODE_ESRAM3_4_IN_2_ARB_2_LIMIT 0x1844 +#define NODE_ESRAM3_4_IN_3_ARB_1_LIMIT 0x1848 +#define NODE_ESRAM3_4_IN_3_ARB_2_LIMIT 0x184C + +static struct { + void __iomem *base; + u32 hibw1_esram_in_pri[3]; + u32 hibw1_esram_in0_arb[3]; + u32 hibw1_esram_in1_arb[3]; + u32 hibw1_esram_in2_arb[3]; + u32 hibw1_ddr_in_prio[3]; + u32 hibw1_ddr_in_limit[3]; + u32 hibw1_ddr_out_prio; + + /* HiBw2 node registers */ + u32 hibw2_esram_in_pri[2]; + u32 hibw2_esram_in0_arblimit[3]; + u32 hibw2_esram_in1_arblimit[3]; + u32 hibw2_ddr_in_prio[4]; + u32 hibw2_ddr_in_limit[4]; + u32 hibw2_ddr_out_prio; + + /* ESRAM node registers */ + u32 esram_in_prio[4]; + u32 esram_in_lim[4]; + u32 esram0_in_prio[4]; + u32 esram0_in_lim[4]; + u32 esram12_in_prio[4]; + u32 esram12_in_arb_lim[8]; + u32 esram34_in_prio[4]; + u32 esram34_in_arb_lim[8]; +} context_icn; + +/** + * u8500_context_save_icn() - save ICN context + * + */ +void u8500_context_save_icn(void) +{ + void __iomem *b = context_icn.base; + + context_icn.hibw1_esram_in_pri[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_PRIORITY); + context_icn.hibw1_esram_in_pri[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_PRIORITY); + context_icn.hibw1_esram_in_pri[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_PRIORITY); + + context_icn.hibw1_esram_in0_arb[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT); + context_icn.hibw1_esram_in0_arb[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT); + context_icn.hibw1_esram_in0_arb[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT); + + context_icn.hibw1_esram_in1_arb[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT); + context_icn.hibw1_esram_in1_arb[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT); + context_icn.hibw1_esram_in1_arb[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT); + + context_icn.hibw1_esram_in2_arb[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_ARB_1_LIMIT); + context_icn.hibw1_esram_in2_arb[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_ARB_2_LIMIT); + context_icn.hibw1_esram_in2_arb[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_ARB_3_LIMIT); + + context_icn.hibw1_ddr_in_prio[0] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_0_PRIORITY); + context_icn.hibw1_ddr_in_prio[1] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_1_PRIORITY); + context_icn.hibw1_ddr_in_prio[2] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_2_PRIORITY); + + context_icn.hibw1_ddr_in_limit[0] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_0_LIMIT); + context_icn.hibw1_ddr_in_limit[1] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_1_LIMIT); + context_icn.hibw1_ddr_in_limit[2] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_2_LIMIT); + + context_icn.hibw1_ddr_out_prio = + readl_relaxed(b + NODE_HIBW1_DDR_OUT_0_PRIORITY); + + context_icn.hibw2_esram_in_pri[0] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_PRIORITY); + context_icn.hibw2_esram_in_pri[1] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_PRIORITY); + + context_icn.hibw2_esram_in0_arblimit[0] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT); + context_icn.hibw2_esram_in0_arblimit[1] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT); + context_icn.hibw2_esram_in0_arblimit[2] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT); + + context_icn.hibw2_esram_in1_arblimit[0] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT); + context_icn.hibw2_esram_in1_arblimit[1] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT); + context_icn.hibw2_esram_in1_arblimit[2] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT); + + context_icn.hibw2_ddr_in_prio[0] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_0_PRIORITY); + context_icn.hibw2_ddr_in_prio[1] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_1_PRIORITY); + context_icn.hibw2_ddr_in_prio[2] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_2_PRIORITY); + + context_icn.hibw2_ddr_in_limit[0] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_0_LIMIT); + context_icn.hibw2_ddr_in_limit[1] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_1_LIMIT); + + context_icn.hibw2_ddr_in_limit[2] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_2_LIMIT); + + context_icn.hibw2_ddr_out_prio = + readl_relaxed(b + NODE_HIBW2_DDR_OUT_0_PRIORITY); + + context_icn.esram0_in_prio[0] = + readl_relaxed(b + NODE_ESRAM0_IN_0_PRIORITY); + context_icn.esram0_in_prio[1] = + readl_relaxed(b + NODE_ESRAM0_IN_1_PRIORITY); + context_icn.esram0_in_prio[2] = + readl_relaxed(b + NODE_ESRAM0_IN_2_PRIORITY); + context_icn.esram0_in_prio[3] = + readl_relaxed(b + NODE_ESRAM0_IN_3_PRIORITY); + + context_icn.esram0_in_lim[0] = + readl_relaxed(b + NODE_ESRAM0_IN_0_LIMIT); + context_icn.esram0_in_lim[1] = + readl_relaxed(b + NODE_ESRAM0_IN_1_LIMIT); + context_icn.esram0_in_lim[2] = + readl_relaxed(b + NODE_ESRAM0_IN_2_LIMIT); + context_icn.esram0_in_lim[3] = + readl_relaxed(b + NODE_ESRAM0_IN_3_LIMIT); + + context_icn.esram12_in_prio[0] = + readl_relaxed(b + NODE_ESRAM1_2_IN_0_PRIORITY); + context_icn.esram12_in_prio[1] = + readl_relaxed(b + NODE_ESRAM1_2_IN_1_PRIORITY); + context_icn.esram12_in_prio[2] = + readl_relaxed(b + NODE_ESRAM1_2_IN_2_PRIORITY); + context_icn.esram12_in_prio[3] = + readl_relaxed(b + NODE_ESRAM1_2_IN_3_PRIORITY); + + context_icn.esram12_in_arb_lim[0] = + readl_relaxed(b + NODE_ESRAM1_2_IN_0_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[1] = + readl_relaxed(b + NODE_ESRAM1_2_IN_0_ARB_2_LIMIT); + context_icn.esram12_in_arb_lim[2] = + readl_relaxed(b + NODE_ESRAM1_2_IN_1_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[3] = + readl_relaxed(b + NODE_ESRAM1_2_IN_1_ARB_2_LIMIT); + context_icn.esram12_in_arb_lim[4] = + readl_relaxed(b + NODE_ESRAM1_2_IN_2_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[5] = + readl_relaxed(b + NODE_ESRAM1_2_IN_2_ARB_2_LIMIT); + context_icn.esram12_in_arb_lim[6] = + readl_relaxed(b + NODE_ESRAM1_2_IN_3_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[7] = + readl_relaxed(b + NODE_ESRAM1_2_IN_3_ARB_2_LIMIT); + + context_icn.esram34_in_prio[0] = + readl_relaxed(b + NODE_ESRAM3_4_IN_0_PRIORITY); + context_icn.esram34_in_prio[1] = + readl_relaxed(b + NODE_ESRAM3_4_IN_1_PRIORITY); + context_icn.esram34_in_prio[2] = + readl_relaxed(b + NODE_ESRAM3_4_IN_2_PRIORITY); + context_icn.esram34_in_prio[3] = + readl_relaxed(b + NODE_ESRAM3_4_IN_3_PRIORITY); + + context_icn.esram34_in_arb_lim[0] = + readl_relaxed(b + NODE_ESRAM3_4_IN_0_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[1] = + readl_relaxed(b + NODE_ESRAM3_4_IN_0_ARB_2_LIMIT); + context_icn.esram34_in_arb_lim[2] = + readl_relaxed(b + NODE_ESRAM3_4_IN_1_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[3] = + readl_relaxed(b + NODE_ESRAM3_4_IN_1_ARB_2_LIMIT); + context_icn.esram34_in_arb_lim[4] = + readl_relaxed(b + NODE_ESRAM3_4_IN_2_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[5] = + readl_relaxed(b + NODE_ESRAM3_4_IN_2_ARB_2_LIMIT); + context_icn.esram34_in_arb_lim[6] = + readl_relaxed(b + NODE_ESRAM3_4_IN_3_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[7] = + readl_relaxed(b + NODE_ESRAM3_4_IN_3_ARB_2_LIMIT); +} + +/** + * u8500_context_restore_icn() - restore ICN context + * + */ +void u8500_context_restore_icn(void) +{ + void __iomem *b = context_icn.base; + + writel_relaxed(context_icn.hibw1_esram_in_pri[0], + b + NODE_HIBW1_ESRAM_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw1_esram_in_pri[1], + b + NODE_HIBW1_ESRAM_IN_1_PRIORITY); + writel_relaxed(context_icn.hibw1_esram_in_pri[2], + b + NODE_HIBW1_ESRAM_IN_2_PRIORITY); + + writel_relaxed(context_icn.hibw1_esram_in0_arb[0], + b + NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in0_arb[1], + b + NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in0_arb[2], + b + NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_esram_in1_arb[0], + b + NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in1_arb[1], + b + NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in1_arb[2], + b + NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_esram_in2_arb[0], + b + NODE_HIBW1_ESRAM_IN_2_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in2_arb[1], + b + NODE_HIBW1_ESRAM_IN_2_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in2_arb[2], + b + NODE_HIBW1_ESRAM_IN_2_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_ddr_in_prio[0], + b + NODE_HIBW1_DDR_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw1_ddr_in_prio[1], + b + NODE_HIBW1_DDR_IN_1_PRIORITY); + writel_relaxed(context_icn.hibw1_ddr_in_prio[2], + b + NODE_HIBW1_DDR_IN_2_PRIORITY); + + writel_relaxed(context_icn.hibw1_ddr_in_limit[0], + b + NODE_HIBW1_DDR_IN_0_LIMIT); + writel_relaxed(context_icn.hibw1_ddr_in_limit[1], + b + NODE_HIBW1_DDR_IN_1_LIMIT); + writel_relaxed(context_icn.hibw1_ddr_in_limit[2], + b + NODE_HIBW1_DDR_IN_2_LIMIT); + + writel_relaxed(context_icn.hibw1_ddr_out_prio, + b + NODE_HIBW1_DDR_OUT_0_PRIORITY); + + writel_relaxed(context_icn.hibw2_esram_in_pri[0], + b + NODE_HIBW2_ESRAM_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw2_esram_in_pri[1], + b + NODE_HIBW2_ESRAM_IN_1_PRIORITY); + + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[0], + b + NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[1], + b + NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[2], + b + NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[0], + b + NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[1], + b + NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[2], + b + NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw2_ddr_in_prio[0], + b + NODE_HIBW2_DDR_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw2_ddr_in_prio[1], + b + NODE_HIBW2_DDR_IN_1_PRIORITY); + writel_relaxed(context_icn.hibw2_ddr_in_prio[2], + b + NODE_HIBW2_DDR_IN_2_PRIORITY); + writel_relaxed(context_icn.hibw2_ddr_in_limit[0], + b + NODE_HIBW2_DDR_IN_0_LIMIT); + writel_relaxed(context_icn.hibw2_ddr_in_limit[1], + b + NODE_HIBW2_DDR_IN_1_LIMIT); + writel_relaxed(context_icn.hibw2_ddr_in_limit[2], + b + NODE_HIBW2_DDR_IN_2_LIMIT); + writel_relaxed(context_icn.hibw2_ddr_out_prio, + b + NODE_HIBW2_DDR_OUT_0_PRIORITY); + + writel_relaxed(context_icn.esram0_in_prio[0], + b + NODE_ESRAM0_IN_0_PRIORITY); + writel_relaxed(context_icn.esram0_in_prio[1], + b + NODE_ESRAM0_IN_1_PRIORITY); + writel_relaxed(context_icn.esram0_in_prio[2], + b + NODE_ESRAM0_IN_2_PRIORITY); + writel_relaxed(context_icn.esram0_in_prio[3], + b + NODE_ESRAM0_IN_3_PRIORITY); + + writel_relaxed(context_icn.esram0_in_lim[0], + b + NODE_ESRAM0_IN_0_LIMIT); + writel_relaxed(context_icn.esram0_in_lim[1], + b + NODE_ESRAM0_IN_1_LIMIT); + writel_relaxed(context_icn.esram0_in_lim[2], + b + NODE_ESRAM0_IN_2_LIMIT); + writel_relaxed(context_icn.esram0_in_lim[3], + b + NODE_ESRAM0_IN_3_LIMIT); + + writel_relaxed(context_icn.esram12_in_prio[0], + b + NODE_ESRAM1_2_IN_0_PRIORITY); + writel_relaxed(context_icn.esram12_in_prio[1], + b + NODE_ESRAM1_2_IN_1_PRIORITY); + writel_relaxed(context_icn.esram12_in_prio[2], + b + NODE_ESRAM1_2_IN_2_PRIORITY); + writel_relaxed(context_icn.esram12_in_prio[3], + b + NODE_ESRAM1_2_IN_3_PRIORITY); + + writel_relaxed(context_icn.esram12_in_arb_lim[0], + b + NODE_ESRAM1_2_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[1], + b + NODE_ESRAM1_2_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[2], + b + NODE_ESRAM1_2_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[3], + b + NODE_ESRAM1_2_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[4], + b + NODE_ESRAM1_2_IN_2_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[5], + b + NODE_ESRAM1_2_IN_2_ARB_2_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[6], + b + NODE_ESRAM1_2_IN_3_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[7], + b + NODE_ESRAM1_2_IN_3_ARB_2_LIMIT); + + writel_relaxed(context_icn.esram34_in_prio[0], + b + NODE_ESRAM3_4_IN_0_PRIORITY); + writel_relaxed(context_icn.esram34_in_prio[1], + b + NODE_ESRAM3_4_IN_1_PRIORITY); + writel_relaxed(context_icn.esram34_in_prio[2], + b + NODE_ESRAM3_4_IN_2_PRIORITY); + writel_relaxed(context_icn.esram34_in_prio[3], + b + NODE_ESRAM3_4_IN_3_PRIORITY); + + writel_relaxed(context_icn.esram34_in_arb_lim[0], + b + NODE_ESRAM3_4_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[1], + b + NODE_ESRAM3_4_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[2], + b + NODE_ESRAM3_4_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[3], + b + NODE_ESRAM3_4_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[4], + b + NODE_ESRAM3_4_IN_2_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[5], + b + NODE_ESRAM3_4_IN_2_ARB_2_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[6], + b + NODE_ESRAM3_4_IN_3_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[7], + b + NODE_ESRAM3_4_IN_3_ARB_2_LIMIT); +} + +void u8500_context_init(void) +{ + context_icn.base = ioremap(U8500_ICN_BASE, SZ_8K); +} diff --git a/arch/arm/mach-ux500/pm/context-db9540.c b/arch/arm/mach-ux500/pm/context-db9540.c new file mode 100644 index 00000000000..8d8aaeb9d8f --- /dev/null +++ b/arch/arm/mach-ux500/pm/context-db9540.c @@ -0,0 +1,651 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * Author: Sundar Iyer for ST-Ericsson + * + */ + +#include <linux/io.h> + +#include <mach/hardware.h> +#include <mach/context.h> + +/* + * ST-Interconnect context + */ + +/* priority, bw limiter register offsets */ +#define NODE_HIBW1_ESRAM_IN_0_PRIORITY 0x00 +#define NODE_HIBW1_ESRAM_IN_1_PRIORITY 0x04 +#define NODE_HIBW1_ESRAM_IN_2_PRIORITY 0x08 +#define NODE_HIBW1_ESRAM_IN_3_PRIORITY 0x0C +#define NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT 0x30 +#define NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT 0x34 +#define NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT 0x38 +#define NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT 0x3C +#define NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT 0x40 +#define NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT 0x44 +#define NODE_HIBW1_ESRAM_IN_2_ARB_1_LIMIT 0x48 +#define NODE_HIBW1_ESRAM_IN_2_ARB_2_LIMIT 0x4C +#define NODE_HIBW1_ESRAM_IN_2_ARB_3_LIMIT 0x50 +#define NODE_HIBW1_ESRAM_IN_3_ARB_1_LIMIT 0x54 +#define NODE_HIBW1_ESRAM_IN_3_ARB_2_LIMIT 0x58 +#define NODE_HIBW1_ESRAM_IN_3_ARB_3_LIMIT 0x5C +#define NODE_HIBW1_DDR0_IN_0_PRIORITY 0x400 +#define NODE_HIBW1_DDR0_IN_1_PRIORITY 0x404 +#define NODE_HIBW1_DDR0_IN_2_PRIORITY 0x408 +#define NODE_HIBW1_DDR0_IN_3_PRIORITY 0x40C +#define NODE_HIBW1_DDR0_IN_0_LIMIT 0x430 +#define NODE_HIBW1_DDR0_IN_1_LIMIT 0x434 +#define NODE_HIBW1_DDR0_IN_2_LIMIT 0x438 +#define NODE_HIBW1_DDR0_IN_3_LIMIT 0x43C +#define NODE_HIBW1_DDR0_OUT_0_PRIORITY 0x440 +#define NODE_HIBW2_ESRAM_IN_0_PRIORITY 0x800 +#define NODE_HIBW2_ESRAM_IN_1_PRIORITY 0x804 +#define NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT 0x818 +#define NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT 0x81C +#define NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT 0x820 +#define NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT 0x824 +#define NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT 0x828 +#define NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT 0x82C + +#define NODE_HIBW2_DDR0_IN_0_PRIORITY 0xC00 +#define NODE_HIBW2_DDR0_IN_1_PRIORITY 0xC04 + +#define NODE_HIBW2_DDR0_IN_0_LIMIT 0xC18 +#define NODE_HIBW2_DDR0_IN_1_LIMIT 0xC1C + +#define NODE_HIBW2_DDR0_OUT_0_PRIORITY 0xC20 + +/* + * Note the following addresses are presented in + * db8500 design spec v3.1 and v3.3, table 10. + * But their addresses are not the same as in the + * description. The addresses in the description + * of each registers are correct. + * NODE_HIBW2_DDR_IN_3_LIMIT is only present in v1. + * + * Faulty registers addresses in table 10: + * NODE_HIBW2_DDR_IN_2_LIMIT 0xC38 + * NODE_HIBW2_DDR_IN_3_LIMIT 0xC3C + * NODE_HIBW2_DDR_OUT_0_PRIORITY 0xC40 + */ + +#define NODE_ESRAM0_IN_0_PRIORITY 0x1000 +#define NODE_ESRAM0_IN_1_PRIORITY 0x1004 +#define NODE_ESRAM0_IN_2_PRIORITY 0x1008 +#define NODE_ESRAM0_IN_3_PRIORITY 0x100C +#define NODE_ESRAM0_IN_0_LIMIT 0x1030 +#define NODE_ESRAM0_IN_1_LIMIT 0x1034 +#define NODE_ESRAM0_IN_2_LIMIT 0x1038 +#define NODE_ESRAM0_IN_3_LIMIT 0x103C +#define NODE_ESRAM0_OUT_0_PRIORITY 0x1040 + +/* common */ +#define NODE_ESRAM1_2_IN_0_PRIORITY 0x1400 +#define NODE_ESRAM1_2_IN_1_PRIORITY 0x1404 +#define NODE_ESRAM1_2_IN_2_PRIORITY 0x1408 +#define NODE_ESRAM1_2_IN_3_PRIORITY 0x140C +#define NODE_ESRAM1_2_IN_0_ARB_1_LIMIT 0x1430 +#define NODE_ESRAM1_2_IN_0_ARB_2_LIMIT 0x1434 +#define NODE_ESRAM1_2_IN_1_ARB_1_LIMIT 0x1438 +#define NODE_ESRAM1_2_IN_1_ARB_2_LIMIT 0x143C +#define NODE_ESRAM1_2_IN_2_ARB_1_LIMIT 0x1440 +#define NODE_ESRAM1_2_IN_2_ARB_2_LIMIT 0x1444 +#define NODE_ESRAM1_2_IN_3_ARB_1_LIMIT 0x1448 +#define NODE_ESRAM1_2_IN_3_ARB_2_LIMIT 0x144C + +#define NODE_ESRAM3_4_IN_0_PRIORITY 0x1800 +#define NODE_ESRAM3_4_IN_1_PRIORITY 0x1804 +#define NODE_ESRAM3_4_IN_2_PRIORITY 0x1808 +#define NODE_ESRAM3_4_IN_3_PRIORITY 0x180C +#define NODE_ESRAM3_4_IN_0_ARB_1_LIMIT 0x1830 +#define NODE_ESRAM3_4_IN_0_ARB_2_LIMIT 0x1834 +#define NODE_ESRAM3_4_IN_1_ARB_1_LIMIT 0x1838 +#define NODE_ESRAM3_4_IN_1_ARB_2_LIMIT 0x183C +#define NODE_ESRAM3_4_IN_2_ARB_1_LIMIT 0x1840 +#define NODE_ESRAM3_4_IN_2_ARB_2_LIMIT 0x1844 +#define NODE_ESRAM3_4_IN_3_ARB_1_LIMIT 0x1848 +#define NODE_ESRAM3_4_IN_3_ARB_2_LIMIT 0x184C + +#define NODE_HIBW1_DDR1_IN_0_PRIORITY_REG 0x1C00 +#define NODE_HIBW1_DDR1_IN_1_PRIORITY_REG 0x1C04 +#define NODE_HIBW1_DDR1_IN_2_PRIORITY_REG 0x1C08 +#define NODE_HIBW1_DDR1_IN_3_PRIORITY_REG 0x1C0C + +#define NODE_HIBW1_DDR1_IN_0_LIMIT_REG 0x1C30 +#define NODE_HIBW1_DDR1_IN_1_LIMIT_REG 0x1C34 +#define NODE_HIBW1_DDR1_IN_2_LIMIT_REG 0x1C38 +#define NODE_HIBW1_DDR1_IN_3_LIMIT_REG 0x1C3C + +#define NODE_HIBW1_DDR1_OUT_0_PRIORITY_REG 0x1C40 + +#define NODE_HIBW2_DDR1_IN_0_PRIORITY_REG 0x2000 +#define NODE_HIBW2_DDR1_IN_1_PRIORITY_REG 0x2004 + +#define NODE_HIBW2_DDR1_IN_0_LIMIT_REG 0x2018 +#define NODE_HIBW2_DDR1_IN_1_LIMIT_REG 0x201C +#define NODE_HIBW2_DDR1_OUT_0_PRIORITY_REG 0x2020 +#define NODE_DDR_AWQOS_DSP_DDR0_REG 0x340C +#define NODE_DDR_AWQOS_DSP_DDR1_REG 0x3400 +#define NODE_DDR_ARQOS_DSP_DDR0_REG 0x3408 +#define NODE_DDR_ARQOS_DSP_DDR1_REG 0x3404 +#define NODE_DDR_AWQOS_ARM_DDR_REG 0x3410 +#define NODE_DDR_ARQOS_ARM_DDR_REG 0x3414 +#define NODE_SGA_AWQOS_DDR0_HIBW2_REG 0x3418 +#define NODE_SGA_ARQOS_DDR0_HIBW2_REG 0x341C +#define NODE_SGA_AWQOS_SGA_HIBW2_REG 0x3420 +#define NODE_SGA_ARQOS_SGA_HIBW2_REG 0x3424 + +static struct { + void __iomem *base; + u32 hibw1_esram_in_pri[4]; + u32 hibw1_esram_in0_arb[3]; + u32 hibw1_esram_in1_arb[3]; + u32 hibw1_esram_in2_arb[3]; + u32 hibw1_esram_in3_arb[3]; + u32 hibw1_ddr0_in_prio[4]; + u32 hibw1_ddr0_in_limit[4]; + u32 hibw1_ddr0_out_prio; + u32 hibw1_ddr1_in_prio[4]; + u32 hibw1_ddr1_in_limit[4]; + u32 hibw1_ddr1_out_prio; + + /* HiBw2 node registers */ + u32 hibw2_esram_in_pri[2]; + u32 hibw2_esram_in0_arblimit[3]; + u32 hibw2_esram_in1_arblimit[3]; + u32 hibw2_ddr0_in_prio[2]; + u32 hibw2_ddr0_in_limit[2]; + u32 hibw2_ddr0_out_prio; + u32 hibw2_ddr1_in_prio[2]; + u32 hibw2_ddr1_in_limit[2]; + u32 hibw2_ddr1_out_prio; + + /* ESRAM node registers */ + u32 esram0_in_prio[4]; + u32 esram0_in_lim[4]; + u32 esram0_out_prio; + u32 esram12_in_prio[4]; + u32 esram12_in_arb_lim[8]; + u32 esram34_in_prio[4]; + u32 esram34_in_arb_lim[8]; + + u32 ddr_awqos_dsp_ddr0; + u32 ddr_awqos_dsp_ddr1; + u32 ddr_arqos_dsp_ddr0; + u32 ddr_arqos_dsp_ddr1; + u32 ddr_awqos_arm_ddr; + u32 ddr_arqos_arm_ddr; + + u32 sga_awqos_ddr0_hibw2; + u32 sga_arqos_ddr0_hibw2; + + u32 sga_awqos_sga_hibw2; + u32 sga_arqos_sga_hibw2; +} context_icn; + +/** + * u8500_context_save_icn() - save ICN context + * + */ +void u9540_context_save_icn(void) +{ + void __iomem *b = context_icn.base; + + context_icn.hibw1_esram_in_pri[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_PRIORITY); + context_icn.hibw1_esram_in_pri[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_PRIORITY); + context_icn.hibw1_esram_in_pri[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_PRIORITY); + context_icn.hibw1_esram_in_pri[3] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_3_PRIORITY); + + context_icn.hibw1_esram_in0_arb[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT); + context_icn.hibw1_esram_in0_arb[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT); + context_icn.hibw1_esram_in0_arb[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT); + + context_icn.hibw1_esram_in1_arb[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT); + context_icn.hibw1_esram_in1_arb[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT); + context_icn.hibw1_esram_in1_arb[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT); + + context_icn.hibw1_esram_in2_arb[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_ARB_1_LIMIT); + context_icn.hibw1_esram_in2_arb[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_ARB_2_LIMIT); + context_icn.hibw1_esram_in2_arb[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_ARB_3_LIMIT); + + context_icn.hibw1_esram_in3_arb[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_3_ARB_1_LIMIT); + context_icn.hibw1_esram_in3_arb[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_3_ARB_2_LIMIT); + context_icn.hibw1_esram_in3_arb[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_3_ARB_3_LIMIT); + + context_icn.hibw1_ddr0_in_prio[0] = + readl_relaxed(b + NODE_HIBW1_DDR0_IN_0_PRIORITY); + context_icn.hibw1_ddr0_in_prio[1] = + readl_relaxed(b + NODE_HIBW1_DDR0_IN_1_PRIORITY); + context_icn.hibw1_ddr0_in_prio[2] = + readl_relaxed(b + NODE_HIBW1_DDR0_IN_2_PRIORITY); + context_icn.hibw1_ddr0_in_prio[3] = + readl_relaxed(b + NODE_HIBW1_DDR0_IN_3_PRIORITY); + + context_icn.hibw1_ddr0_in_limit[0] = + readl_relaxed(b + NODE_HIBW1_DDR0_IN_0_LIMIT); + context_icn.hibw1_ddr0_in_limit[1] = + readl_relaxed(b + NODE_HIBW1_DDR0_IN_1_LIMIT); + context_icn.hibw1_ddr0_in_limit[2] = + readl_relaxed(b + NODE_HIBW1_DDR0_IN_2_LIMIT); + context_icn.hibw1_ddr0_in_limit[3] = + readl_relaxed(b + NODE_HIBW1_DDR0_IN_3_LIMIT); + + context_icn.hibw1_ddr0_out_prio = + readl_relaxed(b + NODE_HIBW1_DDR0_OUT_0_PRIORITY); + + context_icn.hibw1_ddr1_in_prio[0] = + readl_relaxed(b + NODE_HIBW1_DDR1_IN_0_PRIORITY_REG); + context_icn.hibw1_ddr1_in_prio[1] = + readl_relaxed(b + NODE_HIBW1_DDR1_IN_1_PRIORITY_REG); + context_icn.hibw1_ddr1_in_prio[2] = + readl_relaxed(b + NODE_HIBW1_DDR1_IN_2_PRIORITY_REG); + context_icn.hibw1_ddr1_in_prio[3] = + readl_relaxed(b + NODE_HIBW1_DDR1_IN_3_PRIORITY_REG); + + context_icn.hibw1_ddr1_in_limit[0] = + readl_relaxed(b + NODE_HIBW1_DDR1_IN_0_LIMIT_REG); + context_icn.hibw1_ddr1_in_limit[1] = + readl_relaxed(b + NODE_HIBW1_DDR1_IN_1_LIMIT_REG); + context_icn.hibw1_ddr1_in_limit[2] = + readl_relaxed(b + NODE_HIBW1_DDR1_IN_2_LIMIT_REG); + context_icn.hibw1_ddr1_in_limit[3] = + readl_relaxed(b + NODE_HIBW1_DDR1_IN_3_LIMIT_REG); + + context_icn.hibw1_ddr1_out_prio = + readl_relaxed(b + NODE_HIBW1_DDR1_OUT_0_PRIORITY_REG); + + context_icn.hibw2_esram_in_pri[0] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_PRIORITY); + context_icn.hibw2_esram_in_pri[1] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_PRIORITY); + + context_icn.hibw2_esram_in0_arblimit[0] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT); + context_icn.hibw2_esram_in0_arblimit[1] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT); + context_icn.hibw2_esram_in0_arblimit[2] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT); + + context_icn.hibw2_esram_in1_arblimit[0] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT); + context_icn.hibw2_esram_in1_arblimit[1] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT); + context_icn.hibw2_esram_in1_arblimit[2] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT); + + context_icn.hibw2_ddr0_in_prio[0] = + readl_relaxed(b + NODE_HIBW2_DDR0_IN_0_PRIORITY); + context_icn.hibw2_ddr0_in_prio[1] = + readl_relaxed(b + NODE_HIBW2_DDR0_IN_1_PRIORITY); + + context_icn.hibw2_ddr0_in_limit[0] = + readl_relaxed(b + NODE_HIBW2_DDR0_IN_0_LIMIT); + context_icn.hibw2_ddr0_in_limit[1] = + readl_relaxed(b + NODE_HIBW2_DDR0_IN_1_LIMIT); + + context_icn.hibw2_ddr0_out_prio = + readl_relaxed(b + NODE_HIBW2_DDR0_OUT_0_PRIORITY); + + context_icn.hibw2_ddr1_in_prio[0] = + readl_relaxed(b + NODE_HIBW2_DDR1_IN_0_PRIORITY_REG); + context_icn.hibw2_ddr1_in_prio[1] = + readl_relaxed(b + NODE_HIBW2_DDR1_IN_1_PRIORITY_REG); + + context_icn.hibw2_ddr1_in_limit[0] = + readl_relaxed(b + NODE_HIBW2_DDR1_IN_0_LIMIT_REG); + context_icn.hibw2_ddr1_in_limit[1] = + readl_relaxed(b + NODE_HIBW2_DDR1_IN_1_LIMIT_REG); + + context_icn.hibw2_ddr1_out_prio = + readl_relaxed(b + NODE_HIBW2_DDR1_OUT_0_PRIORITY_REG); + + context_icn.esram0_in_prio[0] = + readl_relaxed(b + NODE_ESRAM0_IN_0_PRIORITY); + context_icn.esram0_in_prio[1] = + readl_relaxed(b + NODE_ESRAM0_IN_1_PRIORITY); + context_icn.esram0_in_prio[2] = + readl_relaxed(b + NODE_ESRAM0_IN_2_PRIORITY); + context_icn.esram0_in_prio[3] = + readl_relaxed(b + NODE_ESRAM0_IN_3_PRIORITY); + + context_icn.esram0_in_lim[0] = + readl_relaxed(b + NODE_ESRAM0_IN_0_LIMIT); + context_icn.esram0_in_lim[1] = + readl_relaxed(b + NODE_ESRAM0_IN_1_LIMIT); + context_icn.esram0_in_lim[2] = + readl_relaxed(b + NODE_ESRAM0_IN_2_LIMIT); + context_icn.esram0_in_lim[3] = + readl_relaxed(b + NODE_ESRAM0_IN_3_LIMIT); + + context_icn.esram0_out_prio = + readl_relaxed(b + NODE_ESRAM0_OUT_0_PRIORITY); + + context_icn.esram12_in_prio[0] = + readl_relaxed(b + NODE_ESRAM1_2_IN_0_PRIORITY); + context_icn.esram12_in_prio[1] = + readl_relaxed(b + NODE_ESRAM1_2_IN_1_PRIORITY); + context_icn.esram12_in_prio[2] = + readl_relaxed(b + NODE_ESRAM1_2_IN_2_PRIORITY); + context_icn.esram12_in_prio[3] = + readl_relaxed(b + NODE_ESRAM1_2_IN_3_PRIORITY); + + context_icn.esram12_in_arb_lim[0] = + readl_relaxed(b + NODE_ESRAM1_2_IN_0_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[1] = + readl_relaxed(b + NODE_ESRAM1_2_IN_0_ARB_2_LIMIT); + context_icn.esram12_in_arb_lim[2] = + readl_relaxed(b + NODE_ESRAM1_2_IN_1_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[3] = + readl_relaxed(b + NODE_ESRAM1_2_IN_1_ARB_2_LIMIT); + context_icn.esram12_in_arb_lim[4] = + readl_relaxed(b + NODE_ESRAM1_2_IN_2_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[5] = + readl_relaxed(b + NODE_ESRAM1_2_IN_2_ARB_2_LIMIT); + context_icn.esram12_in_arb_lim[6] = + readl_relaxed(b + NODE_ESRAM1_2_IN_3_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[7] = + readl_relaxed(b + NODE_ESRAM1_2_IN_3_ARB_2_LIMIT); + + context_icn.esram34_in_prio[0] = + readl_relaxed(b + NODE_ESRAM3_4_IN_0_PRIORITY); + context_icn.esram34_in_prio[1] = + readl_relaxed(b + NODE_ESRAM3_4_IN_1_PRIORITY); + context_icn.esram34_in_prio[2] = + readl_relaxed(b + NODE_ESRAM3_4_IN_2_PRIORITY); + context_icn.esram34_in_prio[3] = + readl_relaxed(b + NODE_ESRAM3_4_IN_3_PRIORITY); + + context_icn.esram34_in_arb_lim[0] = + readl_relaxed(b + NODE_ESRAM3_4_IN_0_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[1] = + readl_relaxed(b + NODE_ESRAM3_4_IN_0_ARB_2_LIMIT); + context_icn.esram34_in_arb_lim[2] = + readl_relaxed(b + NODE_ESRAM3_4_IN_1_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[3] = + readl_relaxed(b + NODE_ESRAM3_4_IN_1_ARB_2_LIMIT); + context_icn.esram34_in_arb_lim[4] = + readl_relaxed(b + NODE_ESRAM3_4_IN_2_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[5] = + readl_relaxed(b + NODE_ESRAM3_4_IN_2_ARB_2_LIMIT); + context_icn.esram34_in_arb_lim[6] = + readl_relaxed(b + NODE_ESRAM3_4_IN_3_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[7] = + readl_relaxed(b + NODE_ESRAM3_4_IN_3_ARB_2_LIMIT); + + context_icn.ddr_awqos_dsp_ddr0 = + readl_relaxed(b + NODE_DDR_AWQOS_DSP_DDR0_REG); + context_icn.ddr_awqos_dsp_ddr1 = + readl_relaxed(b + NODE_DDR_AWQOS_DSP_DDR1_REG); + context_icn.ddr_arqos_dsp_ddr0 = + readl_relaxed(b + NODE_DDR_ARQOS_DSP_DDR0_REG); + context_icn.ddr_arqos_dsp_ddr1 = + readl_relaxed(b + NODE_DDR_ARQOS_DSP_DDR1_REG); + context_icn.ddr_awqos_arm_ddr = + readl_relaxed(b + NODE_DDR_AWQOS_ARM_DDR_REG); + context_icn.ddr_arqos_arm_ddr = + readl_relaxed(b + NODE_DDR_ARQOS_ARM_DDR_REG); + + context_icn.sga_awqos_ddr0_hibw2 = + readl_relaxed(b + NODE_SGA_AWQOS_DDR0_HIBW2_REG); + context_icn.sga_arqos_ddr0_hibw2 = + readl_relaxed(b + NODE_SGA_ARQOS_DDR0_HIBW2_REG); + + context_icn.sga_awqos_sga_hibw2 = + readl_relaxed(b + NODE_SGA_AWQOS_SGA_HIBW2_REG); + context_icn.sga_arqos_sga_hibw2 = + readl_relaxed(b + NODE_SGA_ARQOS_SGA_HIBW2_REG); +} + +/** + * u9540_context_restore_icn() - restore ICN context + * + */ +void u9540_context_restore_icn(void) +{ + void __iomem *b = context_icn.base; + + writel_relaxed(context_icn.hibw1_esram_in_pri[0], + b + NODE_HIBW1_ESRAM_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw1_esram_in_pri[1], + b + NODE_HIBW1_ESRAM_IN_1_PRIORITY); + writel_relaxed(context_icn.hibw1_esram_in_pri[2], + b + NODE_HIBW1_ESRAM_IN_2_PRIORITY); + writel_relaxed(context_icn.hibw1_esram_in_pri[3], + b + NODE_HIBW1_ESRAM_IN_3_PRIORITY); + + writel_relaxed(context_icn.hibw1_esram_in0_arb[0], + b + NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in0_arb[1], + b + NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in0_arb[2], + b + NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_esram_in1_arb[0], + b + NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in1_arb[1], + b + NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in1_arb[2], + b + NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_esram_in2_arb[0], + b + NODE_HIBW1_ESRAM_IN_2_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in2_arb[1], + b + NODE_HIBW1_ESRAM_IN_2_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in2_arb[2], + b + NODE_HIBW1_ESRAM_IN_2_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_esram_in3_arb[0], + b + NODE_HIBW1_ESRAM_IN_3_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in3_arb[1], + b + NODE_HIBW1_ESRAM_IN_3_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in3_arb[2], + b + NODE_HIBW1_ESRAM_IN_3_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_ddr0_in_prio[0], + b + NODE_HIBW1_DDR0_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw1_ddr0_in_prio[1], + b + NODE_HIBW1_DDR0_IN_1_PRIORITY); + writel_relaxed(context_icn.hibw1_ddr0_in_prio[2], + b + NODE_HIBW1_DDR0_IN_2_PRIORITY); + writel_relaxed(context_icn.hibw1_ddr0_in_prio[3], + b + NODE_HIBW1_DDR0_IN_3_PRIORITY); + + writel_relaxed(context_icn.hibw1_ddr0_in_limit[0], + b + NODE_HIBW1_DDR0_IN_0_LIMIT); + writel_relaxed(context_icn.hibw1_ddr0_in_limit[1], + b + NODE_HIBW1_DDR0_IN_1_LIMIT); + writel_relaxed(context_icn.hibw1_ddr0_in_limit[2], + b + NODE_HIBW1_DDR0_IN_2_LIMIT); + writel_relaxed(context_icn.hibw1_ddr0_in_limit[3], + b + NODE_HIBW1_DDR0_IN_3_LIMIT); + + writel_relaxed(context_icn.hibw1_ddr0_out_prio, + b + NODE_HIBW1_DDR0_OUT_0_PRIORITY); + + writel_relaxed(context_icn.hibw1_ddr1_in_prio[0], + b + NODE_HIBW1_DDR1_IN_0_PRIORITY_REG); + writel_relaxed(context_icn.hibw1_ddr1_in_prio[1], + b + NODE_HIBW1_DDR1_IN_1_PRIORITY_REG); + writel_relaxed(context_icn.hibw1_ddr1_in_prio[2], + b + NODE_HIBW1_DDR1_IN_2_PRIORITY_REG); + writel_relaxed(context_icn.hibw1_ddr1_in_prio[3], + b + NODE_HIBW1_DDR1_IN_3_PRIORITY_REG); + + writel_relaxed(context_icn.hibw1_ddr1_in_limit[0], + b + NODE_HIBW1_DDR1_IN_0_LIMIT_REG); + writel_relaxed(context_icn.hibw1_ddr1_in_limit[1], + b + NODE_HIBW1_DDR1_IN_1_LIMIT_REG); + writel_relaxed(context_icn.hibw1_ddr1_in_limit[2], + b + NODE_HIBW1_DDR1_IN_2_LIMIT_REG); + writel_relaxed(context_icn.hibw1_ddr1_in_limit[3], + b + NODE_HIBW1_DDR1_IN_3_LIMIT_REG); + + writel_relaxed(context_icn.hibw1_ddr1_out_prio, + b + NODE_HIBW1_DDR1_OUT_0_PRIORITY_REG); + + writel_relaxed(context_icn.hibw2_esram_in_pri[0], + b + NODE_HIBW2_ESRAM_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw2_esram_in_pri[1], + b + NODE_HIBW2_ESRAM_IN_1_PRIORITY); + + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[0], + b + NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[1], + b + NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[2], + b + NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[0], + b + NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[1], + b + NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[2], + b + NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw2_ddr0_in_prio[0], + b + NODE_HIBW2_DDR0_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw2_ddr0_in_prio[1], + b + NODE_HIBW2_DDR0_IN_1_PRIORITY); + + writel_relaxed(context_icn.hibw2_ddr0_in_limit[0], + b + NODE_HIBW2_DDR0_IN_0_LIMIT); + writel_relaxed(context_icn.hibw2_ddr0_in_limit[1], + b + NODE_HIBW2_DDR0_IN_1_LIMIT); + + writel_relaxed(context_icn.hibw2_ddr0_out_prio, + b + NODE_HIBW2_DDR0_OUT_0_PRIORITY); + + writel_relaxed(context_icn.hibw2_ddr1_in_prio[0], + b + NODE_HIBW2_DDR1_IN_0_PRIORITY_REG); + writel_relaxed(context_icn.hibw2_ddr1_in_prio[1], + b + NODE_HIBW2_DDR1_IN_1_PRIORITY_REG); + + writel_relaxed(context_icn.hibw2_ddr1_in_limit[0], + b + NODE_HIBW2_DDR1_IN_0_LIMIT_REG); + writel_relaxed(context_icn.hibw2_ddr1_in_limit[1], + b + NODE_HIBW2_DDR1_IN_1_LIMIT_REG); + + writel_relaxed(context_icn.hibw2_ddr1_out_prio, + b + NODE_HIBW2_DDR1_OUT_0_PRIORITY_REG); + + writel_relaxed(context_icn.esram0_in_prio[0], + b + NODE_ESRAM0_IN_0_PRIORITY); + writel_relaxed(context_icn.esram0_in_prio[1], + b + NODE_ESRAM0_IN_1_PRIORITY); + writel_relaxed(context_icn.esram0_in_prio[2], + b + NODE_ESRAM0_IN_2_PRIORITY); + writel_relaxed(context_icn.esram0_in_prio[3], + b + NODE_ESRAM0_IN_3_PRIORITY); + + writel_relaxed(context_icn.esram0_in_lim[0], + b + NODE_ESRAM0_IN_0_LIMIT); + writel_relaxed(context_icn.esram0_in_lim[1], + b + NODE_ESRAM0_IN_1_LIMIT); + writel_relaxed(context_icn.esram0_in_lim[2], + b + NODE_ESRAM0_IN_2_LIMIT); + writel_relaxed(context_icn.esram0_in_lim[3], + b + NODE_ESRAM0_IN_3_LIMIT); + + writel_relaxed(context_icn.esram0_out_prio, + b + NODE_ESRAM0_OUT_0_PRIORITY); + + writel_relaxed(context_icn.esram12_in_prio[0], + b + NODE_ESRAM1_2_IN_0_PRIORITY); + writel_relaxed(context_icn.esram12_in_prio[1], + b + NODE_ESRAM1_2_IN_1_PRIORITY); + writel_relaxed(context_icn.esram12_in_prio[2], + b + NODE_ESRAM1_2_IN_2_PRIORITY); + writel_relaxed(context_icn.esram12_in_prio[3], + b + NODE_ESRAM1_2_IN_3_PRIORITY); + + writel_relaxed(context_icn.esram12_in_arb_lim[0], + b + NODE_ESRAM1_2_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[1], + b + NODE_ESRAM1_2_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[2], + b + NODE_ESRAM1_2_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[3], + b + NODE_ESRAM1_2_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[4], + b + NODE_ESRAM1_2_IN_2_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[5], + b + NODE_ESRAM1_2_IN_2_ARB_2_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[6], + b + NODE_ESRAM1_2_IN_3_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[7], + b + NODE_ESRAM1_2_IN_3_ARB_2_LIMIT); + + writel_relaxed(context_icn.esram34_in_prio[0], + b + NODE_ESRAM3_4_IN_0_PRIORITY); + writel_relaxed(context_icn.esram34_in_prio[1], + b + NODE_ESRAM3_4_IN_1_PRIORITY); + writel_relaxed(context_icn.esram34_in_prio[2], + b + NODE_ESRAM3_4_IN_2_PRIORITY); + writel_relaxed(context_icn.esram34_in_prio[3], + b + NODE_ESRAM3_4_IN_3_PRIORITY); + + writel_relaxed(context_icn.esram34_in_arb_lim[0], + b + NODE_ESRAM3_4_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[1], + b + NODE_ESRAM3_4_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[2], + b + NODE_ESRAM3_4_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[3], + b + NODE_ESRAM3_4_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[4], + b + NODE_ESRAM3_4_IN_2_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[5], + b + NODE_ESRAM3_4_IN_2_ARB_2_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[6], + b + NODE_ESRAM3_4_IN_3_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[7], + b + NODE_ESRAM3_4_IN_3_ARB_2_LIMIT); + + writel_relaxed(context_icn.ddr_awqos_dsp_ddr0, + b + NODE_DDR_AWQOS_DSP_DDR0_REG); + writel_relaxed(context_icn.ddr_awqos_dsp_ddr1, + b + NODE_DDR_AWQOS_DSP_DDR1_REG); + writel_relaxed(context_icn.ddr_arqos_dsp_ddr0, + b + NODE_DDR_ARQOS_DSP_DDR0_REG); + writel_relaxed(context_icn.ddr_arqos_dsp_ddr1, + b + NODE_DDR_ARQOS_DSP_DDR1_REG); + writel_relaxed(context_icn.ddr_awqos_arm_ddr, + b + NODE_DDR_AWQOS_ARM_DDR_REG); + writel_relaxed(context_icn.ddr_arqos_arm_ddr, + b + NODE_DDR_ARQOS_ARM_DDR_REG); + + writel_relaxed(context_icn.sga_awqos_ddr0_hibw2, + b + NODE_SGA_AWQOS_DDR0_HIBW2_REG); + writel_relaxed(context_icn.sga_arqos_ddr0_hibw2, + b + NODE_SGA_ARQOS_DDR0_HIBW2_REG); + + writel_relaxed(context_icn.sga_awqos_sga_hibw2, + b + NODE_SGA_AWQOS_SGA_HIBW2_REG); + writel_relaxed(context_icn.sga_arqos_sga_hibw2, + b + NODE_SGA_ARQOS_SGA_HIBW2_REG); +} + +void u9540_context_init(void) +{ + context_icn.base = ioremap(U8500_ICN_BASE, SZ_16K); +} diff --git a/arch/arm/mach-ux500/pm/context.c b/arch/arm/mach-ux500/pm/context.c new file mode 100644 index 00000000000..5902b861930 --- /dev/null +++ b/arch/arm/mach-ux500/pm/context.c @@ -0,0 +1,1002 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com>, + * Rickard Andersson <rickard.andersson@stericsson.com>, + * Jonas Aaberg <jonas.aberg@stericsson.com>, + * Sundar Iyer for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/smp.h> +#include <linux/percpu.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/notifier.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/gpio/nomadik.h> + +#include <mach/hardware.h> +#include <mach/irqs.h> +#include <mach/pm.h> +#include <mach/context.h> + +#include <asm/hardware/gic.h> +#include <asm/smp_twd.h> + +#include "scu.h" +#include "../product.h" +#include "../prcc.h" + +#include "../board-pins-sleep-force.h" + +#define GPIO_NUM_BANKS 9 +#define GPIO_NUM_SAVE_REGISTERS 7 + +/* + * TODO: + * - Use the "UX500*"-macros instead where possible + */ + +#define U8500_BACKUPRAM_SIZE SZ_64K + +#define U8500_PUBLIC_BOOT_ROM_BASE (U8500_BOOT_ROM_BASE + 0x17000) +#define U9540_PUBLIC_BOOT_ROM_BASE (U9540_BOOT_ROM_BASE + 0x17000) +#define U5500_PUBLIC_BOOT_ROM_BASE (U5500_BOOT_ROM_BASE + 0x18000) + +/* + * Special dedicated addresses in backup RAM. The 5500 addresses are identical + * to the 8500 ones. + */ +#define U8500_EXT_RAM_LOC_BACKUPRAM_ADDR 0x80151FDC +#define U8500_CPU0_CP15_CR_BACKUPRAM_ADDR 0x80151F80 +#define U8500_CPU1_CP15_CR_BACKUPRAM_ADDR 0x80151FA0 + +#define U8500_CPU0_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR 0x80151FD8 +#define U8500_CPU1_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR 0x80151FE0 + +#define GIC_DIST_ENABLE_NS 0x0 + +/* 32 interrupts fits in 4 bytes */ +#define GIC_DIST_ENABLE_SET_COMMON_NUM ((DBX500_NR_INTERNAL_IRQS - \ + IRQ_SHPI_START) / 32) +#define GIC_DIST_ENABLE_SET_CPU_NUM (IRQ_SHPI_START / 32) +#define GIC_DIST_ENABLE_SET_SPI0 GIC_DIST_ENABLE_SET +#define GIC_DIST_ENABLE_SET_SPI32 (GIC_DIST_ENABLE_SET + IRQ_SHPI_START / 8) + +#define GIC_DIST_ENABLE_CLEAR_0 GIC_DIST_ENABLE_CLEAR +#define GIC_DIST_ENABLE_CLEAR_32 (GIC_DIST_ENABLE_CLEAR + 4) +#define GIC_DIST_ENABLE_CLEAR_64 (GIC_DIST_ENABLE_CLEAR + 8) +#define GIC_DIST_ENABLE_CLEAR_96 (GIC_DIST_ENABLE_CLEAR + 12) +#define GIC_DIST_ENABLE_CLEAR_128 (GIC_DIST_ENABLE_CLEAR + 16) + +#define GIC_DIST_PRI_COMMON_NUM ((DBX500_NR_INTERNAL_IRQS - IRQ_SHPI_START) / 4) +#define GIC_DIST_PRI_CPU_NUM (IRQ_SHPI_START / 4) +#define GIC_DIST_PRI_SPI0 GIC_DIST_PRI +#define GIC_DIST_PRI_SPI32 (GIC_DIST_PRI + IRQ_SHPI_START) + +#define GIC_DIST_SPI_TARGET_COMMON_NUM ((DBX500_NR_INTERNAL_IRQS - \ + IRQ_SHPI_START) / 4) +#define GIC_DIST_SPI_TARGET_CPU_NUM (IRQ_SHPI_START / 4) +#define GIC_DIST_SPI_TARGET_SPI0 GIC_DIST_TARGET +#define GIC_DIST_SPI_TARGET_SPI32 (GIC_DIST_TARGET + IRQ_SHPI_START) + +/* 16 interrupts per 4 bytes */ +#define GIC_DIST_CONFIG_COMMON_NUM ((DBX500_NR_INTERNAL_IRQS - IRQ_SHPI_START) \ + / 16) +#define GIC_DIST_CONFIG_CPU_NUM (IRQ_SHPI_START / 16) +#define GIC_DIST_CONFIG_SPI0 GIC_DIST_CONFIG +#define GIC_DIST_CONFIG_SPI32 (GIC_DIST_CONFIG + IRQ_SHPI_START / 4) + +/* TODO! Move STM reg offsets to suitable place */ +#define STM_CR_OFFSET 0x00 +#define STM_MMC_OFFSET 0x08 +#define STM_TER_OFFSET 0x10 + +#define TPIU_PORT_SIZE 0x4 +#define TPIU_TRIGGER_COUNTER 0x104 +#define TPIU_TRIGGER_MULTIPLIER 0x108 +#define TPIU_CURRENT_TEST_PATTERN 0x204 +#define TPIU_TEST_PATTERN_REPEAT 0x208 +#define TPIU_FORMATTER 0x304 +#define TPIU_FORMATTER_SYNC 0x308 +#define TPIU_LOCK_ACCESS_REGISTER 0xFB0 + +#define TPIU_UNLOCK_CODE 0xc5acce55 + +#define SCU_FILTER_STARTADDR 0x40 +#define SCU_FILTER_ENDADDR 0x44 +#define SCU_ACCESS_CTRL_SAC 0x50 + +/* The context of the Trace Port Interface Unit (TPIU) */ +static struct { + void __iomem *base; + u32 port_size; + u32 trigger_counter; + u32 trigger_multiplier; + u32 current_test_pattern; + u32 test_pattern_repeat; + u32 formatter; + u32 formatter_sync; +} context_tpiu; + +static struct { + void __iomem *base; + u32 cr; + u32 mmc; + u32 ter; +} context_stm_ape; + +struct context_gic_cpu { + void __iomem *base; + u32 ctrl; + u32 primask; + u32 binpoint; +}; +static DEFINE_PER_CPU(struct context_gic_cpu, context_gic_cpu); + +static struct { + void __iomem *base; + u32 ns; + u32 enable_set[GIC_DIST_ENABLE_SET_COMMON_NUM]; /* IRQ 32 to 160 */ + u32 priority_level[GIC_DIST_PRI_COMMON_NUM]; + u32 spi_target[GIC_DIST_SPI_TARGET_COMMON_NUM]; + u32 config[GIC_DIST_CONFIG_COMMON_NUM]; +} context_gic_dist_common; + +struct context_gic_dist_cpu { + void __iomem *base; + u32 enable_set[GIC_DIST_ENABLE_SET_CPU_NUM]; /* IRQ 0 to 31 */ + u32 priority_level[GIC_DIST_PRI_CPU_NUM]; + u32 spi_target[GIC_DIST_SPI_TARGET_CPU_NUM]; + u32 config[GIC_DIST_CONFIG_CPU_NUM]; +}; +static DEFINE_PER_CPU(struct context_gic_dist_cpu, context_gic_dist_cpu); + +static struct { + void __iomem *base; + u32 ctrl; + u32 cpu_pwrstatus; + u32 inv_all_nonsecure; + u32 filter_start_addr; + u32 filter_end_addr; + u32 access_ctrl_sac; +} context_scu; + +#define UX500_NR_PRCC_BANKS 5 +static struct { + void __iomem *base; + struct clk *clk; + u32 bus_clk; + u32 kern_clk; +} context_prcc[UX500_NR_PRCC_BANKS]; + +static u32 backup_sram_storage[NR_CPUS] = { + IO_ADDRESS(U8500_CPU0_CP15_CR_BACKUPRAM_ADDR), + IO_ADDRESS(U8500_CPU1_CP15_CR_BACKUPRAM_ADDR), +}; + +static u32 gpio_bankaddr[GPIO_NUM_BANKS] = {IO_ADDRESS(U8500_GPIOBANK0_BASE), + IO_ADDRESS(U8500_GPIOBANK1_BASE), + IO_ADDRESS(U8500_GPIOBANK2_BASE), + IO_ADDRESS(U8500_GPIOBANK3_BASE), + IO_ADDRESS(U8500_GPIOBANK4_BASE), + IO_ADDRESS(U8500_GPIOBANK5_BASE), + IO_ADDRESS(U8500_GPIOBANK6_BASE), + IO_ADDRESS(U8500_GPIOBANK7_BASE), + IO_ADDRESS(U8500_GPIOBANK8_BASE) +}; + +static u32 gpio_save[GPIO_NUM_BANKS][GPIO_NUM_SAVE_REGISTERS]; + +void __iomem *fsmc_base_addr; +static u32 fsmc_bcr0; +/* + * Stacks and stack pointers + */ +static DEFINE_PER_CPU(u32[128], varm_registers_backup_stack); +static DEFINE_PER_CPU(u32 *, varm_registers_pointer); + +static DEFINE_PER_CPU(u32[128], varm_cp15_backup_stack); +static DEFINE_PER_CPU(u32 *, varm_cp15_pointer); + +static ATOMIC_NOTIFIER_HEAD(context_ape_notifier_list); +static ATOMIC_NOTIFIER_HEAD(context_arm_notifier_list); + +/* + * Register a simple callback for handling vape context save/restore + */ +int context_ape_notifier_register(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&context_ape_notifier_list, nb); +} +EXPORT_SYMBOL(context_ape_notifier_register); + +/* + * Remove a previously registered callback + */ +int context_ape_notifier_unregister(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&context_ape_notifier_list, + nb); +} +EXPORT_SYMBOL(context_ape_notifier_unregister); + +/* + * Register a simple callback for handling varm context save/restore + */ +int context_arm_notifier_register(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&context_arm_notifier_list, nb); +} +EXPORT_SYMBOL(context_arm_notifier_register); + +/* + * Remove a previously registered callback + */ +int context_arm_notifier_unregister(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&context_arm_notifier_list, + nb); +} +EXPORT_SYMBOL(context_arm_notifier_unregister); + +static void save_prcc(void) +{ + int i; + + for (i = 0; i < UX500_NR_PRCC_BANKS; i++) { + clk_enable(context_prcc[i].clk); + + context_prcc[i].bus_clk = + readl(context_prcc[i].base + PRCC_PCKSR); + context_prcc[i].kern_clk = + readl(context_prcc[i].base + PRCC_KCKSR); + + clk_disable(context_prcc[i].clk); + } +} + +static void restore_prcc(void) +{ + int i; + + for (i = 0; i < UX500_NR_PRCC_BANKS; i++) { + clk_enable(context_prcc[i].clk); + + writel(~context_prcc[i].bus_clk, + context_prcc[i].base + PRCC_PCKDIS); + writel(~context_prcc[i].kern_clk, + context_prcc[i].base + PRCC_KCKDIS); + + writel(context_prcc[i].bus_clk, + context_prcc[i].base + PRCC_PCKEN); + writel(context_prcc[i].kern_clk, + context_prcc[i].base + PRCC_KCKEN); + /* + * Consider having a while over KCK/BCK_STATUS + * to check that all clocks get disabled/enabled + */ + + clk_disable(context_prcc[i].clk); + } +} + +static void save_stm_ape(void) +{ + /* + * TODO: Check with PRCMU developers how STM is handled by PRCMU + * firmware. According to DB5500 design spec there is a "flush" + * mechanism supposed to be used by the PRCMU before power down, + * PRCMU fw might save/restore the following three registers + * at the same time. + */ + context_stm_ape.cr = readl(context_stm_ape.base + + STM_CR_OFFSET); + context_stm_ape.mmc = readl(context_stm_ape.base + + STM_MMC_OFFSET); + context_stm_ape.ter = readl(context_stm_ape.base + + STM_TER_OFFSET); +} + +static void restore_stm_ape(void) +{ + writel(context_stm_ape.ter, + context_stm_ape.base + STM_TER_OFFSET); + writel(context_stm_ape.mmc, + context_stm_ape.base + STM_MMC_OFFSET); + writel(context_stm_ape.cr, + context_stm_ape.base + STM_CR_OFFSET); +} + +static bool inline tpiu_clocked(void) +{ + return ux500_jtag_enabled(); +} + +/* + * Save the context of the Trace Port Interface Unit (TPIU). + * Saving/restoring is needed for the PTM tracing to work together + * with the sleep states ApSleep and ApDeepSleep. + */ +static void save_tpiu(void) +{ + if (!tpiu_clocked()) + return; + + context_tpiu.port_size = readl(context_tpiu.base + + TPIU_PORT_SIZE); + context_tpiu.trigger_counter = readl(context_tpiu.base + + TPIU_TRIGGER_COUNTER); + context_tpiu.trigger_multiplier = readl(context_tpiu.base + + TPIU_TRIGGER_MULTIPLIER); + context_tpiu.current_test_pattern = readl(context_tpiu.base + + TPIU_CURRENT_TEST_PATTERN); + context_tpiu.test_pattern_repeat = readl(context_tpiu.base + + TPIU_TEST_PATTERN_REPEAT); + context_tpiu.formatter = readl(context_tpiu.base + + TPIU_FORMATTER); + context_tpiu.formatter_sync = readl(context_tpiu.base + + TPIU_FORMATTER_SYNC); +} + +/* + * Restore the context of the Trace Port Interface Unit (TPIU). + * Saving/restoring is needed for the PTM tracing to work together + * with the sleep states ApSleep and ApDeepSleep. + */ +static void restore_tpiu(void) +{ + if (!tpiu_clocked()) + return; + + writel(TPIU_UNLOCK_CODE, + context_tpiu.base + TPIU_LOCK_ACCESS_REGISTER); + + writel(context_tpiu.port_size, + context_tpiu.base + TPIU_PORT_SIZE); + writel(context_tpiu.trigger_counter, + context_tpiu.base + TPIU_TRIGGER_COUNTER); + writel(context_tpiu.trigger_multiplier, + context_tpiu.base + TPIU_TRIGGER_MULTIPLIER); + writel(context_tpiu.current_test_pattern, + context_tpiu.base + TPIU_CURRENT_TEST_PATTERN); + writel(context_tpiu.test_pattern_repeat, + context_tpiu.base + TPIU_TEST_PATTERN_REPEAT); + writel(context_tpiu.formatter, + context_tpiu.base + TPIU_FORMATTER); + writel(context_tpiu.formatter_sync, + context_tpiu.base + TPIU_FORMATTER_SYNC); +} + +/* + * Save GIC CPU IF registers + * + * This is per cpu so it needs to be called for each one. + */ +static void save_gic_if_cpu(struct context_gic_cpu *c_gic_cpu) +{ + c_gic_cpu->ctrl = readl_relaxed(c_gic_cpu->base + GIC_CPU_CTRL); + c_gic_cpu->primask = readl_relaxed(c_gic_cpu->base + GIC_CPU_PRIMASK); + c_gic_cpu->binpoint = readl_relaxed(c_gic_cpu->base + GIC_CPU_BINPOINT); +} + +/* + * Restore GIC CPU IF registers + * + * This is per cpu so it needs to be called for each one. + */ +static void restore_gic_if_cpu(struct context_gic_cpu *c_gic_cpu) +{ + writel_relaxed(c_gic_cpu->ctrl, c_gic_cpu->base + GIC_CPU_CTRL); + writel_relaxed(c_gic_cpu->primask, c_gic_cpu->base + GIC_CPU_PRIMASK); + writel_relaxed(c_gic_cpu->binpoint, c_gic_cpu->base + GIC_CPU_BINPOINT); +} + +/* + * Save GIC Distributor Common registers + * + * This context is common. Only one CPU needs to call. + * + * Save SPI (Shared Peripheral Interrupt) settings, IRQ 32-159. + */ +static void save_gic_dist_common(void) +{ + int i; + + context_gic_dist_common.ns = readl_relaxed(context_gic_dist_common.base + + GIC_DIST_ENABLE_NS); + + for (i = 0; i < GIC_DIST_ENABLE_SET_COMMON_NUM; i++) + context_gic_dist_common.enable_set[i] = + readl_relaxed(context_gic_dist_common.base + + GIC_DIST_ENABLE_SET_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_PRI_COMMON_NUM; i++) + context_gic_dist_common.priority_level[i] = + readl_relaxed(context_gic_dist_common.base + + GIC_DIST_PRI_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_SPI_TARGET_COMMON_NUM; i++) + context_gic_dist_common.spi_target[i] = + readl_relaxed(context_gic_dist_common.base + + GIC_DIST_SPI_TARGET_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_CONFIG_COMMON_NUM; i++) + context_gic_dist_common.config[i] = + readl_relaxed(context_gic_dist_common.base + + GIC_DIST_CONFIG_SPI32 + i * 4); +} + +/* + * Restore GIC Distributor Common registers + * + * This context is common. Only one CPU needs to call. + * + * Save SPI (Shared Peripheral Interrupt) settings, IRQ 32-159. + */ +static void restore_gic_dist_common(void) +{ + int i; + + for (i = 0; i < GIC_DIST_CONFIG_COMMON_NUM; i++) + writel_relaxed(context_gic_dist_common.config[i], + context_gic_dist_common.base + + GIC_DIST_CONFIG_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_SPI_TARGET_COMMON_NUM; i++) + writel_relaxed(context_gic_dist_common.spi_target[i], + context_gic_dist_common.base + + GIC_DIST_SPI_TARGET_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_PRI_COMMON_NUM; i++) + writel_relaxed(context_gic_dist_common.priority_level[i], + context_gic_dist_common.base + + GIC_DIST_PRI_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_ENABLE_SET_COMMON_NUM; i++) + writel_relaxed(context_gic_dist_common.enable_set[i], + context_gic_dist_common.base + + GIC_DIST_ENABLE_SET_SPI32 + i * 4); + + writel_relaxed(context_gic_dist_common.ns, + context_gic_dist_common.base + GIC_DIST_ENABLE_NS); +} + +/* + * Save GIC Dist CPU registers + * + * This needs to be called by all cpu:s which will not call + * save_gic_dist_common(). Only the registers of the GIC which are + * banked will be saved. + */ +static void save_gic_dist_cpu(struct context_gic_dist_cpu *c_gic) +{ + int i; + + for (i = 0; i < GIC_DIST_ENABLE_SET_CPU_NUM; i++) + c_gic->enable_set[i] = + readl_relaxed(c_gic->base + + GIC_DIST_ENABLE_SET_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_PRI_CPU_NUM; i++) + c_gic->priority_level[i] = + readl_relaxed(c_gic->base + + GIC_DIST_PRI_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_SPI_TARGET_CPU_NUM; i++) + c_gic->spi_target[i] = + readl_relaxed(c_gic->base + + GIC_DIST_SPI_TARGET_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_CONFIG_CPU_NUM; i++) + c_gic->config[i] = + readl_relaxed(c_gic->base + + GIC_DIST_CONFIG_SPI0 + i * 4); +} + +/* + * Restore GIC Dist CPU registers + * + * This needs to be called by all cpu:s which will not call + * restore_gic_dist_common(). Only the registers of the GIC which are + * banked will be saved. + */ +static void restore_gic_dist_cpu(struct context_gic_dist_cpu *c_gic) +{ + int i; + + for (i = 0; i < GIC_DIST_CONFIG_CPU_NUM; i++) + writel_relaxed(c_gic->config[i], + c_gic->base + + GIC_DIST_CONFIG_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_SPI_TARGET_CPU_NUM; i++) + writel_relaxed(c_gic->spi_target[i], + c_gic->base + + GIC_DIST_SPI_TARGET_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_PRI_CPU_NUM; i++) + writel_relaxed(c_gic->priority_level[i], + c_gic->base + + GIC_DIST_PRI_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_ENABLE_SET_CPU_NUM; i++) + writel_relaxed(c_gic->enable_set[i], + c_gic->base + + GIC_DIST_ENABLE_SET_SPI0 + i * 4); +} + +/* + * Disable interrupts that are not necessary + * to have turned on during ApDeepSleep. + */ +void context_gic_dist_disable_unneeded_irqs(void) +{ + writel(0xffffffff, + context_gic_dist_common.base + + GIC_DIST_ENABLE_CLEAR_0); + + writel(0xffffffff, + context_gic_dist_common.base + + GIC_DIST_ENABLE_CLEAR_32); + + /* Leave PRCMU IRQ 0 and 1 enabled */ + writel(0xffff3fff, + context_gic_dist_common.base + + GIC_DIST_ENABLE_CLEAR_64); + + writel(0xffffffff, + context_gic_dist_common.base + + GIC_DIST_ENABLE_CLEAR_96); + + writel(0xffffffff, + context_gic_dist_common.base + + GIC_DIST_ENABLE_CLEAR_128); +} + +static void save_scu(void) +{ + context_scu.ctrl = + readl_relaxed(context_scu.base + SCU_CTRL); + context_scu.cpu_pwrstatus = + readl_relaxed(context_scu.base + SCU_CPU_STATUS); + context_scu.inv_all_nonsecure = + readl_relaxed(context_scu.base + SCU_INVALIDATE); + context_scu.filter_start_addr = + readl_relaxed(context_scu.base + SCU_FILTER_STARTADDR); + context_scu.filter_end_addr = + readl_relaxed(context_scu.base + SCU_FILTER_ENDADDR); + context_scu.access_ctrl_sac = + readl_relaxed(context_scu.base + SCU_ACCESS_CTRL_SAC); +} + +static void restore_scu(void) +{ + writel_relaxed(context_scu.ctrl, + context_scu.base + SCU_CTRL); + writel_relaxed(context_scu.cpu_pwrstatus, + context_scu.base + SCU_CPU_STATUS); + writel_relaxed(context_scu.inv_all_nonsecure, + context_scu.base + SCU_INVALIDATE); + writel_relaxed(context_scu.filter_start_addr, + context_scu.base + SCU_FILTER_STARTADDR); + writel_relaxed(context_scu.filter_end_addr, + context_scu.base + SCU_FILTER_ENDADDR); + writel_relaxed(context_scu.access_ctrl_sac, + context_scu.base + SCU_ACCESS_CTRL_SAC); +} + +/* + * Save VAPE context + */ +void context_vape_save(void) +{ + atomic_notifier_call_chain(&context_ape_notifier_list, + CONTEXT_APE_SAVE, NULL); + + if (cpu_is_u5500()) + u5500_context_save_icn(); + if (cpu_is_u8500()) + u8500_context_save_icn(); + if (cpu_is_u9540()) + u9540_context_save_icn(); + + save_stm_ape(); + + save_tpiu(); + + save_prcc(); +} + +/* + * Restore VAPE context + */ +void context_vape_restore(void) +{ + restore_prcc(); + + restore_tpiu(); + + restore_stm_ape(); + + if (cpu_is_u5500()) + u5500_context_restore_icn(); + if (cpu_is_u8500()) + u8500_context_restore_icn(); + if (cpu_is_u9540()) + u9540_context_restore_icn(); + + atomic_notifier_call_chain(&context_ape_notifier_list, + CONTEXT_APE_RESTORE, NULL); +} + +/* + * Save FSMC registers that will be reset + * during power save. + */ +void context_fsmc_save(void) +{ + fsmc_base_addr = ioremap_nocache(U8500_FSMC_BASE, 8); + fsmc_bcr0 = readl(fsmc_base_addr); +} + +/* + * Restore FSMC registers that will be reset + * during power save. + */ +void context_fsmc_restore(void) +{ + writel(fsmc_bcr0, fsmc_base_addr); + iounmap(fsmc_base_addr); +} + +/* + * Save GPIO registers that might be modified + * for power save reasons. + */ +void context_gpio_save(void) +{ + int i; + + for (i = 0; i < GPIO_NUM_BANKS; i++) { + gpio_save[i][0] = readl(gpio_bankaddr[i] + NMK_GPIO_AFSLA); + gpio_save[i][1] = readl(gpio_bankaddr[i] + NMK_GPIO_AFSLB); + gpio_save[i][2] = readl(gpio_bankaddr[i] + NMK_GPIO_PDIS); + gpio_save[i][3] = readl(gpio_bankaddr[i] + NMK_GPIO_DIR); + gpio_save[i][4] = readl(gpio_bankaddr[i] + NMK_GPIO_DAT); + gpio_save[i][6] = readl(gpio_bankaddr[i] + NMK_GPIO_SLPC); + } + +} + +/* + * Restore GPIO registers that might be modified + * for power save reasons. + */ +void context_gpio_restore(void) +{ + int i; + u32 output_state; + u32 pull_up; + u32 pull_down; + u32 pull; + + for (i = 0; i < GPIO_NUM_BANKS; i++) { + writel(gpio_save[i][2], gpio_bankaddr[i] + NMK_GPIO_PDIS); + + writel(gpio_save[i][3], gpio_bankaddr[i] + NMK_GPIO_DIR); + + /* Set the high outputs. outpute_state = GPIO_DIR & GPIO_DAT */ + output_state = gpio_save[i][3] & gpio_save[i][4]; + writel(output_state, gpio_bankaddr[i] + NMK_GPIO_DATS); + + /* + * Set the low outputs. + * outpute_state = ~(GPIO_DIR & GPIO_DAT) & GPIO_DIR + */ + output_state = ~(gpio_save[i][3] & gpio_save[i][4]) & + gpio_save[i][3]; + writel(output_state, gpio_bankaddr[i] + NMK_GPIO_DATC); + + /* + * Restore pull up/down. + * Only write pull up/down settings on inputs where + * PDIS is not set. + * pull = (~GPIO_DIR & ~GPIO_PDIS) + */ + pull = (~gpio_save[i][3] & ~gpio_save[i][2]); + nmk_gpio_read_pull(i, &pull_up); + + pull_down = pull & ~pull_up; + pull_up = pull & pull_up; + /* Set pull ups */ + writel(pull_up, gpio_bankaddr[i] + NMK_GPIO_DATS); + /* Set pull downs */ + writel(pull_down, gpio_bankaddr[i] + NMK_GPIO_DATC); + + writel(gpio_save[i][6], gpio_bankaddr[i] + NMK_GPIO_SLPC); + } + +} + +/* + * Restore GPIO mux registers that might be modified + * for power save reasons. + */ +void context_gpio_restore_mux(void) +{ + int i; + + /* Change mux settings */ + for (i = 0; i < GPIO_NUM_BANKS; i++) { + writel(gpio_save[i][0], gpio_bankaddr[i] + NMK_GPIO_AFSLA); + writel(gpio_save[i][1], gpio_bankaddr[i] + NMK_GPIO_AFSLB); + } +} + +/* + * Safe sequence used to switch IOs between GPIO and Alternate-C mode: + * - Save SLPM registers (Not done.) + * - Set SLPM=0 for the IOs you want to switch. (We assume that all + * SLPM registers already are 0 except for the ones that wants to + * have the mux connected in sleep (e.g modem STM)). + * - Configure the GPIO registers for the IOs that are being switched + * - Set IOFORCE=1 + * - Modify the AFLSA/B registers for the IOs that are being switched + * - Set IOFORCE=0 + * - Restore SLPM registers (Not done.) + * - Any spurious wake up event during switch sequence to be ignored + * and cleared + */ +void context_gpio_mux_safe_switch(bool begin) +{ + int i; + + static u32 rwimsc[GPIO_NUM_BANKS]; + static u32 fwimsc[GPIO_NUM_BANKS]; + + if (begin) { + for (i = 0; i < GPIO_NUM_BANKS; i++) { + /* Save registers */ + rwimsc[i] = readl(gpio_bankaddr[i] + NMK_GPIO_RWIMSC); + fwimsc[i] = readl(gpio_bankaddr[i] + NMK_GPIO_FWIMSC); + + /* Prevent spurious wakeups */ + writel(0, gpio_bankaddr[i] + NMK_GPIO_RWIMSC); + writel(0, gpio_bankaddr[i] + NMK_GPIO_FWIMSC); + } + + ux500_pm_prcmu_set_ioforce(true); + } else { + ux500_pm_prcmu_set_ioforce(false); + + /* Restore wake up settings */ + for (i = 0; i < GPIO_NUM_BANKS; i++) { + writel(rwimsc[i], gpio_bankaddr[i] + NMK_GPIO_RWIMSC); + writel(fwimsc[i], gpio_bankaddr[i] + NMK_GPIO_FWIMSC); + } + } +} + +/* + * Save common + * + * This function must be called once for all cores before going to deep sleep. + */ +void context_varm_save_common(void) +{ + atomic_notifier_call_chain(&context_arm_notifier_list, + CONTEXT_ARM_COMMON_SAVE, NULL); + + /* Save common parts */ + save_gic_dist_common(); + save_scu(); +} + +/* + * Restore common + * + * This function must be called once for all cores when waking up from deep + * sleep. + */ +void context_varm_restore_common(void) +{ + /* Restore common parts */ + restore_scu(); + restore_gic_dist_common(); + + atomic_notifier_call_chain(&context_arm_notifier_list, + CONTEXT_ARM_COMMON_RESTORE, NULL); +} + +/* + * Save core + * + * This function must be called once for each cpu core before going to deep + * sleep. + */ +void context_varm_save_core(void) +{ + int cpu = smp_processor_id(); + + atomic_notifier_call_chain(&context_arm_notifier_list, + CONTEXT_ARM_CORE_SAVE, NULL); + + per_cpu(varm_cp15_pointer, cpu) = per_cpu(varm_cp15_backup_stack, cpu); + + /* Save core */ + twd_save(); + save_gic_if_cpu(&per_cpu(context_gic_cpu, cpu)); + save_gic_dist_cpu(&per_cpu(context_gic_dist_cpu, cpu)); + context_save_cp15_registers(&per_cpu(varm_cp15_pointer, cpu)); +} + +/* + * Restore core + * + * This function must be called once for each cpu core when waking up from + * deep sleep. + */ +void context_varm_restore_core(void) +{ + int cpu = smp_processor_id(); + + /* Restore core */ + context_restore_cp15_registers(&per_cpu(varm_cp15_pointer, cpu)); + restore_gic_dist_cpu(&per_cpu(context_gic_dist_cpu, cpu)); + restore_gic_if_cpu(&per_cpu(context_gic_cpu, cpu)); + twd_restore(); + + atomic_notifier_call_chain(&context_arm_notifier_list, + CONTEXT_ARM_CORE_RESTORE, NULL); +} + +/* + * Save CPU registers + * + * This function saves ARM registers. + */ +void context_save_cpu_registers(void) +{ + int cpu = smp_processor_id(); + + per_cpu(varm_registers_pointer, cpu) = + per_cpu(varm_registers_backup_stack, cpu); + context_save_arm_registers(&per_cpu(varm_registers_pointer, cpu)); +} + +/* + * Restore CPU registers + * + * This function restores ARM registers. + */ +void context_restore_cpu_registers(void) +{ + int cpu = smp_processor_id(); + + context_restore_arm_registers(&per_cpu(varm_registers_pointer, cpu)); +} + +/* + * This function stores CP15 registers related to cache and mmu + * in backup SRAM. It also stores stack pointer, CPSR + * and return address for the PC in backup SRAM and + * does wait for interrupt. + */ +void context_save_to_sram_and_wfi(bool cleanL2cache) +{ + int cpu = smp_processor_id(); + + context_save_to_sram_and_wfi_internal(backup_sram_storage[cpu], + cleanL2cache); +} + +static int __init context_init(void) +{ + int i; + void __iomem *ux500_backup_ptr; + + /* allocate backup pointer for RAM data */ + ux500_backup_ptr = (void *)__get_free_pages(GFP_KERNEL, + get_order(U8500_BACKUPRAM_SIZE)); + + if (!ux500_backup_ptr) { + pr_warning("context: could not allocate backup memory\n"); + return -ENOMEM; + } + + /* + * ROM code addresses to store backup contents, + * pass the physical address of back up to ROM code + */ + writel(virt_to_phys(ux500_backup_ptr), + IO_ADDRESS(U8500_EXT_RAM_LOC_BACKUPRAM_ADDR)); + + if (cpu_is_u5500()) { + writel(IO_ADDRESS(U5500_PUBLIC_BOOT_ROM_BASE), + IO_ADDRESS(U8500_CPU0_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR)); + + writel(IO_ADDRESS(U5500_PUBLIC_BOOT_ROM_BASE), + IO_ADDRESS(U8500_CPU1_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR)); + + context_tpiu.base = ioremap(U5500_TPIU_BASE, SZ_4K); + context_stm_ape.base = ioremap(U5500_STM_REG_BASE, SZ_4K); + context_scu.base = ioremap(U5500_SCU_BASE, SZ_4K); + + context_prcc[0].base = ioremap(U5500_CLKRST1_BASE, SZ_4K); + context_prcc[1].base = ioremap(U5500_CLKRST2_BASE, SZ_4K); + context_prcc[2].base = ioremap(U5500_CLKRST3_BASE, SZ_4K); + context_prcc[3].base = ioremap(U5500_CLKRST5_BASE, SZ_4K); + context_prcc[4].base = ioremap(U5500_CLKRST6_BASE, SZ_4K); + + context_gic_dist_common.base = ioremap(U5500_GIC_DIST_BASE, SZ_4K); + per_cpu(context_gic_cpu, 0).base = ioremap(U5500_GIC_CPU_BASE, SZ_4K); + } else if (cpu_is_u8500() || cpu_is_u9540()) { + /* Give logical address to backup RAM. For both CPUs */ + if (cpu_is_u9540()) { + writel(IO_ADDRESS_DB9540_ROM(U9540_PUBLIC_BOOT_ROM_BASE), + IO_ADDRESS(U8500_CPU0_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR)); + + writel(IO_ADDRESS_DB9540_ROM(U9540_PUBLIC_BOOT_ROM_BASE), + IO_ADDRESS(U8500_CPU1_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR)); + } else { + writel(IO_ADDRESS(U8500_PUBLIC_BOOT_ROM_BASE), + IO_ADDRESS(U8500_CPU0_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR)); + + writel(IO_ADDRESS(U8500_PUBLIC_BOOT_ROM_BASE), + IO_ADDRESS(U8500_CPU1_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR)); + } + + context_tpiu.base = ioremap(U8500_TPIU_BASE, SZ_4K); + context_stm_ape.base = ioremap(U8500_STM_REG_BASE, SZ_4K); + context_scu.base = ioremap(U8500_SCU_BASE, SZ_4K); + + /* PERIPH4 is always on, so no need saving prcc */ + context_prcc[0].base = ioremap(U8500_CLKRST1_BASE, SZ_4K); + context_prcc[1].base = ioremap(U8500_CLKRST2_BASE, SZ_4K); + context_prcc[2].base = ioremap(U8500_CLKRST3_BASE, SZ_4K); + context_prcc[3].base = ioremap(U8500_CLKRST5_BASE, SZ_4K); + context_prcc[4].base = ioremap(U8500_CLKRST6_BASE, SZ_4K); + + context_gic_dist_common.base = ioremap(U8500_GIC_DIST_BASE, SZ_4K); + per_cpu(context_gic_cpu, 0).base = ioremap(U8500_GIC_CPU_BASE, SZ_4K); + } + + per_cpu(context_gic_dist_cpu, 0).base = context_gic_dist_common.base; + + for (i = 1; i < num_possible_cpus(); i++) { + per_cpu(context_gic_cpu, i).base + = per_cpu(context_gic_cpu, 0).base; + per_cpu(context_gic_dist_cpu, i).base + = per_cpu(context_gic_dist_cpu, 0).base; + } + + for (i = 0; i < ARRAY_SIZE(context_prcc); i++) { + const int clusters[] = {1, 2, 3, 5, 6}; + char clkname[10]; + + snprintf(clkname, sizeof(clkname), "PERIPH%d", clusters[i]); + + context_prcc[i].clk = clk_get_sys(clkname, NULL); + BUG_ON(IS_ERR(context_prcc[i].clk)); + } + + if (cpu_is_u8500()) { + u8500_context_init(); + } else if (cpu_is_u5500()) { + u5500_context_init(); + } else if (cpu_is_u9540()) { + u9540_context_init(); + } else { + printk(KERN_ERR "context: unknown hardware!\n"); + return -EINVAL; + } + + return 0; +} +subsys_initcall(context_init); diff --git a/arch/arm/mach-ux500/pm/context_arm.S b/arch/arm/mach-ux500/pm/context_arm.S new file mode 100644 index 00000000000..edb894d6a35 --- /dev/null +++ b/arch/arm/mach-ux500/pm/context_arm.S @@ -0,0 +1,409 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> + * Rickard Andersson <rickard.andersson@stericsson.com> for + * ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#include <linux/linkage.h> +#include <mach/hardware.h> +#include <asm/hardware/cache-l2x0.h> + +/* + * Save and increment macro + */ +.macro SAVE_AND_INCREMENT FROM_REG TO_REG + str \FROM_REG, [\TO_REG], #+4 +.endm + +/* + * Decrement and restore macro + */ +.macro DECREMENT_AND_RESTORE FROM_REG TO_REG + ldr \TO_REG, [\FROM_REG, #-4]! +.endm + +/* + * Save ARM registers + * + * This function must be called in supervisor mode. + * + * r0 = address to backup stack pointer + * + * Backup stack operations: + * + {sp, lr}^ + * + cpsr + * + {r3, r8-r14} (FIQ mode: r3=spsr) + * + {r3, r13, r14} (IRQ mode: r3=spsr) + * + {r3, r13, r14} (abort mode: r3=spsr) + * + {r3, r13, r14} (undef mode: r3=spsr) + */ + .align + .section ".text", "ax" +ENTRY(context_save_arm_registers) + stmfd sp!, {r1, r2, r3, lr} @ Save on stack + ldr r1, [r0] @ Read backup stack pointer + +ARM( stmia r1, {sp, lr}^ ) @ Store user mode sp and lr + @ registers +ARM( add r1, r1, #8 ) @ Update backup pointer (not + @ done in previous instruction) +THUMB( str sp, [r1], #+4 ) +THUMB( str lr, [r1], #+4 ) + + mrs r2, cpsr @ Get CPSR + SAVE_AND_INCREMENT r2 r1 @ Save CPSR register + orr r2, r2, #0xc0 @ Disable FIQ and IRQ + bic r2, r2, #0x1f @ Setup r2 to change mode + + @ The suffix to CPSR refers to which field(s) of the CPSR is + @ rereferenced (you can specify one or more). Defined fields are: + @ + @ c - control + @ x - extension + @ s - status + @ f - flags + + orr r3, r2, #0x11 @ Save FIQ mode registers + msr cpsr_cxsf, r3 + mrs r3, spsr +ARM( stmia r1!, {r3, r8-r14} ) +THUMB( stmia r1!, {r3, r8-r12, r14} ) +THUMB( str r13, [r1], #+4 ) + + + orr r3, r2, #0x12 @ Save IRQ mode registers + msr cpsr_cxsf, r3 + mrs r3, spsr +ARM( stmia r1!, {r3, r13, r14} ) +THUMB( stmia r1!, {r3, r14} ) +THUMB( str r13, [r1], #+4 ) + + orr r3, r2, #0x17 @ Save abort mode registers + + @ common mode registers + msr cpsr_cxsf, r3 + mrs r3, spsr +ARM( stmia r1!, {r3, r13, r14} ) +THUMB( stmia r1!, {r3, r14} ) +THUMB( str r13, [r1], #+4 ) + + orr r3, r2, #0x1B @ Save undef mode registers + msr cpsr_cxsf, r3 + mrs r3, spsr +ARM( stmia r1!, {r3, r13, r14} ) +THUMB( stmia r1!, {r3, r14} ) +THUMB( str r13, [r1], #+4 ) + + orr r3, r2, #0x13 @ Return to supervisor mode + msr cpsr_cxsf, r3 + + str r1, [r0] @ Write backup stack pointer + ldmfd sp!, {r1, r2, r3, pc} @ Restore registers and return + + + +/* + * Restore ARM registers + * + * This function must be called in supervisor mode. + * + * r0 = address to backup stack pointer + * + * Backup stack operations: + * - {r3, r13, r14} (undef mode: spsr=r3) + * - {r3, r13, r14} (abort mode: spsr=r3) + * - {r3, r13, r14} (IRQ mode: spsr=r3) + * - {r3, r8-r14} (FIQ mode: spsr=r3) + * - cpsr + * - {sp, lr}^ + */ + .align + .section ".text", "ax" +ENTRY(context_restore_arm_registers) + stmfd sp!, {r1, r2, r3, lr} @ Save on stack + ldr r1, [r0] @ Read backup stack pointer + + mrs r2, cpsr @ Get CPSR + orr r2, r2, #0xc0 @ Disable FIQ and IRQ + bic r2, r2, #0x1f @ Setup r2 to change mode + + orr r3, r2, #0x1b @ Restore undef mode registers + msr cpsr_cxsf, r3 +ARM( ldmdb r1!, {r3, r13, r14} ) +THUMB( ldr r13, [r1], #-4 ) +THUMB( ldmdb r1!, {r3, r14} ) + msr spsr_cxsf, r3 + + orr r3, r2, #0x17 @ Restore abort mode registers + msr cpsr_cxsf, r3 +ARM( ldmdb r1!, {r3, r13, r14} ) +THUMB( ldr r13, [r1], #-4 ) +THUMB( ldmdb r1!, {r3, r14} ) + msr spsr_cxsf, r3 + + orr r3, r2, #0x12 @ Restore IRQ mode registers + msr cpsr_cxsf, r3 +ARM( ldmdb r1!, {r3, r13, r14} ) +THUMB( ldr r13, [r1], #-4 ) +THUMB( ldmdb r1!, {r3, r14} ) + msr spsr_cxsf, r3 + + orr r3, r2, #0x11 @ Restore FIQ mode registers + msr cpsr_cxsf, r3 +ARM( ldmdb r1!, {r3, r8-r14} ) +THUMB( ldr r13, [r1], #-4 ) +THUMB( ldmdb r1!, {r3, r8-r12, r14} ) + + msr spsr_cxsf, r3 + + DECREMENT_AND_RESTORE r1 r3 @ Restore cpsr register + msr cpsr_cxsf, r3 + +ARM( ldmdb r1, {sp, lr}^ ) @ Restore sp and lr registers +ARM( sub r1, r1, #8 ) @ Update backup pointer (not + @ done in previous instruction) +THUMB( ldr lr, [r1], #-4 ) +THUMB( ldr sp, [r1], #-4 ) + + str r1, [r0] @ Write backup stack pointer + ldmfd sp!, {r1, r2, r3, pc} @ Restore registers and return + + + +/* + * Save CP15 registers + * + * This function must be called in supervisor mode. + * + * r0 = address to backup stack pointer + * + * TTBR0, TTBR1, TTBRC, DACR CP15 registers are restored by boot ROM from SRAM. + */ + .align 4 + .section ".text", "ax" +ENTRY(context_save_cp15_registers) + stmfd sp!, {r1, r2, r3, lr} @ Save on stack (r3 is saved due + @ to 8 byte aligned stack) + ldr r1, [r0] @ Read backup stack pointer + + mrc p15, 0, r2, c12, c0, 0 @ Read Non-secure Vector Base + @ Address Register + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 0, r2, c10, c2, 0 @ Access primary memory region + @ remap register + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 0, r2, c10, c2, 1 @ Access normal memory region + @ remap register + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 0, r2, c13, c0, 1 @ Read Context ID Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c13, c0, 2 @ Read Thread ID registers, + @ this register is both user + @ and privileged R/W accessible + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c13, c0, 3 @ Read Thread ID registers, + @ this register is user + @ read-only and privileged R/W + @ accessible. + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c13, c0, 4 @ Read Thread ID registers, + @ this register is privileged + @ R/W accessible only. + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 2, r2, c0, c0, 0 @ Cache Size Selection Register + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 0, r2, c9, c12, 0 @ Read PMNC Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c12, 1 @ Read PMCNTENSET Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c12, 5 @ Read PMSELR Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c13, 0 @ Read PMCCNTR Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c13, 1 @ Read PMXEVTYPER Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c14, 0 @ Read PMUSERENR Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c14, 1 @ Read PMINTENSET Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c14, 2 @ Read PMINTENCLR Register + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 0, r2, c1, c0, 2 @ Read CPACR Register + SAVE_AND_INCREMENT r2 r1 + + str r1, [r0] @ Write backup stack pointer + ldmfd sp!, {r1, r2, r3, pc} @ Restore registers and return + + + +/* + * Restore CP15 registers + * + * This function must be called in supervisor mode. + * + * r0 = address to backup stack pointer + */ + .align 4 + .section ".text", "ax" +ENTRY(context_restore_cp15_registers) + stmfd sp!, {r1, r2, r3, lr} @ Save on stack (r3 is saved due + @ to 8 byte aligned stack) + ldr r1, [r0] @ Read backup stack pointer + + DECREMENT_AND_RESTORE r1 r2 @ Write CPACR register + mcr p15, 0, r2, c1, c0, 2 + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c14, 2 @ Write PMINTENCLR Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c14, 1 @ Write PMINTENSET Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c14, 0 @ Write PMUSERENR Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c13, 1 @ Write PMXEVTYPER Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c13, 0 @ Write PMCCNTR Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c12, 5 @ Write PMSELR Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c12, 1 @ Write PMCNTENSET Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c12, 0 @ Write PMNC Register + + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 2, r2, c0, c0, 0 @ Cache Size Selection Register + + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c13, c0, 4 @ Write Thread ID registers, + @ this register is privileged + @ R/W accessible only + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c13, c0, 3 @ Write Thread ID registers, + @ this register is user + @ read-only and privileged R/W + @ accessible + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c13, c0, 2 @ Write Thread ID registers, + @ this register is both user + @ and privileged R/W accessible + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c13, c0, 1 @ Write Context ID Register + + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c10, c2, 1 @ Access normal memory region + @ remap register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c10, c2, 0 @ Access primary memory region + @ remap register + + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c12, c0, 0 @ Write Non-secure Vector Base + @ Address Register + + str r1, [r0] @ Write backup stack pointer + ldmfd sp!, {r1, r2, r3, pc} @ Restore registers and return + + +/* + * L1 cache clean function. Commit 'dirty' data from L1 + * to L2 cache. + * + * r0, r1, r2, used locally + * + */ + .align 4 + .section ".text", "ax" +ENTRY(context_clean_l1_cache_all) + + mov r0, #0 @ swith to cache level 0 + @ (L1 cache) + mcr p15, 2, r0, c0, c0, 0 @ select current cache level + @ in cssr + + dmb + mov r1, #0 @ r1 = way index +wayLoopL1clean: + mov r0, #0 @ r0 = line index +lineLoopL1clean: + mov r2, r1, lsl #30 @ TODO: OK to hard-code + @ SoC-specific L1 cache details? + mov r3, r0, lsl #5 + add r2, r3 +@ add r2, r0, lsl #5 + mcr p15, 0, r2, c7, c10, 2 @ Clean cache by set/way + add r0, r0, #1 + cmp r0, #256 @ TODO: Ok with hard-coded + @ set/way sizes or do we have to + @ read them from ARM regs? Is it + @ set correctly in silicon? + bne lineLoopL1clean + add r1, r1, #1 + cmp r1, #4 @ TODO: Ditto, sizes... + bne wayLoopL1clean + + dsb + isb + mov pc, lr + +ENDPROC(context_clean_l1_cache_all) + +/* + * Last saves to backup RAM, cache clean and WFI + * + * r0 = address to backup_sram_storage base adress + * r1 = indicate whether also L2 cache should be cleaned + */ + .align 4 + .section ".text", "ax" +ENTRY(context_save_to_sram_and_wfi_internal) + + stmfd sp!, {r2-r12, lr} @ save on stack. + + mrc p15, 0, r2, c1, c0, 0 @ read cp15 system control + @ register + str r2, [r0, #0x00] + mrc p15, 0, r2, c2, c0, 0 @ read cp15 ttb0 register + str r2, [r0, #0x04] + mrc p15, 0, r2, c2, c0, 1 @ read cp15 ttb1 register + str r2, [r0, #0x08] + mrc p15, 0, r2, c2, c0, 2 @ read cp15 ttb control register + str r2, [r0, #0x0C] + mrc p15, 0, r2, c3, c0, 0 @ read domain access control + @ register + str r2, [r0, #0x10] + + ldr r2, =return_here + str r2, [r0, #0x14] @ save program counter restore + @ value to backup_sram_storage + mrs r2, cpsr + str r2, [r0, #0x18] @ save cpsr to + @ backup_sram_storage + str sp, [r0, #0x1c] @ save sp to backup_sram_storage + + mov r4, r1 @ Set r4 = cleanL2cache, r1 + @ will be destroyed by + @ v7_clean_l1_cache_all + + bl context_clean_l1_cache_all @ Commit all dirty data in L1 + @ cache to L2 without + @ invalidating + + dsb @ data synchronization barrier + isb @ instruction synchronization + @ barrier + wfi @ wait for interrupt + +return_here: @ both cores return here + @ now we are out deep sleep + @ with all the context lost + @ except pc, sp and cpsr + + ldmfd sp!, {r2-r12, pc} @ restore from stack + diff --git a/arch/arm/mach-ux500/pm/performance.c b/arch/arm/mach-ux500/pm/performance.c new file mode 100644 index 00000000000..04aca3cb5bd --- /dev/null +++ b/arch/arm/mach-ux500/pm/performance.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Johan Rudholm <johan.rudholm@stericsson.com> + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + */ + +#include <linux/kernel.h> +#include <linux/genhd.h> +#include <linux/major.h> +#include <linux/cdev.h> +#include <linux/kernel_stat.h> +#include <linux/workqueue.h> +#include <linux/kernel.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/cpu.h> +#include <linux/pm_qos.h> + +#include <mach/irqs.h> + +#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 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); diff --git a/arch/arm/mach-ux500/pm/pm.c b/arch/arm/mach-ux500/pm/pm.c new file mode 100644 index 00000000000..880e9763a0f --- /dev/null +++ b/arch/arm/mach-ux500/pm/pm.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Rickard Andersson <rickard.andersson@stericsson.com> for + * ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#include <linux/io.h> +#include <linux/percpu.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/gpio/nomadik.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <asm/hardware/gic.h> +#include <asm/processor.h> + +#include <mach/hardware.h> +#include <mach/pm.h> + +#define STABILIZATION_TIME 30 /* us */ +#define GIC_FREEZE_DELAY 1 /* us */ + +#define PRCM_ARM_WFI_STANDBY_CPU0_WFI 0x8 +#define PRCM_ARM_WFI_STANDBY_CPU1_WFI 0x10 + +/* Dual A9 core interrupt management unit registers */ +#define PRCM_A9_MASK_REQ 0x328 +#define PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ 0x1 +#define PRCM_A9_MASK_ACK 0x32c + +#define PRCM_ARMITMSK31TO0 0x11c +#define PRCM_ARMITMSK63TO32 0x120 +#define PRCM_ARMITMSK95TO64 0x124 +#define PRCM_ARMITMSK127TO96 0x128 +#define PRCM_POWER_STATE_VAL 0x25C +#define PRCM_ARMITVAL31TO0 0x260 +#define PRCM_ARMITVAL63TO32 0x264 +#define PRCM_ARMITVAL95TO64 0x268 +#define PRCM_ARMITVAL127TO96 0x26C + +/* ARM WFI Standby signal register */ +#define PRCM_ARM_WFI_STANDBY 0x130 + +/* IO force */ +#define PRCM_IOCR 0x310 +#define PRCM_IOCR_IOFORCE 0x1 +#ifdef CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART +int ux500_console_uart_gpio_pin = CONFIG_UX500_CONSOLE_UART_GPIO_PIN; +#endif +static u32 u8500_gpio_banks[] = {U8500_GPIOBANK0_BASE, + U8500_GPIOBANK1_BASE, + U8500_GPIOBANK2_BASE, + U8500_GPIOBANK3_BASE, + U8500_GPIOBANK4_BASE, + U8500_GPIOBANK5_BASE, + U8500_GPIOBANK6_BASE, + U8500_GPIOBANK7_BASE, + U8500_GPIOBANK8_BASE}; + +static u32 u5500_gpio_banks[] = {U5500_GPIOBANK0_BASE, + U5500_GPIOBANK1_BASE, + U5500_GPIOBANK2_BASE, + U5500_GPIOBANK3_BASE, + U5500_GPIOBANK4_BASE, + U5500_GPIOBANK5_BASE, + U5500_GPIOBANK6_BASE, + U5500_GPIOBANK7_BASE}; + +static u32 ux500_gpio_wks[ARRAY_SIZE(u8500_gpio_banks)]; + +inline int ux500_pm_arm_on_ext_clk(bool leave_arm_pll_on) +{ + return 0; +} + +/* Decouple GIC from the interrupt bus */ +void ux500_pm_gic_decouple(void) +{ + prcmu_write_masked(PRCM_A9_MASK_REQ, + PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ, + PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ); + + (void)prcmu_read(PRCM_A9_MASK_REQ); + + /* TODO: Use the ack bit when possible */ + udelay(GIC_FREEZE_DELAY); /* Wait for the GIC to freeze */ +} + +/* Recouple GIC with the interrupt bus */ +void ux500_pm_gic_recouple(void) +{ + prcmu_write_masked(PRCM_A9_MASK_REQ, + PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ, + 0); + + /* TODO: Use the ack bit when possible */ +} + +#define GIC_NUMBER_REGS 5 +bool ux500_pm_gic_pending_interrupt(void) +{ + u32 pr; /* Pending register */ + u32 er; /* Enable register */ + int i; + + /* 5 registers. STI & PPI not skipped */ + for (i = 0; i < GIC_NUMBER_REGS; i++) { + + pr = readl_relaxed(__io_address(U8500_GIC_DIST_BASE) + + GIC_DIST_PENDING_SET + i * 4); + er = readl_relaxed(__io_address(U8500_GIC_DIST_BASE) + + GIC_DIST_ENABLE_SET + i * 4); + + if (pr & er) + return true; /* There is a pending interrupt */ + } + return false; +} + +#define GIC_NUMBER_SPI_REGS 4 +bool ux500_pm_prcmu_pending_interrupt(void) +{ + u32 it; + u32 im; + int i; + + for (i = 0; i < GIC_NUMBER_SPI_REGS; i++) { /* There are 4 registers */ + + it = prcmu_read(PRCM_ARMITVAL31TO0 + i * 4); + im = prcmu_read(PRCM_ARMITMSK31TO0 + i * 4); + + if (it & im) + return true; /* There is a pending interrupt */ + } + + return false; +} + +void ux500_pm_prcmu_set_ioforce(bool enable) +{ + if (enable) + prcmu_write_masked(PRCM_IOCR, + PRCM_IOCR_IOFORCE, + PRCM_IOCR_IOFORCE); + else + prcmu_write_masked(PRCM_IOCR, + PRCM_IOCR_IOFORCE, + 0); +} + +void ux500_pm_prcmu_copy_gic_settings(void) +{ + u32 er; /* Enable register */ + int i; + + for (i = 0; i < GIC_NUMBER_SPI_REGS; i++) { /* 4*32 SPI interrupts */ + /* +1 due to skip STI and PPI */ + er = readl_relaxed(__io_address(U8500_GIC_DIST_BASE) + + GIC_DIST_ENABLE_SET + (i + 1) * 4); + prcmu_write(PRCM_ARMITMSK31TO0 + i * 4, er); + } +} + +void ux500_pm_gpio_save_wake_up_status(void) +{ + int num_banks; + u32 *banks; + int i; + + if (cpu_is_u5500()) { + num_banks = ARRAY_SIZE(u5500_gpio_banks); + banks = u5500_gpio_banks; + } else { + num_banks = ARRAY_SIZE(u8500_gpio_banks); + banks = u8500_gpio_banks; + } + + nmk_gpio_clocks_enable(); + + for (i = 0; i < num_banks; i++) + ux500_gpio_wks[i] = readl(__io_address(banks[i]) + NMK_GPIO_WKS); + + nmk_gpio_clocks_disable(); +} + +u32 ux500_pm_gpio_read_wake_up_status(unsigned int bank_num) +{ + if (WARN_ON(cpu_is_u5500() && bank_num >= + ARRAY_SIZE(u5500_gpio_banks))) + return 0; + + if (WARN_ON(cpu_is_u8500() && bank_num >= + ARRAY_SIZE(u8500_gpio_banks))) + return 0; + + return ux500_gpio_wks[bank_num]; +} + +/* Check if the other CPU is in WFI */ +bool ux500_pm_other_cpu_wfi(void) +{ + if (smp_processor_id()) { + /* We are CPU 1 => check if CPU0 is in WFI */ + if (prcmu_read(PRCM_ARM_WFI_STANDBY) & + PRCM_ARM_WFI_STANDBY_CPU0_WFI) + return true; + } else { + /* We are CPU 0 => check if CPU1 is in WFI */ + if (prcmu_read(PRCM_ARM_WFI_STANDBY) & + PRCM_ARM_WFI_STANDBY_CPU1_WFI) + return true; + } + + return false; +} diff --git a/arch/arm/mach-ux500/pm/prcmu-qos-power.c b/arch/arm/mach-ux500/pm/prcmu-qos-power.c new file mode 100644 index 00000000000..3bd99b766af --- /dev/null +++ b/arch/arm/mach-ux500/pm/prcmu-qos-power.c @@ -0,0 +1,711 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Martin Persson + * Per Fransson <per.xx.fransson@stericsson.com> + * + * Quality of Service for the U8500 PRCM Unit interface driver + * + * Strongly influenced by kernel/pm_qos_params.c. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/cpufreq.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/cpufreq-dbx500.h> + +#include <mach/prcmu-debug.h> + +#define ARM_THRESHOLD_FREQ (400000) + +static int qos_delayed_cpufreq_notifier(struct notifier_block *, + unsigned long, void *); + +static s32 cpufreq_requirement_queued; +static s32 cpufreq_requirement_set; + +/* + * locking rule: all changes to requirements or prcmu_qos_object list + * and prcmu_qos_objects need to happen with prcmu_qos_lock + * held, taken with _irqsave. One lock to rule them all + */ +struct requirement_list { + struct list_head list; + union { + s32 value; + s32 usec; + s32 kbps; + }; + char *name; +}; + +static s32 max_compare(s32 v1, s32 v2); + +struct prcmu_qos_object { + struct requirement_list requirements; + struct blocking_notifier_head *notifiers; + struct miscdevice prcmu_qos_power_miscdev; + char *name; + s32 default_value; + s32 force_value; + atomic_t target_value; + s32 (*comparitor)(s32, s32); +}; + +static struct prcmu_qos_object null_qos; +static BLOCKING_NOTIFIER_HEAD(prcmu_ape_opp_notifier); +static BLOCKING_NOTIFIER_HEAD(prcmu_ddr_opp_notifier); + +static struct prcmu_qos_object ape_opp_qos = { + .requirements = { + LIST_HEAD_INIT(ape_opp_qos.requirements.list) + }, + .notifiers = &prcmu_ape_opp_notifier, + .name = "ape_opp", + /* Target value in % APE OPP */ + .default_value = 50, + .force_value = 0, + .target_value = ATOMIC_INIT(50), + .comparitor = max_compare +}; + +static struct prcmu_qos_object ddr_opp_qos = { + .requirements = { + LIST_HEAD_INIT(ddr_opp_qos.requirements.list) + }, + .notifiers = &prcmu_ddr_opp_notifier, + .name = "ddr_opp", + /* Target value in % DDR OPP */ + .default_value = 25, + .force_value = 0, + .target_value = ATOMIC_INIT(25), + .comparitor = max_compare +}; + +static struct prcmu_qos_object arm_opp_qos = { + .requirements = { + LIST_HEAD_INIT(arm_opp_qos.requirements.list) + }, + /* + * No notifier on ARM opp qos request, since this won't actually + * do anything, except changing limits for cpufreq + */ + .name = "arm_opp", + /* Target value in % ARM OPP, note can be 125% */ + .default_value = 25, + .force_value = 0, + .target_value = ATOMIC_INIT(25), + .comparitor = max_compare +}; + +static struct prcmu_qos_object *prcmu_qos_array[] = { + &null_qos, + &ape_opp_qos, + &ddr_opp_qos, + &arm_opp_qos, +}; + +static DEFINE_MUTEX(prcmu_qos_mutex); +static DEFINE_SPINLOCK(prcmu_qos_lock); + +static bool ape_opp_50_partly_25_enabled; + +static unsigned long cpufreq_opp_delay = HZ / 5; + +unsigned long prcmu_qos_get_cpufreq_opp_delay(void) +{ + return cpufreq_opp_delay; +} + +static struct notifier_block qos_delayed_cpufreq_notifier_block = { + .notifier_call = qos_delayed_cpufreq_notifier, +}; + +void prcmu_qos_set_cpufreq_opp_delay(unsigned long n) +{ + if (n == 0) { + cpufreq_unregister_notifier(&qos_delayed_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + prcmu_qos_update_requirement(PRCMU_QOS_DDR_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + cpufreq_requirement_set = PRCMU_QOS_DEFAULT_VALUE; + cpufreq_requirement_queued = PRCMU_QOS_DEFAULT_VALUE; + } else if (cpufreq_opp_delay != 0) { + cpufreq_register_notifier(&qos_delayed_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + } + cpufreq_opp_delay = n; +} +#ifdef CONFIG_CPU_FREQ +static void update_cpu_limits(s32 extreme_value) +{ + int cpu; + struct cpufreq_policy policy; + int ret; + int min_freq, max_freq; + + for_each_online_cpu(cpu) { + ret = cpufreq_get_policy(&policy, cpu); + if (ret) { + pr_err("prcmu qos: get cpufreq policy failed (cpu%d)\n", + cpu); + continue; + } + + ret = dbx500_cpufreq_get_limits(cpu, extreme_value, + &min_freq, &max_freq); + if (ret) + continue; + /* + * cpufreq fw does not allow frequency change if + * "current min freq" > "new max freq" or + * "current max freq" < "new min freq". + * Thus the intermediate steps below. + */ + if (policy.min > max_freq) { + ret = cpufreq_update_freq(cpu, min_freq, policy.max); + if (ret) + pr_err("prcmu qos: update min cpufreq failed (1)\n"); + } + if (policy.max < min_freq) { + ret = cpufreq_update_freq(cpu, policy.min, max_freq); + if (ret) + pr_err("prcmu qos: update max cpufreq failed (2)\n"); + } + + ret = cpufreq_update_freq(cpu, min_freq, max_freq); + if (ret) + pr_err("prcmu qos: update max cpufreq failed (3)\n"); + } + +} +#else +static inline void update_cpu_limits(s32 extreme_value) { } +#endif +/* static helper function */ +static s32 max_compare(s32 v1, s32 v2) +{ + return max(v1, v2); +} + +static void update_target(int target) +{ + s32 extreme_value; + struct requirement_list *node; + unsigned long flags; + bool update = false; + u8 op; + + mutex_lock(&prcmu_qos_mutex); + + spin_lock_irqsave(&prcmu_qos_lock, flags); + extreme_value = prcmu_qos_array[target]->default_value; + + if (prcmu_qos_array[target]->force_value != 0) { + extreme_value = prcmu_qos_array[target]->force_value; + update = true; + } else { + list_for_each_entry(node, + &prcmu_qos_array[target]->requirements.list, + list) { + extreme_value = prcmu_qos_array[target]->comparitor( + extreme_value, node->value); + } + if (atomic_read(&prcmu_qos_array[target]->target_value) + != extreme_value) { + update = true; + atomic_set(&prcmu_qos_array[target]->target_value, + extreme_value); + pr_debug("prcmu qos: new target for qos %d is %d\n", + target, atomic_read( + &prcmu_qos_array[target]->target_value + )); + } + } + + spin_unlock_irqrestore(&prcmu_qos_lock, flags); + + if (!update) + goto unlock_and_return; + + if (prcmu_qos_array[target]->notifiers) + blocking_notifier_call_chain(prcmu_qos_array[target]->notifiers, + (unsigned long)extreme_value, + NULL); + switch (target) { + case PRCMU_QOS_DDR_OPP: + switch (extreme_value) { + case 50: + op = DDR_50_OPP; + pr_debug("prcmu qos: set ddr opp to 50%%\n"); + break; + case 100: + op = DDR_100_OPP; + pr_debug("prcmu qos: set ddr opp to 100%%\n"); + break; + case 25: + /* 25% DDR OPP is not supported on 5500 */ + if (!cpu_is_u5500()) { + op = DDR_25_OPP; + pr_debug("prcmu qos: set ddr opp to 25%%\n"); + break; + } + default: + pr_err("prcmu qos: Incorrect ddr target value (%d)", + extreme_value); + goto unlock_and_return; + } + prcmu_set_ddr_opp(op); + prcmu_debug_ddr_opp_log(op); + break; + case PRCMU_QOS_APE_OPP: + switch (extreme_value) { + case 50: + if (ape_opp_50_partly_25_enabled) + op = APE_50_PARTLY_25_OPP; + else + op = APE_50_OPP; + pr_debug("prcmu qos: set ape opp to 50%%\n"); + break; + case 100: + op = APE_100_OPP; + pr_debug("prcmu qos: set ape opp to 100%%\n"); + break; + default: + pr_err("prcmu qos: Incorrect ape target value (%d)", + extreme_value); + goto unlock_and_return; + } + (void)prcmu_set_ape_opp(op); + prcmu_debug_ape_opp_log(op); + break; + case PRCMU_QOS_ARM_OPP: + { + mutex_unlock(&prcmu_qos_mutex); + /* + * We can't hold the mutex since changing cpufreq + * will trigger an prcmu fw callback. + */ + update_cpu_limits(extreme_value); + /* Return since the lock is unlocked */ + return; + + break; + } + default: + pr_err("prcmu qos: Incorrect target\n"); + break; + } + +unlock_and_return: + mutex_unlock(&prcmu_qos_mutex); +} + +void prcmu_qos_force_opp(int prcmu_qos_class, s32 i) +{ + prcmu_qos_array[prcmu_qos_class]->force_value = i; + update_target(prcmu_qos_class); +} + +void prcmu_qos_voice_call_override(bool enable) +{ + int ape_opp; + + mutex_lock(&prcmu_qos_mutex); + + ape_opp_50_partly_25_enabled = enable; + + ape_opp = prcmu_get_ape_opp(); + + if (ape_opp == APE_50_OPP) { + if (enable) + prcmu_set_ape_opp(APE_50_PARTLY_25_OPP); + else + prcmu_set_ape_opp(APE_50_OPP); + } + + mutex_unlock(&prcmu_qos_mutex); +} + +/** + * prcmu_qos_requirement - returns current prcmu qos expectation + * @prcmu_qos_class: identification of which qos value is requested + * + * This function returns the current target value in an atomic manner. + */ +int prcmu_qos_requirement(int prcmu_qos_class) +{ + return atomic_read(&prcmu_qos_array[prcmu_qos_class]->target_value); +} +EXPORT_SYMBOL_GPL(prcmu_qos_requirement); + +/** + * prcmu_qos_add_requirement - inserts new qos request into the list + * @prcmu_qos_class: identifies which list of qos request to us + * @name: identifies the request + * @value: defines the qos request + * + * This function inserts a new entry in the prcmu_qos_class list of requested + * qos performance characteristics. It recomputes the aggregate QoS + * expectations for the prcmu_qos_class of parameters. + */ +int prcmu_qos_add_requirement(int prcmu_qos_class, char *name, s32 value) +{ + struct requirement_list *dep; + unsigned long flags; + + dep = kzalloc(sizeof(struct requirement_list), GFP_KERNEL); + if (dep == NULL) + return -ENOMEM; + + if (value == PRCMU_QOS_DEFAULT_VALUE) + dep->value = prcmu_qos_array[prcmu_qos_class]->default_value; + else + dep->value = value; + dep->name = kstrdup(name, GFP_KERNEL); + if (!dep->name) + goto cleanup; + + spin_lock_irqsave(&prcmu_qos_lock, flags); + list_add(&dep->list, + &prcmu_qos_array[prcmu_qos_class]->requirements.list); + spin_unlock_irqrestore(&prcmu_qos_lock, flags); + update_target(prcmu_qos_class); + + return 0; + +cleanup: + kfree(dep); + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(prcmu_qos_add_requirement); + +/** + * prcmu_qos_update_requirement - modifies an existing qos request + * @prcmu_qos_class: identifies which list of qos request to us + * @name: identifies the request + * @value: defines the qos request + * + * Updates an existing qos requirement for the prcmu_qos_class of parameters + * along with updating the target prcmu_qos_class value. + * + * If the named request isn't in the list then no change is made. + */ +int prcmu_qos_update_requirement(int prcmu_qos_class, char *name, s32 new_value) +{ + unsigned long flags; + struct requirement_list *node; + int pending_update = 0; + + spin_lock_irqsave(&prcmu_qos_lock, flags); + list_for_each_entry(node, + &prcmu_qos_array[prcmu_qos_class]->requirements.list, list) { + if (strcmp(node->name, name) == 0) { + if (new_value == PRCMU_QOS_DEFAULT_VALUE) + node->value = + prcmu_qos_array[prcmu_qos_class]->default_value; + else + node->value = new_value; + pending_update = 1; + break; + } + } + spin_unlock_irqrestore(&prcmu_qos_lock, flags); + if (pending_update) + update_target(prcmu_qos_class); + + return 0; +} +EXPORT_SYMBOL_GPL(prcmu_qos_update_requirement); + +/** + * prcmu_qos_remove_requirement - modifies an existing qos request + * @prcmu_qos_class: identifies which list of qos request to us + * @name: identifies the request + * + * Will remove named qos request from prcmu_qos_class list of parameters and + * recompute the current target value for the prcmu_qos_class. + */ +void prcmu_qos_remove_requirement(int prcmu_qos_class, char *name) +{ + unsigned long flags; + struct requirement_list *node; + int pending_update = 0; + + spin_lock_irqsave(&prcmu_qos_lock, flags); + list_for_each_entry(node, + &prcmu_qos_array[prcmu_qos_class]->requirements.list, list) { + if (strcmp(node->name, name) == 0) { + kfree(node->name); + list_del(&node->list); + kfree(node); + pending_update = 1; + break; + } + } + spin_unlock_irqrestore(&prcmu_qos_lock, flags); + if (pending_update) + update_target(prcmu_qos_class); +} +EXPORT_SYMBOL_GPL(prcmu_qos_remove_requirement); + +/** + * prcmu_qos_add_notifier - sets notification entry for changes to target value + * @prcmu_qos_class: identifies which qos target changes should be notified. + * @notifier: notifier block managed by caller. + * + * will register the notifier into a notification chain that gets called + * upon changes to the prcmu_qos_class target value. + */ +int prcmu_qos_add_notifier(int prcmu_qos_class, struct notifier_block *notifier) +{ + int retval = -EINVAL; + + if (prcmu_qos_array[prcmu_qos_class]->notifiers) + retval = blocking_notifier_chain_register( + prcmu_qos_array[prcmu_qos_class]->notifiers, notifier); + + return retval; +} +EXPORT_SYMBOL_GPL(prcmu_qos_add_notifier); + +/** + * prcmu_qos_remove_notifier - deletes notification entry from chain. + * @prcmu_qos_class: identifies which qos target changes are notified. + * @notifier: notifier block to be removed. + * + * will remove the notifier from the notification chain that gets called + * upon changes to the prcmu_qos_class target value. + */ +int prcmu_qos_remove_notifier(int prcmu_qos_class, + struct notifier_block *notifier) +{ + int retval = -EINVAL; + if (prcmu_qos_array[prcmu_qos_class]->notifiers) + retval = blocking_notifier_chain_unregister( + prcmu_qos_array[prcmu_qos_class]->notifiers, notifier); + + return retval; +} +EXPORT_SYMBOL_GPL(prcmu_qos_remove_notifier); + +#define USER_QOS_NAME_LEN 32 + +static int prcmu_qos_power_open(struct inode *inode, struct file *filp, + long prcmu_qos_class) +{ + int ret; + char name[USER_QOS_NAME_LEN]; + + filp->private_data = (void *)prcmu_qos_class; + snprintf(name, USER_QOS_NAME_LEN, "file_%08x", (unsigned int)filp); + ret = prcmu_qos_add_requirement(prcmu_qos_class, name, + PRCMU_QOS_DEFAULT_VALUE); + if (ret >= 0) + return 0; + + return -EPERM; +} + + +static int prcmu_qos_ape_power_open(struct inode *inode, struct file *filp) +{ + return prcmu_qos_power_open(inode, filp, PRCMU_QOS_APE_OPP); +} + +static int prcmu_qos_ddr_power_open(struct inode *inode, struct file *filp) +{ + return prcmu_qos_power_open(inode, filp, PRCMU_QOS_DDR_OPP); +} + +static int prcmu_qos_arm_power_open(struct inode *inode, struct file *filp) +{ + return prcmu_qos_power_open(inode, filp, PRCMU_QOS_ARM_OPP); +} + +static int prcmu_qos_power_release(struct inode *inode, struct file *filp) +{ + int prcmu_qos_class; + char name[USER_QOS_NAME_LEN]; + + prcmu_qos_class = (long)filp->private_data; + snprintf(name, USER_QOS_NAME_LEN, "file_%08x", (unsigned int)filp); + prcmu_qos_remove_requirement(prcmu_qos_class, name); + + return 0; +} + +static ssize_t prcmu_qos_power_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + s32 value; + int prcmu_qos_class; + char name[USER_QOS_NAME_LEN]; + + prcmu_qos_class = (long)filp->private_data; + if (count != sizeof(s32)) + return -EINVAL; + if (copy_from_user(&value, buf, sizeof(s32))) + return -EFAULT; + snprintf(name, USER_QOS_NAME_LEN, "file_%08x", (unsigned int)filp); + prcmu_qos_update_requirement(prcmu_qos_class, name, value); + + return sizeof(s32); +} + +/* Functions to provide QoS to user space */ +static const struct file_operations prcmu_qos_ape_power_fops = { + .write = prcmu_qos_power_write, + .open = prcmu_qos_ape_power_open, + .release = prcmu_qos_power_release, +}; + +/* Functions to provide QoS to user space */ +static const struct file_operations prcmu_qos_ddr_power_fops = { + .write = prcmu_qos_power_write, + .open = prcmu_qos_ddr_power_open, + .release = prcmu_qos_power_release, +}; + +static const struct file_operations prcmu_qos_arm_power_fops = { + .write = prcmu_qos_power_write, + .open = prcmu_qos_arm_power_open, + .release = prcmu_qos_power_release, +}; + +static int register_prcmu_qos_misc(struct prcmu_qos_object *qos, + const struct file_operations *fops) +{ + qos->prcmu_qos_power_miscdev.minor = MISC_DYNAMIC_MINOR; + qos->prcmu_qos_power_miscdev.name = qos->name; + qos->prcmu_qos_power_miscdev.fops = fops; + + return misc_register(&qos->prcmu_qos_power_miscdev); +} + +static void qos_delayed_work_up_fn(struct work_struct *work) +{ + prcmu_qos_update_requirement(PRCMU_QOS_DDR_OPP, "cpufreq", 100); + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "cpufreq", 100); + cpufreq_requirement_set = 100; +} + +static void qos_delayed_work_down_fn(struct work_struct *work) +{ + prcmu_qos_update_requirement(PRCMU_QOS_DDR_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + cpufreq_requirement_set = PRCMU_QOS_DEFAULT_VALUE; +} + +static DECLARE_DELAYED_WORK(qos_delayed_work_up, qos_delayed_work_up_fn); +static DECLARE_DELAYED_WORK(qos_delayed_work_down, qos_delayed_work_down_fn); + +static int qos_delayed_cpufreq_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct cpufreq_freqs *freq = data; + s32 new_ddr_target; + + /* Only react once per transition and only for one core, e.g. core 0 */ + if (event != CPUFREQ_POSTCHANGE || freq->cpu != 0) + return 0; + + /* + * APE and DDR OPP are always handled together in this solution. + * Hence no need to check both DDR and APE opp in the code below. + */ + + /* Which DDR OPP are we aiming for? */ + if (freq->new > ARM_THRESHOLD_FREQ) + new_ddr_target = 100; + else + new_ddr_target = PRCMU_QOS_DEFAULT_VALUE; + + if (new_ddr_target == cpufreq_requirement_queued) { + /* + * We're already at, or going to, the target requirement. + * This is only a fluctuation within the interval + * corresponding to the same DDR requirement. + */ + return 0; + } + cpufreq_requirement_queued = new_ddr_target; + + if (freq->new > ARM_THRESHOLD_FREQ) { + cancel_delayed_work_sync(&qos_delayed_work_down); + /* + * Only schedule this requirement if it is not the current + * one. + */ + if (new_ddr_target != cpufreq_requirement_set) + schedule_delayed_work(&qos_delayed_work_up, + cpufreq_opp_delay); + } else { + cancel_delayed_work_sync(&qos_delayed_work_up); + /* + * Only schedule this requirement if it is not the current + * one. + */ + if (new_ddr_target != cpufreq_requirement_set) + schedule_delayed_work(&qos_delayed_work_down, + cpufreq_opp_delay); + } + + return 0; +} + +static int __init prcmu_qos_power_init(void) +{ + int ret; + + /* 25% DDR OPP is not supported on u5500 */ + if (cpu_is_u5500()) { + ddr_opp_qos.default_value = 50; + atomic_set(&ddr_opp_qos.target_value, 50); + } + + ret = register_prcmu_qos_misc(&ape_opp_qos, &prcmu_qos_ape_power_fops); + if (ret < 0) { + pr_err("prcmu ape qos: setup failed\n"); + return ret; + } + + ret = register_prcmu_qos_misc(&ddr_opp_qos, &prcmu_qos_ddr_power_fops); + if (ret < 0) { + pr_err("prcmu ddr qos: setup failed\n"); + return ret; + } + + ret = register_prcmu_qos_misc(&arm_opp_qos, &prcmu_qos_arm_power_fops); + if (ret < 0) { + pr_err("prcmu arm qos: setup failed\n"); + return ret; + } + + prcmu_qos_add_requirement(PRCMU_QOS_DDR_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + cpufreq_requirement_set = PRCMU_QOS_DEFAULT_VALUE; + cpufreq_requirement_queued = PRCMU_QOS_DEFAULT_VALUE; + + cpufreq_register_notifier(&qos_delayed_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + return ret; +} + +late_initcall(prcmu_qos_power_init); diff --git a/arch/arm/mach-ux500/pm/runtime.c b/arch/arm/mach-ux500/pm/runtime.c new file mode 100644 index 00000000000..8608c43479e --- /dev/null +++ b/arch/arm/mach-ux500/pm/runtime.c @@ -0,0 +1,514 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Rabin Vincent <rabin.vincent@stericsson> for ST-Ericsson + * + * Based on: + * Runtime PM support code for SuperH Mobile ARM + * Copyright (C) 2009-2010 Magnus Damm + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/pm_runtime.h> +#include <linux/platform_device.h> +#include <linux/amba/bus.h> +#include <linux/regulator/dbx500-prcmu.h> +#include <linux/clk.h> +#include <linux/gfp.h> +#include <plat/pincfg.h> + +#include "../pins.h" + +#ifdef CONFIG_PM_RUNTIME +#define BIT_ONCE 0 +#define BIT_ACTIVE 1 +#define BIT_ENABLED 2 + +struct pm_runtime_data { + unsigned long flags; + struct ux500_regulator *regulator; + struct ux500_pins *pins; +}; + +static void __devres_release(struct device *dev, void *res) +{ + struct pm_runtime_data *prd = res; + + dev_dbg(dev, "__devres_release()\n"); + + if (test_bit(BIT_ENABLED, &prd->flags)) { + if (prd->pins) + ux500_pins_disable(prd->pins); + if (prd->regulator) + ux500_regulator_atomic_disable(prd->regulator); + } + + if (test_bit(BIT_ACTIVE, &prd->flags)) { + if (prd->pins) + ux500_pins_put(prd->pins); + if (prd->regulator) + ux500_regulator_put(prd->regulator); + } +} + +static struct pm_runtime_data *__to_prd(struct device *dev) +{ + return devres_find(dev, __devres_release, NULL, NULL); +} + +static void platform_pm_runtime_init(struct device *dev, + struct pm_runtime_data *prd) +{ + prd->pins = ux500_pins_get(dev_name(dev)); + + prd->regulator = ux500_regulator_get(dev); + if (IS_ERR(prd->regulator)) + prd->regulator = NULL; + + if (prd->pins || prd->regulator) { + dev_info(dev, "managed by runtime pm: %s%s\n", + prd->pins ? "pins " : "", + prd->regulator ? "regulator " : ""); + + set_bit(BIT_ACTIVE, &prd->flags); + } +} + +static void platform_pm_runtime_bug(struct device *dev, + struct pm_runtime_data *prd) +{ + if (prd && !test_and_set_bit(BIT_ONCE, &prd->flags)) + dev_err(dev, "runtime pm suspend before resume\n"); +} + +static void platform_pm_runtime_used(struct device *dev, + struct pm_runtime_data *prd) +{ + if (prd) + set_bit(BIT_ONCE, &prd->flags); +} + +static int ux500_pd_runtime_idle(struct device *dev) +{ + return pm_runtime_suspend(dev); +} + +static void ux500_pd_disable(struct pm_runtime_data *prd) +{ + if (prd && test_bit(BIT_ACTIVE, &prd->flags)) { + + if (prd->pins) + ux500_pins_disable(prd->pins); + + if (prd->regulator) + ux500_regulator_atomic_disable(prd->regulator); + + clear_bit(BIT_ENABLED, &prd->flags); + } +} + +static int ux500_pd_runtime_suspend(struct device *dev) +{ + int ret; + struct pm_runtime_data *prd = __to_prd(dev); + + dev_vdbg(dev, "%s()\n", __func__); + + platform_pm_runtime_bug(dev, prd); + + ret = pm_generic_runtime_suspend(dev); + if (ret) + return ret; + + ux500_pd_disable(prd); + + return 0; +} + +static void ux500_pd_enable(struct pm_runtime_data *prd) +{ + if (prd && test_bit(BIT_ACTIVE, &prd->flags)) { + if (prd->pins) + ux500_pins_enable(prd->pins); + + if (prd->regulator) + ux500_regulator_atomic_enable(prd->regulator); + + set_bit(BIT_ENABLED, &prd->flags); + } +} + +static int ux500_pd_runtime_resume(struct device *dev) +{ + struct pm_runtime_data *prd = __to_prd(dev); + + dev_vdbg(dev, "%s()\n", __func__); + + platform_pm_runtime_used(dev, prd); + ux500_pd_enable(prd); + + return pm_generic_runtime_resume(dev); +} + +static int ux500_pd_suspend_noirq(struct device *dev) +{ + struct pm_runtime_data *prd = __to_prd(dev); + + dev_vdbg(dev, "%s()\n", __func__); + + /* Only handle devices that use runtime pm */ + if (!prd || !test_bit(BIT_ONCE, &prd->flags)) + return 0; + + /* Already is runtime suspended? Nothing to do. */ + if (pm_runtime_status_suspended(dev)) + return 0; + + /* + * We get here only if the device was not runtime suspended for some + * reason. We still need to do the power save stuff when going into + * suspend, so force it here. + */ + return ux500_pd_runtime_suspend(dev); +} + +static int ux500_pd_resume_noirq(struct device *dev) +{ + struct pm_runtime_data *prd = __to_prd(dev); + + dev_vdbg(dev, "%s()\n", __func__); + + /* Only handle devices that use runtime pm */ + if (!prd || !test_bit(BIT_ONCE, &prd->flags)) + return 0; + + /* + * Already was runtime suspended? No need to resume here, runtime + * resume will take care of it. + */ + if (pm_runtime_status_suspended(dev)) + return 0; + + /* + * We get here only if the device was not runtime suspended, + * but we forced it down in suspend_noirq above. Bring it + * up since pm-runtime thinks it is not suspended. + */ + return ux500_pd_runtime_resume(dev); +} +#ifdef CONFIG_UX500_SUSPEND +static int ux500_pd_amba_suspend_noirq(struct device *dev) +{ + struct pm_runtime_data *prd = __to_prd(dev); + int (*callback)(struct device *) = NULL; + int ret = 0; + bool is_suspended = pm_runtime_status_suspended(dev); + + dev_vdbg(dev, "%s()\n", __func__); + + /* + * Do not bypass AMBA bus pm functions by calling generic + * pm directly. A future fix could be to implement a + * "pm_bus_generic_*" API which we can use instead. + */ + if (dev->bus && dev->bus->pm) + callback = dev->bus->pm->suspend_noirq; + + if (callback) + ret = callback(dev); + else + ret = pm_generic_suspend_noirq(dev); + + if (!ret && !is_suspended) + ux500_pd_disable(prd); + + return ret; +} + +static int ux500_pd_amba_resume_noirq(struct device *dev) +{ + struct pm_runtime_data *prd = __to_prd(dev); + int (*callback)(struct device *) = NULL; + int ret = 0; + bool is_suspended = pm_runtime_status_suspended(dev); + + dev_vdbg(dev, "%s()\n", __func__); + + /* + * Do not bypass AMBA bus pm functions by calling generic + * pm directly. A future fix could be to implement a + * "pm_bus_generic_*" API which we can use instead. + */ + if (dev->bus && dev->bus->pm) + callback = dev->bus->pm->resume_noirq; + + if (callback) + ret = callback(dev); + else + ret = pm_generic_resume_noirq(dev); + + if (!ret && !is_suspended) + ux500_pd_enable(prd); + + return ret; +} +#else +static int ux500_pd_amba_suspend_noirq(struct device *dev) +{ + return 0; +} +static int ux500_pd_amba_resume_noirq(struct device *dev) +{ + return 0; +} +#endif +static int ux500_pd_amba_runtime_suspend(struct device *dev) +{ + struct pm_runtime_data *prd = __to_prd(dev); + int (*callback)(struct device *) = NULL; + int ret; + + dev_vdbg(dev, "%s()\n", __func__); + + /* + * Do this first, to make sure pins is not in undefined state after + * drivers has run their runtime suspend. This also means that drivers + * are not able to use their pins/regulators during runtime suspend. + */ + ux500_pd_disable(prd); + + /* + * Do not bypass AMBA bus pm functions by calling generic + * pm directly. A future fix could be to implement a + * "pm_bus_generic_*" API which we can use instead. + */ + if (dev->bus && dev->bus->pm) + callback = dev->bus->pm->runtime_suspend; + + if (callback) + ret = callback(dev); + else + ret = pm_generic_runtime_suspend(dev); + + if (ret) + ux500_pd_enable(prd); + + return ret; +} + +static int ux500_pd_amba_runtime_resume(struct device *dev) +{ + struct pm_runtime_data *prd = __to_prd(dev); + int (*callback)(struct device *) = NULL; + int ret; + + dev_vdbg(dev, "%s()\n", __func__); + + /* + * Do not bypass AMBA bus pm functions by calling generic + * pm directly. A future fix could be to implement a + * "pm_bus_generic_*" API which we can use instead. + */ + if (dev->bus && dev->bus->pm) + callback = dev->bus->pm->runtime_resume; + + if (callback) + ret = callback(dev); + else + ret = pm_generic_runtime_resume(dev); + + /* + * Restore pins/regulator after drivers has runtime resumed, due + * to that we must not have pins in undefined state. This also means + * that drivers are not able to use their pins/regulators during + * runtime resume. + */ + if (!ret) + ux500_pd_enable(prd); + + return ret; +} + +static int ux500_pd_amba_runtime_idle(struct device *dev) +{ + int (*callback)(struct device *) = NULL; + int ret; + + dev_vdbg(dev, "%s()\n", __func__); + + /* + * Do not bypass AMBA bus runtime functions by calling generic runtime + * directly. A future fix could be to implement a + * "pm_bus_generic_runtime_*" API which we can use instead. + */ + if (dev->bus && dev->bus->pm) + callback = dev->bus->pm->runtime_idle; + + if (callback) + ret = callback(dev); + else + ret = pm_generic_runtime_idle(dev); + + return ret; +} + +static int ux500_pd_bus_notify(struct notifier_block *nb, + unsigned long action, + void *data, + bool enable) +{ + struct device *dev = data; + struct pm_runtime_data *prd; + + dev_dbg(dev, "%s() %ld !\n", __func__, action); + + if (action == BUS_NOTIFY_BIND_DRIVER) { + prd = devres_alloc(__devres_release, sizeof(*prd), GFP_KERNEL); + if (prd) { + devres_add(dev, prd); + platform_pm_runtime_init(dev, prd); + if (enable) + ux500_pd_enable(prd); + } else + dev_err(dev, "unable to alloc memory for runtime pm\n"); + } + + return 0; +} + +static int ux500_pd_plat_bus_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + return ux500_pd_bus_notify(nb, action, data, false); +} + +static int ux500_pd_amba_bus_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + return ux500_pd_bus_notify(nb, action, data, true); +} + +#else /* CONFIG_PM_RUNTIME */ + +#define ux500_pd_suspend_noirq NULL +#define ux500_pd_resume_noirq NULL +#define ux500_pd_runtime_idle NULL +#define ux500_pd_runtime_suspend NULL +#define ux500_pd_runtime_resume NULL + +static int ux500_pd_bus_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct ux500_regulator *regulator = NULL; + struct ux500_pins *pins = NULL; + struct device *dev = data; + const char *onoff = NULL; + + dev_dbg(dev, "%s() %ld !\n", __func__, action); + + switch (action) { + case BUS_NOTIFY_BIND_DRIVER: + pins = ux500_pins_get(dev_name(dev)); + if (pins) { + ux500_pins_enable(pins); + ux500_pins_put(pins); + } + + regulator = ux500_regulator_get(dev); + if (IS_ERR(regulator)) + regulator = NULL; + else { + ux500_regulator_atomic_enable(regulator); + ux500_regulator_put(regulator); + } + + onoff = "on"; + break; + case BUS_NOTIFY_UNBOUND_DRIVER: + pins = ux500_pins_get(dev_name(dev)); + if (pins) { + ux500_pins_disable(pins); + ux500_pins_put(pins); + } + + regulator = ux500_regulator_get(dev); + if (IS_ERR(regulator)) + regulator = NULL; + else { + ux500_regulator_atomic_disable(regulator); + ux500_regulator_put(regulator); + } + + onoff = "off"; + break; + } + + if (pins || regulator) { + dev_info(dev, "runtime pm disabled, forced %s: %s%s\n", + onoff, + pins ? "pins " : "", + regulator ? "regulator " : ""); + } + + return 0; +} + +#endif /* CONFIG_PM_RUNTIME */ + +struct dev_pm_domain ux500_amba_dev_power_domain = { + .ops = { + .suspend = amba_pm_suspend, + .resume = amba_pm_resume, + .freeze = amba_pm_freeze, + .thaw = amba_pm_thaw, + .poweroff = amba_pm_poweroff, + .restore = amba_pm_restore, + SET_RUNTIME_PM_OPS(ux500_pd_amba_runtime_suspend, + ux500_pd_amba_runtime_resume, + ux500_pd_amba_runtime_idle) + .suspend_noirq = ux500_pd_amba_suspend_noirq, + .resume_noirq = ux500_pd_amba_resume_noirq, + }, +}; + +struct dev_pm_domain ux500_dev_power_domain = { + .ops = { + SET_RUNTIME_PM_OPS(ux500_pd_runtime_suspend, + ux500_pd_runtime_resume, + ux500_pd_runtime_idle) + USE_PLATFORM_PM_SLEEP_OPS + .suspend_noirq = ux500_pd_suspend_noirq, + .resume_noirq = ux500_pd_resume_noirq, + }, +}; + +static struct notifier_block ux500_pd_platform_notifier = { + .notifier_call = ux500_pd_plat_bus_notify, +}; + +static struct notifier_block ux500_pd_amba_notifier = { + .notifier_call = ux500_pd_amba_bus_notify, +}; + +static int __init ux500_pm_runtime_platform_init(void) +{ + bus_register_notifier(&platform_bus_type, &ux500_pd_platform_notifier); + return 0; +} +core_initcall(ux500_pm_runtime_platform_init); + +/* + * The amba bus itself gets registered in a core_initcall, so we can't use + * that. + */ +static int __init ux500_pm_runtime_amba_init(void) +{ + bus_register_notifier(&amba_bustype, &ux500_pd_amba_notifier); + return 0; +} +arch_initcall(ux500_pm_runtime_amba_init); diff --git a/arch/arm/mach-ux500/pm/scu.h b/arch/arm/mach-ux500/pm/scu.h new file mode 100644 index 00000000000..a09e86a9d3c --- /dev/null +++ b/arch/arm/mach-ux500/pm/scu.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2009 ST-Ericsson SA + * + * 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. + */ + +#ifndef __ASMARM_ARCH_SCU_H +#define __ASMARM_ARCH_SCU_H + +#include <mach/hardware.h> + +#define SCU_BASE U8500_SCU_BASE +/* + * * SCU registers + * */ +#define SCU_CTRL 0x00 +#define SCU_CONFIG 0x04 +#define SCU_CPU_STATUS 0x08 +#define SCU_INVALIDATE 0x0c +#define SCU_FPGA_REVISION 0x10 + +#endif diff --git a/arch/arm/mach-ux500/pm/suspend.c b/arch/arm/mach-ux500/pm/suspend.c new file mode 100644 index 00000000000..df527964182 --- /dev/null +++ b/arch/arm/mach-ux500/pm/suspend.c @@ -0,0 +1,281 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * + * Authors: Rickard Andersson <rickard.andersson@stericsson.com>, + * Jonas Aaberg <jonas.aberg@stericsson.com>, + * Sundar Iyer for ST-Ericsson. + */ + +#include <linux/suspend.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/gpio/nomadik.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/ab8500-debug.h> +#include <linux/regulator/dbx500-prcmu.h> + +#include <mach/context.h> +#include <mach/pm.h> +#include <mach/id.h> + +#include "suspend_dbg.h" + +static void (*pins_suspend_force)(void); +static void (*pins_suspend_force_mux)(void); + +static suspend_state_t suspend_state = PM_SUSPEND_ON; + +void suspend_set_pins_force_fn(void (*force)(void), void (*force_mux)(void)) +{ + pins_suspend_force = force; + pins_suspend_force_mux = force_mux; +} + +static atomic_t block_sleep = ATOMIC_INIT(0); + +void suspend_block_sleep(void) +{ + atomic_inc(&block_sleep); +} + +void suspend_unblock_sleep(void) +{ + atomic_dec(&block_sleep); +} + +static bool sleep_is_blocked(void) +{ + return (atomic_read(&block_sleep) != 0); +} + +static int suspend(bool do_deepsleep) +{ + bool pins_force = pins_suspend_force_mux && pins_suspend_force; + int ret = 0; + + if (sleep_is_blocked()) { + pr_info("suspend/resume: interrupted by modem.\n"); + return -EBUSY; + } + + nmk_gpio_clocks_enable(); + + ux500_suspend_dbg_add_wake_on_uart(); + + nmk_gpio_wakeups_suspend(); + + /* configure the prcm for a sleep wakeup */ + if (cpu_is_u9500()) + prcmu_enable_wakeups(PRCMU_WAKEUP(ABB) | PRCMU_WAKEUP(HSI0)); + else +#if defined(CONFIG_RTC_DRV_PL031) + prcmu_enable_wakeups(PRCMU_WAKEUP(ABB) | PRCMU_WAKEUP(RTC)); +#else + prcmu_enable_wakeups(PRCMU_WAKEUP(ABB)); +#endif + + context_vape_save(); + + context_fsmc_save(); + + if (pins_force) { + /* + * Save GPIO settings before applying power save + * settings + */ + context_gpio_save(); + + /* Apply GPIO power save mux settings */ + context_gpio_mux_safe_switch(true); + pins_suspend_force_mux(); + context_gpio_mux_safe_switch(false); + + /* Apply GPIO power save settings */ + pins_suspend_force(); + } + + ux500_pm_gic_decouple(); + + if (ux500_pm_gic_pending_interrupt()) { + pr_info("suspend/resume: pending interrupt\n"); + + /* Recouple GIC with the interrupt bus */ + ux500_pm_gic_recouple(); + ret = -EBUSY; + + goto exit; + } + ux500_pm_prcmu_set_ioforce(true); + + if (do_deepsleep) { + context_varm_save_common(); + context_varm_save_core(); + context_gic_dist_disable_unneeded_irqs(); + context_save_cpu_registers(); + + /* + * Due to we have only 100us between requesting a powerstate + * and wfi, we clean the cache before as well to assure the + * final cache clean before wfi has as little as possible to + * do. + */ + context_clean_l1_cache_all(); + + (void) prcmu_set_power_state(PRCMU_AP_DEEP_SLEEP, + false, false); + context_save_to_sram_and_wfi(true); + + context_restore_cpu_registers(); + context_varm_restore_core(); + context_varm_restore_common(); + + } else { + + context_clean_l1_cache_all(); + (void) prcmu_set_power_state(APEXECUTE_TO_APSLEEP, + false, false); + dsb(); + __asm__ __volatile__("wfi\n\t" : : : "memory"); + } + + context_vape_restore(); + + context_fsmc_restore(); + + /* If GPIO woke us up then save the pins that caused the wake up */ + ux500_pm_gpio_save_wake_up_status(); + + ux500_suspend_dbg_sleep_status(do_deepsleep); + + /* APE was turned off, restore IO ring */ + ux500_pm_prcmu_set_ioforce(false); + +exit: + if (pins_force) { + /* Restore gpio settings */ + context_gpio_mux_safe_switch(true); + context_gpio_restore_mux(); + context_gpio_mux_safe_switch(false); + context_gpio_restore(); + } + + /* This is what cpuidle wants */ + if (cpu_is_u9500()) + prcmu_enable_wakeups(PRCMU_WAKEUP(ARM) | PRCMU_WAKEUP(RTC) | + PRCMU_WAKEUP(ABB) | PRCMU_WAKEUP(HSI0)); + else + prcmu_enable_wakeups(PRCMU_WAKEUP(ARM) | PRCMU_WAKEUP(RTC) | + PRCMU_WAKEUP(ABB)); + + nmk_gpio_wakeups_resume(); + + ux500_suspend_dbg_remove_wake_on_uart(); + + nmk_gpio_clocks_disable(); + + return ret; +} + +static int ux500_suspend_enter(suspend_state_t state) +{ + if (ux500_suspend_enabled()) { + if (ux500_suspend_deepsleep_enabled() && + state == PM_SUSPEND_MEM) + return suspend(true); + if (ux500_suspend_sleep_enabled()) + return suspend(false); + } + + ux500_suspend_dbg_add_wake_on_uart(); + /* + * Set IOFORCE in order to wake on GPIO the same way + * as in deeper sleep. + * (U5500 is not ready for IOFORCE) + */ + if (!cpu_is_u5500()) + ux500_pm_prcmu_set_ioforce(true); + + dsb(); + __asm__ __volatile__("wfi\n\t" : : : "memory"); + + if (!cpu_is_u5500()) + ux500_pm_prcmu_set_ioforce(false); + ux500_suspend_dbg_remove_wake_on_uart(); + + return 0; +} + +static int ux500_suspend_valid(suspend_state_t state) +{ + return state == PM_SUSPEND_MEM || state == PM_SUSPEND_STANDBY; +} + +static int ux500_suspend_prepare(void) +{ + int ret; + + ret = regulator_suspend_prepare(suspend_state); + if (ret < 0) + return ret; + + return 0; +} + +static int ux500_suspend_prepare_late(void) +{ + /* ESRAM to retention instead of OFF until ROM is fixed */ + (void) prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET); + + ab8500_regulator_debug_force(); + ux500_regulator_suspend_debug(); + return 0; +} + +static void ux500_suspend_wake(void) +{ + ux500_regulator_resume_debug(); + ab8500_regulator_debug_restore(); + (void) prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET); +} + +static void ux500_suspend_finish(void) +{ + (void)regulator_suspend_finish(); +} + +static int ux500_suspend_begin(suspend_state_t state) +{ + (void) prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, + "suspend", 125); + suspend_state = state; + return ux500_suspend_dbg_begin(state); +} + +static void ux500_suspend_end(void) +{ + (void) prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, + "suspend", 25); + suspend_state = PM_SUSPEND_ON; +} + +static struct platform_suspend_ops ux500_suspend_ops = { + .enter = ux500_suspend_enter, + .valid = ux500_suspend_valid, + .prepare = ux500_suspend_prepare, + .prepare_late = ux500_suspend_prepare_late, + .wake = ux500_suspend_wake, + .finish = ux500_suspend_finish, + .begin = ux500_suspend_begin, + .end = ux500_suspend_end, +}; + +static __init int ux500_suspend_init(void) +{ + ux500_suspend_dbg_init(); + prcmu_qos_add_requirement(PRCMU_QOS_ARM_OPP, "suspend", 25); + suspend_set_ops(&ux500_suspend_ops); + return 0; +} +device_initcall(ux500_suspend_init); diff --git a/arch/arm/mach-ux500/pm/suspend_dbg.c b/arch/arm/mach-ux500/pm/suspend_dbg.c new file mode 100644 index 00000000000..1b7d871ba52 --- /dev/null +++ b/arch/arm/mach-ux500/pm/suspend_dbg.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Rickard Andersson <rickard.andersson@stericsson.com>, + * Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + */ +#include <linux/kernel.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/suspend.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <mach/pm.h> + +#ifdef CONFIG_UX500_SUSPEND_STANDBY +static u32 sleep_enabled = 1; +#else +static u32 sleep_enabled; +#endif + +#ifdef CONFIG_UX500_SUSPEND_MEM +static u32 deepsleep_enabled = 1; +#else +static u32 deepsleep_enabled; +#endif + +static u32 suspend_enabled = 1; + +static u32 deepsleeps_done; +static u32 deepsleeps_failed; +static u32 sleeps_done; +static u32 sleeps_failed; +static u32 suspend_count; + +#ifdef CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART +void ux500_suspend_dbg_add_wake_on_uart(void) +{ + irq_set_irq_wake(GPIO_TO_IRQ(ux500_console_uart_gpio_pin), 1); + irq_set_irq_type(GPIO_TO_IRQ(ux500_console_uart_gpio_pin), + IRQ_TYPE_EDGE_BOTH); +} + +void ux500_suspend_dbg_remove_wake_on_uart(void) +{ + irq_set_irq_wake(GPIO_TO_IRQ(ux500_console_uart_gpio_pin), 0); +} +#endif + +bool ux500_suspend_enabled(void) +{ + return suspend_enabled != 0; +} + +bool ux500_suspend_sleep_enabled(void) +{ + return sleep_enabled != 0; +} + +bool ux500_suspend_deepsleep_enabled(void) +{ + return deepsleep_enabled != 0; +} + +void ux500_suspend_dbg_sleep_status(bool is_deepsleep) +{ + enum prcmu_power_status prcmu_status; + + prcmu_status = prcmu_get_power_state_result(); + + if (is_deepsleep) { + pr_info("Returning from ApDeepSleep. PRCMU ret: 0x%x - %s\n", + prcmu_status, + prcmu_status == PRCMU_DEEP_SLEEP_OK ? + "Success" : "Fail!"); + if (prcmu_status == PRCMU_DEEP_SLEEP_OK) + deepsleeps_done++; + else + deepsleeps_failed++; + } else { + pr_info("Returning from ApSleep. PRCMU ret: 0x%x - %s\n", + prcmu_status, + prcmu_status == PRCMU_SLEEP_OK ? "Success" : "Fail!"); + if (prcmu_status == PRCMU_SLEEP_OK) + sleeps_done++; + else + sleeps_failed++; + } +} + +int ux500_suspend_dbg_begin(suspend_state_t state) +{ + suspend_count++; + return 0; +} + +void ux500_suspend_dbg_init(void) +{ + struct dentry *suspend_dir; + struct dentry *file; + + suspend_dir = debugfs_create_dir("suspend", NULL); + if (IS_ERR_OR_NULL(suspend_dir)) + return; + + file = debugfs_create_bool("sleep", S_IWUGO | S_IRUGO, + suspend_dir, + &sleep_enabled); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_bool("deepsleep", S_IWUGO | S_IRUGO, + suspend_dir, + &deepsleep_enabled); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_bool("enable", S_IWUGO | S_IRUGO, + suspend_dir, + &suspend_enabled); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_u32("count", S_IRUGO, + suspend_dir, + &suspend_count); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_u32("sleep_count", S_IRUGO, + suspend_dir, + &sleeps_done); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_u32("deepsleep_count", S_IRUGO, + suspend_dir, + &deepsleeps_done); + if (IS_ERR_OR_NULL(file)) + goto error; + + + file = debugfs_create_u32("sleep_failed", S_IRUGO, + suspend_dir, + &sleeps_failed); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_u32("deepsleep_failed", S_IRUGO, + suspend_dir, + &deepsleeps_failed); + if (IS_ERR_OR_NULL(file)) + goto error; + + return; +error: + if (!IS_ERR_OR_NULL(suspend_dir)) + debugfs_remove_recursive(suspend_dir); +} diff --git a/arch/arm/mach-ux500/pm/suspend_dbg.h b/arch/arm/mach-ux500/pm/suspend_dbg.h new file mode 100644 index 00000000000..29bfec7e269 --- /dev/null +++ b/arch/arm/mach-ux500/pm/suspend_dbg.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + */ + +#ifndef UX500_SUSPEND_DBG_H +#define UX500_SUSPEND_DBG_H + +#include <linux/kernel.h> +#include <linux/suspend.h> + +#ifdef CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART +void ux500_suspend_dbg_add_wake_on_uart(void); +void ux500_suspend_dbg_remove_wake_on_uart(void); +#else +static inline void ux500_suspend_dbg_add_wake_on_uart(void) { } +static inline void ux500_suspend_dbg_remove_wake_on_uart(void) { } +#endif + +#ifdef CONFIG_UX500_SUSPEND_DBG +bool ux500_suspend_enabled(void); +bool ux500_suspend_sleep_enabled(void); +bool ux500_suspend_deepsleep_enabled(void); +void ux500_suspend_dbg_sleep_status(bool is_deepsleep); +void ux500_suspend_dbg_init(void); +int ux500_suspend_dbg_begin(suspend_state_t state); + +#else +static inline bool ux500_suspend_enabled(void) +{ + return true; +} +static inline bool ux500_suspend_sleep_enabled(void) +{ +#ifdef CONFIG_UX500_SUSPEND_STANDBY + return true; +#else + return false; +#endif +} +static inline bool ux500_suspend_deepsleep_enabled(void) +{ +#ifdef CONFIG_UX500_SUSPEND_MEM + return true; +#else + return false; +#endif +} +static inline void ux500_suspend_dbg_sleep_status(bool is_deepsleep) { } +static inline void ux500_suspend_dbg_init(void) { } + +static inline int ux500_suspend_dbg_begin(suspend_state_t state) +{ + return 0; +} + +#endif + +#endif diff --git a/arch/arm/mach-ux500/pm/timer.c b/arch/arm/mach-ux500/pm/timer.c new file mode 100644 index 00000000000..fc81c2c15ad --- /dev/null +++ b/arch/arm/mach-ux500/pm/timer.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + * License Terms: GNU General Public License v2 + * + * The RTC timer block is a ST Microelectronics variant of ARM PL031. + * Clockwatch part is the same as PL031, while the timer part is only + * present on the ST Microelectronics variant. + * Here only the timer part is used. + * + * The timer part is quite troublesome to program correctly. Lots + * of long delays must be there in order to secure that you actually get what + * you wrote. + * + * In other words, this timer is and should only used from cpuidle during + * special conditions when the surroundings are know in order to be able + * to remove the number of delays. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ktime.h> +#include <linux/delay.h> + +#include <asm/errno.h> + +#include <mach/hardware.h> + +#define RTC_IMSC 0x10 +#define RTC_MIS 0x18 +#define RTC_ICR 0x1C +#define RTC_TDR 0x20 +#define RTC_TLR1 0x24 +#define RTC_TCR 0x28 + +#define RTC_TLR2 0x2C +#define RTC_TPR1 0x3C + +#define RTC_TCR_RTTOS (1 << 0) +#define RTC_TCR_RTTEN (1 << 1) +#define RTC_TCR_RTTSS (1 << 2) + +#define RTC_IMSC_TIMSC (1 << 1) +#define RTC_ICR_TIC (1 << 1) +#define RTC_MIS_RTCTMIS (1 << 1) + +#define RTC_TCR_RTTPS_2 (1 << 4) +#define RTC_TCR_RTTPS_3 (2 << 4) +#define RTC_TCR_RTTPS_4 (3 << 4) +#define RTC_TCR_RTTPS_5 (4 << 4) +#define RTC_TCR_RTTPS_6 (5 << 4) +#define RTC_TCR_RTTPS_7 (6 << 4) +#define RTC_TCR_RTTPS_8 (7 << 4) + +#define WRITE_DELAY 130 /* 4 cycles plus margin */ + +/* + * Count down measure point. It just have to be high to differ + * from scheduled values. + */ +#define MEASURE_VAL 0xffffffff + +/* Just a value bigger than any reason able scheduled timeout. */ +#define MEASURE_VAL_LIMIT 0xf0000000 + +#define TICKS_TO_NS(x) ((s64)x * 30518) +#define US_TO_TICKS(x) ((u32)((1000 * x) / 30518)) + +static void __iomem *rtc_base; +static bool measure_latency; + +#ifdef CONFIG_UX500_CPUIDLE_DEBUG + +/* + * The plan here is to be able to measure the ApSleep/ApDeepSleep exit latency + * by having a know timer pattern. + * The first entry in the pattern, LR1, is the value that the scheduler + * wants us to sleep. The second pattern in a high value, too large to be + * scheduled, so we can differ between a running scheduled value and a + * time measure value. + * When a RTT interrupt has occured, the block will automatically start + * to execute the measure value in LR2 and when the ARM is awake, it reads + * how far the RTT has decreased the value loaded from LR2 and from that + * calculate how long time it took to wake up. + */ +ktime_t u8500_rtc_exit_latency_get(void) +{ + u32 ticks; + + if (measure_latency) { + ticks = MEASURE_VAL - readl(rtc_base + RTC_TDR); + + /* + * Check if we are actually counting on a LR2 value. + * If not we have woken on another interrupt. + */ + if (ticks < MEASURE_VAL_LIMIT) { + /* convert 32 kHz ticks to ns */ + return ktime_set(0, TICKS_TO_NS(ticks)); + } + } + return ktime_set(0, 0); +} + +static void measure_latency_start(void) +{ + udelay(WRITE_DELAY); + /* + * Disable RTT and clean self-start due to we want to restart, + * not continue from current pattern. (See below) + */ + writel(0, rtc_base + RTC_TCR); + udelay(WRITE_DELAY); + + /* + * Program LR2 (load register two) to maximum value to ease + * identification of timer interrupt vs other. + */ + writel(MEASURE_VAL, rtc_base + RTC_TLR2); + /* + * Set Load Register execution pattern, bit clear + * means pick LR1, bit set means LR2 + * 0xfe, binary 11111110 means first do LR1 then do + * LR2 seven times + */ + writel(0xfe, rtc_base + RTC_TPR1); + + udelay(WRITE_DELAY); + + /* + * Enable self-start, plus a pattern of eight. + */ + writel(RTC_TCR_RTTSS | RTC_TCR_RTTPS_8, + rtc_base + RTC_TCR); + udelay(WRITE_DELAY); +} + +void ux500_rtcrtt_measure_latency(bool enable) +{ + if (enable) { + measure_latency_start(); + } else { + writel(RTC_TCR_RTTSS | RTC_TCR_RTTOS, rtc_base + RTC_TCR); + writel(RTC_ICR_TIC, rtc_base + RTC_ICR); + writel(RTC_IMSC_TIMSC, rtc_base + RTC_IMSC); + } + measure_latency = enable; +} +#else +static inline void measure_latency_start(void) { } +static inline void ux500_rtcrtt_measure_latency(bool enable) +{ + writel(RTC_TCR_RTTSS | RTC_TCR_RTTOS, rtc_base + RTC_TCR); + writel(RTC_ICR_TIC, rtc_base + RTC_ICR); + writel(RTC_IMSC_TIMSC, rtc_base + RTC_IMSC); +} +#endif + +void ux500_rtcrtt_off(void) +{ + if (measure_latency) { + measure_latency_start(); + } else { + /* Disable, self start and oneshot mode */ + writel(RTC_TCR_RTTSS | RTC_TCR_RTTOS, rtc_base + RTC_TCR); + + /* Clear eventual interrupts */ + if (readl(rtc_base + RTC_MIS) & RTC_MIS_RTCTMIS) + writel(RTC_ICR_TIC, rtc_base + RTC_ICR); + } +} + +void ux500_rtcrtt_next(u32 time_us) +{ + writel(US_TO_TICKS(time_us), rtc_base + RTC_TLR1); +} + +static int __init ux500_rtcrtt_init(void) +{ + if (cpu_is_u8500() || cpu_is_u9540()) { + rtc_base = __io_address(U8500_RTC_BASE); + } else if (cpu_is_u5500()) { + rtc_base = __io_address(U5500_RTC_BASE); + } else { + pr_err("timer-rtt: Unknown DB Asic!\n"); + return -EINVAL; + } + ux500_rtcrtt_measure_latency(false); + return 0; +} +subsys_initcall(ux500_rtcrtt_init); diff --git a/arch/arm/mach-ux500/pm/usecase_gov.c b/arch/arm/mach-ux500/pm/usecase_gov.c new file mode 100644 index 00000000000..1fd4fbb9830 --- /dev/null +++ b/arch/arm/mach-ux500/pm/usecase_gov.c @@ -0,0 +1,962 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Alexandre Torgue <alexandre.torgue@stericsson.com> for ST-Ericsson + * Author: Vincent Guittot <vincent.guittot@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/io.h> +#include <linux/earlysuspend.h> +#include <linux/cpu.h> +#include <linux/sched.h> +#include <linux/tick.h> +#include <linux/workqueue.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/kernel_stat.h> +#include <linux/ktime.h> +#include <linux/cpufreq.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/cpufreq-dbx500.h> + +#include "../../../../drivers/cpuidle/cpuidle-dbx500.h" + + +#define CPULOAD_MEAS_DELAY 3000 /* 3 secondes of delta */ + +/* debug */ +static unsigned long debug; + +#define hp_printk \ + if (debug) \ + printk \ + +enum ux500_uc { + UX500_UC_NORMAL = 0, + UX500_UC_AUTO, /* Add use case below this. */ + UX500_UC_VC, + UX500_UC_LPA, + UX500_UC_USER, /* Add use case above this. */ + UX500_UC_MAX, +}; + +/* cpu load monitor struct */ +#define LOAD_MONITOR 4 +struct hotplug_cpu_info { + cputime64_t prev_cpu_wall; + cputime64_t prev_cpu_idle; + cputime64_t prev_cpu_io; + unsigned int load[LOAD_MONITOR]; + unsigned int idx; +}; + +static DEFINE_PER_CPU(struct hotplug_cpu_info, hotplug_info); + +/* Auto trigger criteria */ +/* loadavg threshold */ +static unsigned long lower_threshold = 175; +static unsigned long upper_threshold = 450; +/* load balancing */ +static unsigned long max_unbalance = 210; +/* trend load */ +static unsigned long trend_unbalance = 40; +static unsigned long min_trend = 5; +/* instant load */ +static unsigned long max_instant = 85; + +/* Number of interrupts per second before exiting auto mode */ +static u32 exit_irq_per_s = 1000; +static u64 old_num_irqs; + +static DEFINE_MUTEX(usecase_mutex); +static DEFINE_MUTEX(state_mutex); +static bool user_config_updated; +static enum ux500_uc current_uc = UX500_UC_MAX; +static bool is_work_scheduled; +static bool is_early_suspend; +static bool uc_master_enable = true; + +static unsigned int cpuidle_deepest_state; + +struct usecase_config { + char *name; + /* Minimum required ARM OPP. if no requirement set 25 */ + unsigned int min_arm_opp; + unsigned int max_arm_opp; + unsigned long cpuidle_multiplier; + bool second_cpu_online; + bool l2_prefetch_en; + bool enable; + unsigned int forced_state; /* Forced cpu idle state. */ + bool vc_override; /* QOS override for voice-call. */ +}; + +static struct usecase_config usecase_conf[UX500_UC_MAX] = { + [UX500_UC_NORMAL] = { + .name = "normal", + .min_arm_opp = 25, + .cpuidle_multiplier = 1024, + .second_cpu_online = true, + .l2_prefetch_en = true, + .enable = true, + .forced_state = 0, + .vc_override = false, + }, + [UX500_UC_AUTO] = { + .name = "auto", + .min_arm_opp = 25, + .cpuidle_multiplier = 0, + .second_cpu_online = false, + .l2_prefetch_en = true, + .enable = false, + .forced_state = 0, + .vc_override = false, + }, + [UX500_UC_VC] = { + .name = "voice-call", + .min_arm_opp = 50, + .cpuidle_multiplier = 0, + .second_cpu_online = true, + .l2_prefetch_en = false, + .enable = false, + .forced_state = 0, + .vc_override = true, + }, + [UX500_UC_LPA] = { + .name = "low-power-audio", + .min_arm_opp = 50, + .cpuidle_multiplier = 0, + .second_cpu_online = false, + .l2_prefetch_en = false, + .enable = false, + .forced_state = 0, /* Updated dynamically */ + .vc_override = false, + }, +}; + +/* daemon */ +static struct delayed_work work_usecase; +static struct early_suspend usecase_early_suspend; + +/* calculate loadavg */ +#define LOAD_INT(x) ((x) >> FSHIFT) +#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100) + +extern int cpufreq_update_freq(int cpu, unsigned int min, unsigned int max); +extern int cpuidle_set_multiplier(unsigned int value); +extern int cpuidle_force_state(unsigned int state); + +static unsigned long determine_loadavg(void) +{ + unsigned long avg = 0; + unsigned long avnrun[3]; + + get_avenrun(avnrun, FIXED_1 / 200, 0); + avg += (LOAD_INT(avnrun[0]) * 100) + (LOAD_FRAC(avnrun[0]) % 100); + + return avg; +} + +static unsigned long determine_cpu_load(void) +{ + int i; + unsigned long total_load = 0; + + /* get cpu load of each cpu */ + for_each_online_cpu(i) { + unsigned int load; + unsigned int idle_time, wall_time; + cputime64_t cur_wall_time, cur_idle_time; + struct hotplug_cpu_info *info; + + info = &per_cpu(hotplug_info, i); + + /* update both cur_idle_time and cur_wall_time */ + cur_idle_time = get_cpu_idle_time_us(i, &cur_wall_time); + + /* how much wall time has passed since last iteration? */ + wall_time = (unsigned int) cputime64_sub(cur_wall_time, + info->prev_cpu_wall); + info->prev_cpu_wall = cur_wall_time; + + /* how much idle time has passed since last iteration? */ + idle_time = (unsigned int) cputime64_sub(cur_idle_time, + info->prev_cpu_idle); + info->prev_cpu_idle = cur_idle_time; + + if (unlikely(!wall_time || wall_time < idle_time)) + continue; + + /* load is the percentage of time not spent in idle */ + load = 100 * (wall_time - idle_time) / wall_time; + info->load[info->idx++] = load; + if (info->idx >= LOAD_MONITOR) + info->idx = 0; + + hp_printk("cpu %d load %u ", i, load); + + total_load += load; + } + + return total_load; +} + +static unsigned long determine_cpu_load_trend(void) +{ + int i, k; + unsigned long total_load = 0; + + /* Get cpu load of each cpu */ + for_each_online_cpu(i) { + unsigned int load = 0; + struct hotplug_cpu_info *info; + + info = &per_cpu(hotplug_info, i); + + for (k = 0; k < LOAD_MONITOR; k++) + load += info->load[k]; + + load /= LOAD_MONITOR; + + hp_printk("cpu %d load trend %u\n", i, load); + + total_load += load; + } + + return total_load; +} + +static unsigned long determine_cpu_balance_trend(void) +{ + int i, k; + unsigned long total_load = 0; + unsigned long min_load = (unsigned long) (-1); + + /* Get cpu load of each cpu */ + for_each_online_cpu(i) { + unsigned int load = 0; + struct hotplug_cpu_info *info; + info = &per_cpu(hotplug_info, i); + + for (k = 0; k < LOAD_MONITOR; k++) + load += info->load[k]; + + load /= LOAD_MONITOR; + + if (min_load > load) + min_load = load; + total_load += load; + } + + if (min_load > min_trend) + total_load = (100 * total_load) / min_load; + else + total_load = 50 << num_online_cpus(); + + return total_load; +} + +static void init_cpu_load_trend(void) +{ + int i; + + for_each_possible_cpu(i) { + struct hotplug_cpu_info *info; + int j; + + info = &per_cpu(hotplug_info, i); + + info->prev_cpu_idle = get_cpu_idle_time_us(i, + &(info->prev_cpu_wall)); + info->prev_cpu_io = get_cpu_iowait_time_us(i, + &(info->prev_cpu_wall)); + + for (j = 0; j < LOAD_MONITOR; j++) { + info->load[j] = 100; + } + info->idx = 0; + } +} + +static u32 get_num_interrupts_per_s(void) +{ + int cpu; + int i; + u64 num_irqs = 0; + ktime_t now; + static ktime_t last; + unsigned int delta; + u32 irqs = 0; + + now = ktime_get(); + + for_each_possible_cpu(cpu) { + for (i = 0; i < NR_IRQS; i++) + num_irqs += kstat_irqs_cpu(i, cpu); + } + pr_debug("%s: total num irqs: %lld, previous %lld\n", + __func__, num_irqs, old_num_irqs); + + if (old_num_irqs > 0) { + delta = (u32)ktime_to_ms(ktime_sub(now, last)) / 1000; + irqs = ((u32)(num_irqs - old_num_irqs)) / delta; + } + + old_num_irqs = num_irqs; + last = now; + + pr_debug("delta irqs per sec:%d\n", irqs); + + return irqs; +} + +static int set_cpufreq(int cpu, int min_freq, int max_freq) +{ + int ret; + struct cpufreq_policy policy; + + pr_debug("set cpu freq: min %d max: %d\n", min_freq, max_freq); + + ret = cpufreq_get_policy(&policy, cpu); + if (ret < 0) { + pr_err("usecase-gov: failed to read policy\n"); + return ret; + } + + if (policy.min > max_freq) { + ret = cpufreq_update_freq(cpu, min_freq, policy.max); + if (ret) + pr_err("usecase-gov: update min cpufreq failed (1)\n"); + } + if (policy.max < min_freq) { + ret = cpufreq_update_freq(cpu, policy.min, max_freq); + if (ret) + pr_err("usecase-gov: update max cpufreq failed (2)\n"); + } + + ret = cpufreq_update_freq(cpu, min_freq, max_freq); + if (ret) + pr_err("usecase-gov: update min-max cpufreq failed\n"); + + return ret; +} + +static void set_cpu_config(enum ux500_uc new_uc) +{ + bool update = false; + int cpu; + int max_freq, min_freq; + + if (new_uc != current_uc) + update = true; + else if ((user_config_updated) && (new_uc == UX500_UC_USER)) + update = true; + + pr_debug("%s: new_usecase=%d, current_usecase=%d, update=%d\n", + __func__, new_uc, current_uc, update); + + if (!update) + goto exit; + + /* Cpu hotplug */ + if (!(usecase_conf[new_uc].second_cpu_online) && + (num_online_cpus() > 1)) + cpu_down(1); + else if ((usecase_conf[new_uc].second_cpu_online) && + (num_online_cpus() < 2)) + cpu_up(1); + + if(usecase_conf[new_uc].max_arm_opp) + max_freq = dbx500_cpufreq_percent2freq(usecase_conf[new_uc].max_arm_opp); + else + /* Maximum OPP is 125% */ + max_freq = dbx500_cpufreq_percent2freq(125); + + min_freq = dbx500_cpufreq_percent2freq(usecase_conf[new_uc].min_arm_opp); + + for_each_online_cpu(cpu) { + set_cpufreq(cpu, + min_freq, + max_freq); + } + + /* Kinda doing the job twice, but this is needed for reference keeping */ + prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, + "usecase", usecase_conf[new_uc].min_arm_opp); + + /* Cpu idle */ + cpuidle_set_multiplier(usecase_conf[new_uc].cpuidle_multiplier); + + /* L2 prefetch */ + if (usecase_conf[new_uc].l2_prefetch_en) + outer_prefetch_enable(); + else + outer_prefetch_disable(); + + /* Force cpuidle state */ + cpuidle_force_state(usecase_conf[new_uc].forced_state); + + /* QOS override */ + prcmu_qos_voice_call_override(usecase_conf[new_uc].vc_override); + + current_uc = new_uc; + +exit: + /* Its ok to clear even if new_uc != UX500_UC_USER */ + user_config_updated = false; +} + +void usecase_update_governor_state(void) +{ + bool cancel_work = false; + + /* + * usecase_mutex will have to be unlocked to ensure safe exit of + * delayed_usecase_work(). Protect this function with its own mutex + * from being executed by multiple threads at that point. + */ + mutex_lock(&state_mutex); + mutex_lock(&usecase_mutex); + + if (uc_master_enable && (usecase_conf[UX500_UC_AUTO].enable || + usecase_conf[UX500_UC_USER].enable)) { + /* + * Usecases are enabled. If we are in early suspend put + * governor to work. + */ + if (is_early_suspend && !is_work_scheduled) { + schedule_delayed_work_on(0, &work_usecase, + msecs_to_jiffies(CPULOAD_MEAS_DELAY)); + is_work_scheduled = true; + } else if (!is_early_suspend && is_work_scheduled) { + /* Exiting from early suspend. */ + cancel_work = true; + } + + } else if (is_work_scheduled) { + /* No usecase enabled or governor is not enabled. */ + cancel_work = true; + } + + if (cancel_work) { + /* + * usecase_mutex is used by delayed_usecase_work() so it must + * be unlocked before we call to cacnel the work. + */ + mutex_unlock(&usecase_mutex); + cancel_delayed_work_sync(&work_usecase); + mutex_lock(&usecase_mutex); + + is_work_scheduled = false; + + /* Set the default settings before exiting. */ + set_cpu_config(UX500_UC_NORMAL); + } + + mutex_unlock(&usecase_mutex); + mutex_unlock(&state_mutex); +} + +/* + * Start load measurment every 6 s in order detrmine if can unplug one CPU. + * In order to not corrupt measurment, the first load average is not done + * here call in early suspend. + */ +static void usecase_earlysuspend_callback(struct early_suspend *h) +{ + init_cpu_load_trend(); + + is_early_suspend = true; + + usecase_update_governor_state(); +} + +/* Stop measurement, call LCD early resume */ +static void usecase_lateresume_callback(struct early_suspend *h) +{ + is_early_suspend = false; + + usecase_update_governor_state(); +} + +static void delayed_usecase_work(struct work_struct *work) +{ + unsigned long avg, load, trend, balance; + bool inc_perf = false; + bool dec_perf = false; + u32 irqs_per_s; + + /* determine loadavg */ + avg = determine_loadavg(); + hp_printk("loadavg = %lu lower th %lu upper th %lu\n", + avg, lower_threshold, upper_threshold); + + /* determine instant load */ + load = determine_cpu_load(); + hp_printk("cpu instant load = %lu max %lu\n", load, max_instant); + + /* determine load trend */ + trend = determine_cpu_load_trend(); + hp_printk("cpu load trend = %lu min %lu unbal %lu\n", + trend, min_trend, trend_unbalance); + + /* determine load balancing */ + balance = determine_cpu_balance_trend(); + hp_printk("load balancing trend = %lu min %lu\n", + balance, max_unbalance); + + irqs_per_s = get_num_interrupts_per_s(); + + /* Dont let configuration change in the middle of our calculations. */ + mutex_lock(&usecase_mutex); + + /* detect "instant" load increase */ + if (load > max_instant || irqs_per_s > exit_irq_per_s) { + inc_perf = true; + } else if (!usecase_conf[UX500_UC_USER].enable && + usecase_conf[UX500_UC_AUTO].enable) { + /* detect high loadavg use case */ + if (avg > upper_threshold) + inc_perf = true; + /* detect idle use case */ + else if (trend < min_trend) + dec_perf = true; + /* detect unbalanced low cpu load use case */ + else if ((balance > max_unbalance) && (trend < trend_unbalance)) + dec_perf = true; + /* detect low loadavg use case */ + else if (avg < lower_threshold) + dec_perf = true; + /* All user use cases disabled, current load not triggering + * any change. + */ + else if (user_config_updated) + dec_perf = true; + } else { + dec_perf = true; + } + + /* + * set_cpu_config() will not update the config unless it has been + * changed. + */ + if (dec_perf) { + if (usecase_conf[UX500_UC_USER].enable) + set_cpu_config(UX500_UC_USER); + else if (usecase_conf[UX500_UC_AUTO].enable) + set_cpu_config(UX500_UC_AUTO); + } else if (inc_perf) { + set_cpu_config(UX500_UC_NORMAL); + } + + mutex_unlock(&usecase_mutex); + + /* reprogramm scheduled work */ + schedule_delayed_work_on(0, &work_usecase, + msecs_to_jiffies(CPULOAD_MEAS_DELAY)); + +} + +static struct dentry *usecase_dir; + +#ifdef CONFIG_DEBUG_FS +#define define_set(_name) \ +static ssize_t set_##_name(struct file *file, \ + const char __user *user_buf, \ + size_t count, loff_t *ppos) \ +{ \ + int err; \ + long unsigned i; \ + \ + err = kstrtoul_from_user(user_buf, count, 0, &i); \ + \ + if (err) \ + return err; \ + \ + _name = i; \ + hp_printk("New value : %lu\n", _name); \ + \ + return count; \ +} + +define_set(upper_threshold); +define_set(lower_threshold); +define_set(max_unbalance); +define_set(trend_unbalance); +define_set(min_trend); +define_set(max_instant); +define_set(debug); + +#define define_print(_name) \ +static ssize_t print_##_name(struct seq_file *s, void *p) \ +{ \ + return seq_printf(s, "%lu\n", _name); \ +} + +define_print(upper_threshold); +define_print(lower_threshold); +define_print(max_unbalance); +define_print(trend_unbalance); +define_print(min_trend); +define_print(max_instant); +define_print(debug); + +#define define_open(_name) \ +static ssize_t open_##_name(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, print_##_name, inode->i_private); \ +} + +define_open(upper_threshold); +define_open(lower_threshold); +define_open(max_unbalance); +define_open(trend_unbalance); +define_open(min_trend); +define_open(max_instant); +define_open(debug); + +#define define_dbg_file(_name) \ +static const struct file_operations fops_##_name = { \ + .open = open_##_name, \ + .write = set_##_name, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ + .owner = THIS_MODULE, \ +}; \ +static struct dentry *file_##_name; + +define_dbg_file(upper_threshold); +define_dbg_file(lower_threshold); +define_dbg_file(max_unbalance); +define_dbg_file(trend_unbalance); +define_dbg_file(min_trend); +define_dbg_file(max_instant); +define_dbg_file(debug); + +struct dbg_file { + struct dentry **file; + const struct file_operations *fops; + const char *name; +}; + +#define define_dbg_entry(_name) \ +{ \ + .file = &file_##_name, \ + .fops = &fops_##_name, \ + .name = #_name \ +} + +static struct dbg_file debug_entry[] = { + define_dbg_entry(upper_threshold), + define_dbg_entry(lower_threshold), + define_dbg_entry(max_unbalance), + define_dbg_entry(trend_unbalance), + define_dbg_entry(min_trend), + define_dbg_entry(max_instant), + define_dbg_entry(debug), +}; + +static int setup_debugfs(void) +{ + int i; + usecase_dir = debugfs_create_dir("usecase", NULL); + + if (IS_ERR_OR_NULL(usecase_dir)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(debug_entry); i++) { + if (IS_ERR_OR_NULL(debugfs_create_file(debug_entry[i].name, + S_IWUGO | S_IRUGO, + usecase_dir, + NULL, + debug_entry[i].fops))) + goto fail; + } + + if (IS_ERR_OR_NULL(debugfs_create_u32("exit_irq_per_s", + S_IWUGO | S_IRUGO, usecase_dir, + &exit_irq_per_s))) + goto fail; + return 0; +fail: + debugfs_remove_recursive(usecase_dir); + return -EINVAL; +} +#else +static int setup_debugfs(void) +{ + return 0; +} +#endif + +static void usecase_update_user_config(void) +{ + int i; + bool config_enable = false; + struct usecase_config *user_conf = &usecase_conf[UX500_UC_USER]; + + mutex_lock(&usecase_mutex); + + user_conf->min_arm_opp = 25; + user_conf->max_arm_opp = 0; + user_conf->cpuidle_multiplier = 0; + user_conf->second_cpu_online = false; + user_conf->l2_prefetch_en = false; + user_conf->forced_state = cpuidle_deepest_state; + user_conf->vc_override = true; /* A single false will clear it. */ + + /* Dont include Auto and Normal modes in this */ + for (i = (UX500_UC_AUTO + 1); i < UX500_UC_USER; i++) { + if (!usecase_conf[i].enable) + continue; + + config_enable = true; + + /* It's the highest arm opp requirement that should be used */ + if (usecase_conf[i].min_arm_opp > user_conf->min_arm_opp) + user_conf->min_arm_opp = usecase_conf[i].min_arm_opp; + + if (usecase_conf[i].max_arm_opp > user_conf->max_arm_opp) + user_conf->max_arm_opp = usecase_conf[i].max_arm_opp; + + if (usecase_conf[i].cpuidle_multiplier > + user_conf->cpuidle_multiplier) + user_conf->cpuidle_multiplier = + usecase_conf[i].cpuidle_multiplier; + + user_conf->second_cpu_online |= + usecase_conf[i].second_cpu_online; + + user_conf->l2_prefetch_en |= + usecase_conf[i].l2_prefetch_en; + + /* Take the shallowest state. */ + if (usecase_conf[i].forced_state < user_conf->forced_state) + user_conf->forced_state = usecase_conf[i].forced_state; + + /* Only override QOS if all enabled configurations are + * requesting it. + */ + if (!usecase_conf[i].vc_override) + user_conf->vc_override = false; + } + + user_conf->enable = config_enable; + user_config_updated = true; + + mutex_unlock(&usecase_mutex); +} + +struct usecase_devclass_attr { + struct sysdev_class_attribute class_attr; + u32 index; +}; + +/* One for each usecase except "user" + current + enable */ +#define UX500_NUM_SYSFS_NODES (UX500_UC_USER + 2) +#define UX500_CURRENT_NODE_INDEX (UX500_NUM_SYSFS_NODES - 1) +#define UX500_ENABLE_NODE_INDEX (UX500_NUM_SYSFS_NODES - 2) + +static struct usecase_devclass_attr usecase_dc_attr[UX500_NUM_SYSFS_NODES]; + +static struct attribute *dbs_attributes[UX500_NUM_SYSFS_NODES + 1] = {NULL}; + +static struct attribute_group dbs_attr_group = { + .attrs = dbs_attributes, + .name = "usecase", +}; + +static ssize_t show_current(struct sysdev_class *class, + struct sysdev_class_attribute *attr, char *buf) +{ + enum ux500_uc display_uc = (current_uc == UX500_UC_MAX) ? + UX500_UC_NORMAL : current_uc; + + return sprintf(buf, "min_arm_opp: %d\n" + "max_arm_opp: %d\n" + "cpuidle_multiplier: %ld\n" + "second_cpu_online: %s\n" + "l2_prefetch_en: %s\n" + "forced_state: %d\n" + "vc_override: %s\n", + usecase_conf[display_uc].min_arm_opp, + usecase_conf[display_uc].max_arm_opp, + usecase_conf[display_uc].cpuidle_multiplier, + usecase_conf[display_uc].second_cpu_online ? "true" : "false", + usecase_conf[display_uc].l2_prefetch_en ? "true" : "false", + usecase_conf[display_uc].forced_state, + usecase_conf[display_uc].vc_override ? "true" : "false"); +} + +static ssize_t show_enable(struct sysdev_class *class, + struct sysdev_class_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", uc_master_enable); +} + +static ssize_t store_enable(struct sysdev_class *class, + struct sysdev_class_attribute *attr, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; + + uc_master_enable = (bool) input; + + usecase_update_governor_state(); + + return count; +} + +static ssize_t show_dc_attr(struct sysdev_class *class, + struct sysdev_class_attribute *attr, char *buf) +{ + struct usecase_devclass_attr *uattr = + container_of(attr, struct usecase_devclass_attr, class_attr); + + return sprintf(buf, "%u\n", + usecase_conf[uattr->index].enable); +} + +static ssize_t store_dc_attr(struct sysdev_class *class, + struct sysdev_class_attribute *attr, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + + struct usecase_devclass_attr *uattr = + container_of(attr, struct usecase_devclass_attr, class_attr); + + ret = sscanf(buf, "%u", &input); + + /* Normal mode cant be changed. */ + if ((ret != 1) || (uattr->index == 0)) + return -EINVAL; + + usecase_conf[uattr->index].enable = (bool)input; + + if (uattr->index == UX500_UC_VC) + prcmu_vc(usecase_conf[UX500_UC_VC].enable); + + usecase_update_user_config(); + + usecase_update_governor_state(); + + return count; +} + +static int usecase_sysfs_init(void) +{ + int err; + int i; + + /* Last two nodes are not based on usecase configurations */ + for (i = 0; i < (UX500_NUM_SYSFS_NODES - 2); i++) { + usecase_dc_attr[i].class_attr.attr.name = usecase_conf[i].name; + usecase_dc_attr[i].class_attr.attr.mode = 0644; + usecase_dc_attr[i].class_attr.show = show_dc_attr; + usecase_dc_attr[i].class_attr.store = store_dc_attr; + usecase_dc_attr[i].index = i; + + dbs_attributes[i] = &(usecase_dc_attr[i].class_attr.attr); + } + + /* sysfs current */ + usecase_dc_attr[UX500_CURRENT_NODE_INDEX].class_attr.attr.name = + "current"; + usecase_dc_attr[UX500_CURRENT_NODE_INDEX].class_attr.attr.mode = + 0644; + usecase_dc_attr[UX500_CURRENT_NODE_INDEX].class_attr.show = + show_current; + usecase_dc_attr[UX500_CURRENT_NODE_INDEX].class_attr.store = + NULL; + usecase_dc_attr[UX500_CURRENT_NODE_INDEX].index = + 0; + dbs_attributes[UX500_CURRENT_NODE_INDEX] = + &(usecase_dc_attr[UX500_CURRENT_NODE_INDEX].class_attr.attr); + + /* sysfs enable */ + usecase_dc_attr[UX500_ENABLE_NODE_INDEX].class_attr.attr.name = + "enable"; + usecase_dc_attr[UX500_ENABLE_NODE_INDEX].class_attr.attr.mode = + 0644; + usecase_dc_attr[UX500_ENABLE_NODE_INDEX].class_attr.show = + show_enable; + usecase_dc_attr[UX500_ENABLE_NODE_INDEX].class_attr.store = + store_enable; + usecase_dc_attr[UX500_ENABLE_NODE_INDEX].index = + 0; + dbs_attributes[UX500_ENABLE_NODE_INDEX] = + &(usecase_dc_attr[UX500_ENABLE_NODE_INDEX].class_attr.attr); + + err = sysfs_create_group(&(cpu_sysdev_class.kset.kobj), + &dbs_attr_group); + if (err) + pr_err("usecase-gov: sysfs_create_group" + " failed with error = %d\n", err); + + return err; +} + +static void usecase_cpuidle_init(void) +{ + int max_states; + int i; + struct cstate *state = ux500_ci_get_cstates(&max_states); + + for (i = 0; i < max_states; i++) + if ((state[i].APE == APE_OFF) && (state[i].ARM == ARM_RET)) + break; + + usecase_conf[UX500_UC_LPA].forced_state = i; + + cpuidle_deepest_state = max_states - 1; +} + +/* initialize devices */ +static int __init init_usecase_devices(void) +{ + int err; + + pr_info("Use-case governor initialized\n"); + + /* add early_suspend callback */ + usecase_early_suspend.level = 200; + usecase_early_suspend.suspend = usecase_earlysuspend_callback; + usecase_early_suspend.resume = usecase_lateresume_callback; + register_early_suspend(&usecase_early_suspend); + + /* register delayed queuework */ + INIT_DELAYED_WORK_DEFERRABLE(&work_usecase, + delayed_usecase_work); + + init_cpu_load_trend(); + + err = setup_debugfs(); + if (err) + goto error; + err = usecase_sysfs_init(); + if (err) + goto error2; + + usecase_cpuidle_init(); + + prcmu_qos_add_requirement(PRCMU_QOS_ARM_OPP, "usecase", 25); + + return 0; +error2: + debugfs_remove_recursive(usecase_dir); +error: + unregister_early_suspend(&usecase_early_suspend); + return err; +} + +device_initcall(init_usecase_devices); diff --git a/arch/arm/mach-ux500/prcc.h b/arch/arm/mach-ux500/prcc.h new file mode 100644 index 00000000000..4224e478348 --- /dev/null +++ b/arch/arm/mach-ux500/prcc.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009-2011 ST-Ericsson SA + * + * 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. + */ +#ifndef __MACH_UX500_PRCC_H__ + +#define PRCC_PCKEN 0x000 +#define PRCC_PCKDIS 0x004 +#define PRCC_KCKEN 0x008 +#define PRCC_KCKDIS 0x00C +#define PRCC_PCKSR 0x010 +#define PRCC_KCKSR 0x014 +#define PRCC_K_SOFTRST_SET 0x018 +#define PRCC_K_SOFTRST_CLR 0x01C +#define PRCC_K_RST_STATUS 0x020 + +#endif diff --git a/arch/arm/mach-ux500/prcmu-debug.c b/arch/arm/mach-ux500/prcmu-debug.c new file mode 100644 index 00000000000..6842e4b68fe --- /dev/null +++ b/arch/arm/mach-ux500/prcmu-debug.c @@ -0,0 +1,1041 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Martin Persson for ST-Ericsson + * Etienne Carriere <etienne.carriere@stericsson.com> for ST-Ericsson + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <mach/hardware.h> + +#define MAX_STATES 5 +#define MAX_NAMELEN 16 +#define U8500_PRCMU_TCDM_SIZE 4096 + +struct state_history { + ktime_t start; + u32 state; + u32 counter[MAX_STATES]; + u8 opps[MAX_STATES]; + int max_states; + int req; + bool reqs[MAX_STATES]; + ktime_t time[MAX_STATES]; + int state_names[MAX_STATES]; + char prefix[MAX_NAMELEN]; + spinlock_t lock; +}; + +static struct state_history ape_sh = { + .prefix = "APE", + .req = PRCMU_QOS_APE_OPP, + .opps = {APE_50_OPP, APE_100_OPP}, + .state_names = {50, 100}, + .max_states = 2, +}; + +static struct state_history ddr_sh = { + .prefix = "DDR", + .req = PRCMU_QOS_DDR_OPP, + .opps = {DDR_25_OPP, DDR_50_OPP, DDR_100_OPP}, + .state_names = {25, 50, 100}, + .max_states = 3, +}; + +static struct state_history arm_sh = { + .prefix = "ARM", + .req = PRCMU_QOS_ARM_OPP, + .opps = {ARM_EXTCLK, ARM_50_OPP, ARM_100_OPP, ARM_MAX_OPP}, + .state_names = {25, 50, 100, 125}, + .max_states = 4, +}; + +static const u16 u8500_prcmu_dump_regs[] = { + /*ARMCLKFIX_MGT*/ 0x0, /*ACLK_MGT*/ 0x4, + /*SVAMMDSPCLK_MGT*/ 0x8, /*SIAMMDSPCLK_MGT*/ 0xc, + /*SGACLK_MGT*/ 0x14, /*UARTCLK_MGT*/ 0x18, + /*MSP02CLK_MGT*/ 0x1c, /*I2CCLK_MGT*/ 0x20, + /*SDMMCCLK_MGT*/ 0x24, /*SLIMCLK_MGT*/ 0x28, + /*PER1CLK_MGT*/ 0x2c, /*PER2CLK_MGT*/ 0x30, + /*PER3CLK_MGT*/ 0x34, /*PER5CLK_MGT*/ 0x38, + /*PER6CLK_MGT*/ 0x3c, /*PER7CLK_MGT*/ 0x40, + /*LCDCLK_MGT*/ 0x44, /*SPARE1CLK_MGT*/ 0x48, + /*BMLCLK_MGT*/ 0x4c, /*HSITXCLK_MGT*/ 0x50, + /*HSIRXCLK_MGT*/ 0x54, /*HDMICLK_MGT*/ 0x58, + /*APEATCLK_MGT*/ 0x5c, /*APETRACECLK_MGT*/ 0x60, + /*MCDECLK_MGT*/ 0x64, /*IPI2CCLK_MGT*/ 0x68, + /*DSIALTCLK_MGT*/ 0x6c, /*SPARE2CLK_MGT*/ 0x70, + /*DMACLK_MGT*/ 0x74, /*B2R2CLK_MGT*/ 0x78, + /*TVCLK_MGT*/ 0x7c, /*PLLSOC0_FREQ*/ 0x80, + /*PLLSOC1_FREQ*/ 0x84, /*PLLARM_FREQ*/ 0x88, + /*PLLDDR_FREQ*/ 0x8c, /*PLLSOC0_ENABLE*/ 0x90, + /*PLLSOC1_ENABLE*/ 0x94, /*PLLARM_ENABLE*/ 0x98, + /*PLLDDR_ENABLE*/ 0x9c, /*PLLSOC0_LOCKP*/ 0xa0, + /*PLLSOC1_LOCKP*/ 0xa4, /*PLLARM_LOCKP*/ 0xa8, + /*PLLDDR_LOCKP*/ 0xac, /*XP70CLK_MGT*/ 0xb0, + /*TIMER_0_REF*/ 0xb4, /*TIMER_0_DOWNCOUNT*/ 0xb8, + /*TIMER_0_MODE*/ 0xbc, /*TIMER_1_REF*/ 0xc0, + /*TIMER_1_DOWNCOUNT*/ 0xc4, /*TIMER_1_MODE*/ 0xc8, + /*TIMER_2_REF*/ 0xcc, /*TIMER_2_DOWNCOUNT*/ 0xd0, + /*TIMER_2_MODE*/ 0xd4, /*CLK009_MGT*/ 0xe4, + /*MODECLK*/ 0xe8, /*4500_CLK_REQ*/ 0xf8, + /*MBOX_CPU_VAL*/ 0xfc, /*PLL32K_ENABLE*/ 0x10c, + /*PLL32K_LOCKP*/ 0x110, /*ARM_CHGCLKREQ*/ 0x114, + /*ARM_PLLDIVPS*/ 0x118, /*ARMITMSK31TO0*/ 0x11c, + /*ARMITMSK63TO32*/ 0x120, /*ARMITMSK95TO64*/ 0x124, + /*ARMITMSK127TO96*/ 0x128, /*ARMSTANDBY_STATUS*/ 0x130, + /*CGATING_BYPASS*/ 0x134, /*GPIOCR*/ 0x138, + /*LEMI*/ 0x13c, /*COMPCR*/ 0x140, + /*COMPSTA*/ 0x144, /*ITSTATUS0*/ 0x148, + /*ITSTATUS1*/ 0x150, /*ITSTATUS2*/ 0x158, + /*ITSTATUS3*/ 0x160, /*ITSTATUS4*/ 0x168, + /*LINE_VALUE*/ 0x170, /*HOLD_EVT*/ 0x174, + /*EDGE_SENS_L*/ 0x178, /*EDGE_SENS_H*/ 0x17c, + /*DEBUG_CTRL_VAL*/ 0x190, /*DEBUG_NOPWRDOWN_VAL*/ 0x194, + /*DEBUG_CTRL_ACK*/ 0x198, /*A9PL_FORCE_CLKEN*/ 0x19c, + /*TPIU_FLUSHIN_REQ*/ 0x1a0, /*TPIU_FLUSHIN_ACK*/ 0x1a4, + /*STP_FLUSHIN_REQ*/ 0x1a8, /*STP_FLUSHIN_ACK*/ 0x1ac, + /*HWI2C_DIV*/ 0x1b0, /*HWI2C_CMD*/ 0x1b8, + /*HWI2C_DATA123*/ 0x1bc, /*HWI2C_SR*/ 0x1c0, + /*REMAPCR*/ 0x1c4, /*TCR*/ 0x1c8, + /*CLKOCR*/ 0x1cc, /*ITSTATUS_DBG*/ 0x1d0, + /*LINE_VALUE_DBG*/ 0x1d8, /*DBG_HOLD*/ 0x1dc, + /*EDGE_SENS_DBG*/ 0x1e0, /*APE_RESETN_VAL*/ 0x1ec, + /*A9_RESETN_SET*/ 0x1f0, /*A9_RESETN_VAL*/ 0x1f8, + /*MOD_RESETN_VAL*/ 0x204, /*GPIO_RESETN_VAL*/ 0x210, + /*4500_RESETN_VAL*/ 0x21c, /*SWD_RST_TEMPO*/ 0x238, + /*RST_4500_TEMPO*/ 0x23c, /*SVAMMDSP_IT*/ 0x240, + /*SIAMMDSP_IT*/ 0x248, /*POWER_STATE_VAL*/ 0x25c, + /*ARMITVALUE31TO0*/ 0x260, /*ARMITVALUE63TO32*/ 0x264, + /*ARMITVALUE95TO64*/ 0x268, /*ARMITVALUE127TO96*/ 0x26c, + /*REDUN_LOAD*/ 0x270, /*REDUN_STATUS*/ 0x274, + /*UNIPROCLK_MGT*/ 0x278, /*UICCCLK_MGT*/ 0x27c, + /*SSPCLK_MGT*/ 0x280, /*RNGCLK_MGT*/ 0x284, + /*MSP1CLK_MGT*/ 0x288, /*DAP_RESETN_SET*/ 0x2a0, + /*DAP_RESETN_VAL*/ 0x2a8, /*SRAM_DEDCSTOV*/ 0x300, + /*SRAM_LS_SLEEP*/ 0x304, /*SRAM_A9*/ 0x308, + /*ARM_LS_CLAMP*/ 0x30c, /*IOCR*/ 0x310, + /*MODEM_SYSCLKOK*/ 0x314, /*SYSCLKOK_DELAY*/ 0x318, + /*SYSCLKSTATUS*/ 0x31c, /*DSI_SW_RESET*/ 0x324, + /*A9_MASK_REQ*/ 0x328, /*A9_MASK_ACK*/ 0x32c, + /*HOSTACCESS_REQ*/ 0x334, /*TIMER_3_REF*/ 0x338, + /*TIMER_3_DOWNCOUNT*/ 0x33c, /*TIMER_3_MODE*/ 0x340, + /*PMB_SENS_CTRL*/ 0x344, /*PMB_REF_COUNTER*/ 0x348, + /*PMB_SENSOR_STATUS*/ 0x34c, /*APE_EPOD_CFG*/ 0x404, + /*DDR_EPOD_CFG*/ 0x408, /*EPOD_C_VAL*/ 0x418, + /*EPOD_VOK*/ 0x41c, /*MMIP_LS_CLAMP_VAL*/ 0x428, + /*VSAFE_LS_CLAMP_VAL*/ 0x434, /*DDRSUBSYS_APE_MINBW*/ 0x438, + /*DDRSUBSYS_STATUS*/ 0x43c, /*DDRSUBSYS_CONTROL*/ 0x440, + /*DDRSUBSYS_HIGH_LEAK_COND*/ 0x444, /*DDRSUBSYS_CONFIG*/ 0x448, + /*TIMER_4_REF*/ 0x450, /*TIMER_4_DOWNCOUNT*/ 0x454, + /*TIMER_4_MODE*/ 0x458, /*TIMER_5_REF*/ 0x45c, + /*TIMER_5_DOWNCOUNT*/ 0x460, /*TIMER_5_MODE*/ 0x464, + /*APE_MEM_REQ*/ 0x470, /*DBG_FRCS_APE_MEM_REQ*/ 0x474, + /*APE_MEM_WFX_EN*/ 0x478, /*APE_MEM_LATENCY*/ 0x47c, + /*APE_MEM_ACK*/ 0x480, /*ITSTATUS5*/ 0x484, + /*ARM_IT1_VAL*/ 0x494, /*MOD_PWR_OK*/ 0x498, + /*MOD_AUXCLKOK*/ 0x49c, /*MOD_AWAKE_STATUS*/ 0x4a0, + /*MOD_SWRESET_IRQ_ACK*/ 0x4a4, /*MOD_SWRESET_ACK*/ 0x4a8, + /*DBG_PWRCTL*/ 0x4ac, /*HWOBS_H*/ 0x4b0, + /*HWOBS_L*/ 0x4b4, /*PLLDSI_FREQ*/ 0x500, + /*PLLDSI_ENABLE*/ 0x504, /*PLLDSI_LOCKP*/ 0x508, + /*RNG_ENABLE*/ 0x50c, /*YYCLKEN0_MGT_SET*/ 0x510, + /*YYCLKEN0_MGT_VAL*/ 0x520, /*YYCLKEN1_MGT_VAL*/ 0x524, + /*XP70CLK_MGT2*/ 0x528, /*DSITVCLK_DIV*/ 0x52c, + /*DSI_PLLOUT_SEL*/ 0x530, /*DSI_GLITCHFREE_EN*/ 0x534, + /*CLKACTIV*/ 0x538, /*SIA_MMDSP_MEM_MGT*/ 0x53c, + /*SVA_MMDSP_MEM_MGT*/ 0x540, /*SXAMMDSP_FORCE_CLKEN*/ 0x544, + /*UICC_NANDTREE*/ 0x570, /*GPIOCR2*/ 0x574, + /*MDM_ACWAKE*/ 0x578, /*MOD_MEM_REQ*/ 0x5a4, + /*MOD_MEM_ACK*/ 0x5a8, /*ARM_PLLDIVPS_REQ*/ 0x5b0, + /*ARM_PLLDIVPS_ACK*/ 0x5b4, /*SRPTIMER_VAL*/ 0x5d0, +}; + +/* Offsets from secure base which is U8500_PRCMU_BASE + SZ_4K */ +static const u16 u8500_prcmu_dump_secure_regs[] = { + /*SECNONSEWM*/ 0x00, /*ESRAM0_INITN*/ 0x04, + /*ARMITMSKSEC_31TO0*/ 0x08, /*ARMITMSKSEC_63TO32*/ 0x0C, + /*ARMITMSKSEC_95TO64*/ 0x10, /*ARMITMSKSEC_127TO96*/ 0x14, + /*ARMIT_MASKXP70_IT*/ 0x18, /*ESRAM0_EPOD_CFG*/ 0x1C, + /*ESRAM0_EPOD_C_VAL*/ 0x20, /*ESRAM0_EPOD_VOK*/ 0x2C, + /*ESRAM0_LS_SLEEP*/ 0x30, /*SECURE_ONGOING*/ 0x34, + /*I2C_SECURE*/ 0x38, /*RESET_STATUS*/ 0x3C, + /*PERIPH4_RESETN_VAL*/ 0x48, /*SPAREOUT_SEC*/ 0x4C, + /*PIPELINEDCR*/ 0xD8, +}; + +static int ape_voltage_count; + +static void log_set(struct state_history *sh, u8 opp) +{ + ktime_t now; + ktime_t dtime; + unsigned long flags; + int state; + + now = ktime_get(); + spin_lock_irqsave(&sh->lock, flags); + + for (state = 0 ; sh->opps[state] != opp; state++) + ; + BUG_ON(state >= sh->max_states); + + dtime = ktime_sub(now, sh->start); + sh->time[sh->state] = ktime_add(sh->time[sh->state], dtime); + sh->start = now; + sh->counter[sh->state]++; + sh->state = state; + + spin_unlock_irqrestore(&sh->lock, flags); +} + +void prcmu_debug_ape_opp_log(u8 opp) +{ + if (opp == APE_50_PARTLY_25_OPP) + opp = APE_50_OPP; + + log_set(&ape_sh, opp); +} + +void prcmu_debug_ddr_opp_log(u8 opp) +{ + log_set(&ddr_sh, opp); +} + +void prcmu_debug_arm_opp_log(u8 opp) +{ + log_set(&arm_sh, opp); +} + +static void log_reset(struct state_history *sh) +{ + unsigned long flags; + int i; + + pr_info("reset\n"); + + spin_lock_irqsave(&sh->lock, flags); + for (i = 0; i < sh->max_states; i++) { + sh->counter[i] = 0; + sh->time[i] = ktime_set(0, 0); + } + + sh->start = ktime_get(); + spin_unlock_irqrestore(&sh->lock, flags); + +} + +static ssize_t ape_stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + log_reset(&ape_sh); + return count; +} + +static ssize_t ddr_stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + log_reset(&ddr_sh); + return count; +} + +static ssize_t arm_stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + log_reset(&arm_sh); + return count; +} + +static int log_print(struct seq_file *s, struct state_history *sh) +{ + int i; + unsigned long flags; + ktime_t total; + ktime_t dtime; + s64 t_ms; + s64 perc; + s64 total_ms; + + spin_lock_irqsave(&sh->lock, flags); + + dtime = ktime_sub(ktime_get(), sh->start); + + total = dtime; + + for (i = 0; i < sh->max_states; i++) + total = ktime_add(total, sh->time[i]); + total_ms = ktime_to_ms(total); + + for (i = 0; i < sh->max_states; i++) { + ktime_t t = sh->time[i]; + if (sh->state == i) + t = ktime_add(t, dtime); + + t_ms = ktime_to_ms(t); + perc = 100 * t_ms; + do_div(perc, total_ms); + + seq_printf(s, "%s OPP %d: # %u in %lld ms %d%%\n", + sh->prefix, sh->state_names[i], + sh->counter[i] + (int)(sh->state == i), + t_ms, (u32)perc); + + } + spin_unlock_irqrestore(&sh->lock, flags); + return 0; +} + +static int ape_stats_print(struct seq_file *s, void *p) +{ + log_print(s, &ape_sh); + return 0; +} + +static int ddr_stats_print(struct seq_file *s, void *p) +{ + log_print(s, &ddr_sh); + return 0; +} + +static int arm_stats_print(struct seq_file *s, void *p) +{ + log_print(s, &arm_sh); + return 0; +} + +static int opp_read(struct seq_file *s, void *p) +{ + int opp; + + struct state_history *sh = (struct state_history *)s->private; + + switch (sh->req) { + case PRCMU_QOS_DDR_OPP: + opp = prcmu_get_ddr_opp(); + seq_printf(s, "%s (%d)\n", + (opp == DDR_100_OPP) ? "100%" : + (opp == DDR_50_OPP) ? "50%" : + (opp == DDR_25_OPP) ? "25%" : + "unknown", opp); + break; + case PRCMU_QOS_APE_OPP: + opp = prcmu_get_ape_opp(); + seq_printf(s, "%s (%d)\n", + (opp == APE_100_OPP) ? "100%" : + (opp == APE_50_OPP) ? "50%" : + "unknown", opp); + break; + case PRCMU_QOS_ARM_OPP: + opp = prcmu_get_arm_opp(); + seq_printf(s, "%s (%d)\n", + (opp == ARM_MAX_OPP) ? "max" : + (opp == ARM_MAX_FREQ100OPP) ? "max-freq100" : + (opp == ARM_100_OPP) ? "100%" : + (opp == ARM_50_OPP) ? "50%" : + (opp == ARM_EXTCLK) ? "25% (extclk)" : + "unknown", opp); + break; + default: + break; + } + return 0; + +} + +static ssize_t opp_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + long unsigned i; + int err; + struct state_history *sh = (struct state_history *) + ((struct seq_file *)file->private_data)->private; + + err = kstrtoul_from_user(user_buf, count, 0, &i); + + if (err) + return err; + + prcmu_qos_force_opp(sh->req, i); + + pr_info("prcmu debug: forced OPP for %s to %d\n", sh->prefix, (int)i); + + return count; +} + +static int cpufreq_delay_read(struct seq_file *s, void *p) +{ + return seq_printf(s, "%lu\n", prcmu_qos_get_cpufreq_opp_delay()); +} + +static int ape_voltage_read(struct seq_file *s, void *p) +{ + return seq_printf(s, "This reference count only includes " + "requests via debugfs.\nCount: %d\n", + ape_voltage_count); +} + +static ssize_t ape_voltage_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + long unsigned i; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &i); + + if (err) + return err; + + switch (i) { + case 0: + if (ape_voltage_count == 0) + pr_info("prcmu debug: reference count is already 0\n"); + else { + err = prcmu_request_ape_opp_100_voltage(false); + if (err) + pr_err("prcmu debug: drop request failed\n"); + else + ape_voltage_count--; + } + break; + case 1: + err = prcmu_request_ape_opp_100_voltage(true); + if (err) + pr_err("prcmu debug: request failed\n"); + else + ape_voltage_count++; + break; + default: + pr_info("prcmu debug: value not equal to 0 or 1\n"); + } + return count; +} + +static ssize_t cpufreq_delay_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + long unsigned i; + + err = kstrtoul_from_user(user_buf, count, 0, &i); + + if (err) + return err; + + prcmu_qos_set_cpufreq_opp_delay(i); + + pr_info("prcmu debug: changed delay between cpufreq change and QoS " + "requirement to %lu.\n", i); + + return count; +} + +/* These are only for u8500 */ +#define PRCM_AVS_BASE 0x2FC +#define AVS_VBB_RET 0x0 +#define AVS_VBB_MAX_OPP 0x1 +#define AVS_VBB_100_OPP 0x2 +#define AVS_VBB_50_OPP 0x3 +#define AVS_VARM_MAX_OPP 0x4 +#define AVS_VARM_100_OPP 0x5 +#define AVS_VARM_50_OPP 0x6 +#define AVS_VARM_RET 0x7 +#define AVS_VAPE_100_OPP 0x8 +#define AVS_VAPE_50_OPP 0x9 +#define AVS_VMOD_100_OPP 0xA +#define AVS_VMOD_50_OPP 0xB +#define AVS_VSAFE 0xC +#define AVS_VSAFE_RET 0xD +#define AVS_SIZE 14 + +static int avs_read(struct seq_file *s, void *p) +{ + + u8 avs[AVS_SIZE]; + void __iomem *tcdm_base; + + if (cpu_is_u8500()) { + tcdm_base = __io_address(U8500_PRCMU_TCDM_BASE); + + memcpy_fromio(avs, tcdm_base + PRCM_AVS_BASE, AVS_SIZE); + + seq_printf(s, "VBB_RET : 0x%02x\n", avs[AVS_VBB_RET]); + seq_printf(s, "VBB_MAX_OPP : 0x%02x\n", avs[AVS_VBB_MAX_OPP]); + seq_printf(s, "VBB_100_OPP : 0x%02x\n", avs[AVS_VBB_100_OPP]); + seq_printf(s, "VBB_50_OPP : 0x%02x\n", avs[AVS_VBB_50_OPP]); + seq_printf(s, "VARM_MAX_OPP : 0x%02x\n", avs[AVS_VARM_MAX_OPP]); + seq_printf(s, "VARM_100_OPP : 0x%02x\n", avs[AVS_VARM_100_OPP]); + seq_printf(s, "VARM_50_OPP : 0x%02x\n", avs[AVS_VARM_50_OPP]); + seq_printf(s, "VARM_RET : 0x%02x\n", avs[AVS_VARM_RET]); + seq_printf(s, "VAPE_100_OPP : 0x%02x\n", avs[AVS_VAPE_100_OPP]); + seq_printf(s, "VAPE_50_OPP : 0x%02x\n", avs[AVS_VAPE_50_OPP]); + seq_printf(s, "VMOD_100_OPP : 0x%02x\n", avs[AVS_VMOD_100_OPP]); + seq_printf(s, "VMOD_50_OPP : 0x%02x\n", avs[AVS_VMOD_50_OPP]); + seq_printf(s, "VSAFE : 0x%02x\n", avs[AVS_VSAFE]); + seq_printf(s, "VSAFE_RET : 0x%02x\n", avs[AVS_VSAFE_RET]); + } else { + seq_printf(s, "Only u8500 supported.\n"); + } + + return 0; +} + +static void prcmu_data_mem_print(struct seq_file *s) +{ + int i; + int err; + void __iomem *tcdm_base; + u32 dmem[4]; + + if (cpu_is_u8500()) { + tcdm_base = __io_address(U8500_PRCMU_TCDM_BASE); + + for (i = 0; i < U8500_PRCMU_TCDM_SIZE; i += 16) { + dmem[0] = readl(tcdm_base + i + 0); + dmem[1] = readl(tcdm_base + i + 4); + dmem[2] = readl(tcdm_base + i + 8); + dmem[3] = readl(tcdm_base + i + 12); + + if (s) { + err = seq_printf(s, + "0x%x: 0x%08x 0x%08x 0x%08x 0x%08x\n", + ((int)tcdm_base) + i, dmem[0], + dmem[1], dmem[2], dmem[3]); + if (err < 0) { + pr_err("%s: seq_printf overflow, addr=%x\n", + __func__, ((int)tcdm_base) + i); + /* Can't do much here */ + return; + } + } else { + printk(KERN_INFO + "0x%x: 0x%08x 0x%08x 0x%08x 0x%08x\n", + ((int)tcdm_base) + i, dmem[0], + dmem[1], dmem[2], dmem[3]); + } + } + } +} + +void prcmu_debug_dump_data_mem(void) +{ + printk(KERN_INFO "PRCMU data memory dump:\n"); + prcmu_data_mem_print(NULL); +} + +static int prcmu_debugfs_data_mem_read(struct seq_file *s, void *p) +{ + seq_printf(s, "PRCMU data memory:\n"); + prcmu_data_mem_print(s); + + return 0; +} + +static void prcmu_regs_print(struct seq_file *s) +{ + int i; + int err; + void __iomem *prcmu_base; + u32 reg_val; + + if (cpu_is_u8500()) { + prcmu_base = __io_address(U8500_PRCMU_BASE); + + for (i = 0; i < ARRAY_SIZE(u8500_prcmu_dump_regs); i++) { + reg_val = readl(prcmu_base + + u8500_prcmu_dump_regs[i]); + + if (s) { + err = seq_printf(s, "0x%04x: 0x%08x\n", + u8500_prcmu_dump_regs[i], reg_val); + if (err < 0) { + pr_err("%s: seq_printf overflow," + "offset=%x\n", __func__, + u8500_prcmu_dump_regs[i]); + /* Can't do much here */ + return; + } + } else { + printk(KERN_INFO + "0x%04x: 0x%08x\n", + u8500_prcmu_dump_regs[i], reg_val); + } + } + } +} + +static void prcmu_secure_regs_print(struct seq_file *s) +{ + int i; + int err; + void __iomem *prcmu_sec_base; + u32 reg_val; + + if (cpu_is_u8500()) { + /* PRCMU secure base starts after SZ_4K */ + prcmu_sec_base = ioremap(U8500_PRCMU_BASE + SZ_4K, SZ_4K); + if (!prcmu_sec_base) { + pr_err("%s: ioremap faild\n", __func__); + return; + } + + for (i = 0; i < ARRAY_SIZE(u8500_prcmu_dump_secure_regs); i++) { + reg_val = readl(prcmu_sec_base + + u8500_prcmu_dump_secure_regs[i]); + + if (s) { + err = seq_printf(s, "0x%04x: 0x%08x\n", + u8500_prcmu_dump_secure_regs[i] + + SZ_4K, + reg_val); + if (err < 0) { + pr_err("%s: seq_printf overflow," + "offset=%x\n", __func__, + u8500_prcmu_dump_secure_regs[i] + + SZ_4K); + /* Can't do much here */ + break; + } + } else { + printk(KERN_INFO + "0x%04x: 0x%08x\n", + u8500_prcmu_dump_secure_regs[i] + + SZ_4K, + reg_val); + } + } + + iounmap(prcmu_sec_base); + } +} + +void prcmu_debug_dump_regs(void) +{ + printk(KERN_INFO "PRCMU registers dump:\n"); + prcmu_regs_print(NULL); + prcmu_secure_regs_print(NULL); +} + +static int prcmu_debugfs_regs_read(struct seq_file *s, void *p) +{ + seq_printf(s, "PRCMU registers:\n"); + prcmu_regs_print(s); + prcmu_secure_regs_print(s); + return 0; +} + +/* Interrupt debugging */ + +/* There are eight mailboxes */ +#define NUM_MAILBOXES 8 +#define NUM_MAILBOX0_EVENTS 32 +static u32 num_mailbox_interrupts[NUM_MAILBOXES]; +static u32 num_mailbox0_events[NUM_MAILBOX0_EVENTS]; +static u32 num_mailbox0_events_garbage[NUM_MAILBOX0_EVENTS]; + +void prcmu_debug_register_interrupt(u32 mailbox) +{ + if (mailbox < NUM_MAILBOXES) + num_mailbox_interrupts[mailbox]++; +} + +void prcmu_debug_register_mbox0_event(u32 ev, u32 mask) +{ + int i; + + for (i = 0 ; i < NUM_MAILBOX0_EVENTS; i++) + if (ev & (1 << i)) { + if (mask & (1 << i)) + num_mailbox0_events[i]++; + else + num_mailbox0_events_garbage[i]++; + } +} + +static int interrupt_read(struct seq_file *s, void *p) +{ + int i; + char **mbox0names; + + static char *mbox0names_u8500[] = { + "RTC", + "RTT0", + "RTT1", + "HSI0", + "HSI1", + "CA_WAKE", + "USB", + "ABB", + "ABB_FIFO", + "SYSCLK_OK", + "CA_SLEE", + "AC_WAKE_ACK", + "SIDE_TONE_OK", + "ANC_OK", + "SW_ERROR", + "AC_SLEEP_ACK", + NULL, + "ARM", + "HOTMON_LOW", + "HOTMON_HIGH", + "MODEM_SW_RESET_REQ", + NULL, + NULL, + "GPIO0", + "GPIO1", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8"}; + static char *mbox0names_u5500[] = { + "RTC", + "RTT0", + "RTT1", + "CD_IRQ", + "SRP_TIM", + "APE_REQ", + "USB", + "ABB", + "LOW_POWER_AUDIO", + "TEMP_SENSOR_LOW", + "ARM", + "AC_WAKE_ACK", + NULL, + "TEMP_SENSOR_HIGH", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "MODEM_SW_RESET_REQ", + NULL, + NULL, + "GPIO0", + "GPIO1", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "AC_REL_ACK"}; + + if (cpu_is_u8500()) { + mbox0names = mbox0names_u8500; + } else if (cpu_is_u5500()) { + mbox0names = mbox0names_u5500; + } else { + seq_printf(s, "Unknown ASIC!\n"); + return 0; + } + + seq_printf(s, "mailbox0: %d\n", num_mailbox_interrupts[0]); + + for (i = 0; i < NUM_MAILBOX0_EVENTS; i++) + if (mbox0names[i]) { + seq_printf(s, " %20s %d ", mbox0names[i], + num_mailbox0_events[i] + ); + if (num_mailbox0_events_garbage[i]) + seq_printf(s, "unwanted: %d", + num_mailbox0_events_garbage[i]); + seq_printf(s, "\n"); + } else if (num_mailbox0_events[i]) { + seq_printf(s, " unknown (%d) %d\n", + i, num_mailbox0_events[i]); + } + + for (i = 1 ; i < NUM_MAILBOXES; i++) + seq_printf(s, "mailbox%d: %d\n", i, num_mailbox_interrupts[i]); + return 0; +} + +static int opp_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, opp_read, inode->i_private); +} + +static int ape_stats_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, ape_stats_print, inode->i_private); +} + +static int ddr_stats_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, ddr_stats_print, inode->i_private); +} + +static int arm_stats_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, arm_stats_print, inode->i_private); +} + +static int cpufreq_delay_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, cpufreq_delay_read, inode->i_private); +} + +static int ape_voltage_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, ape_voltage_read, inode->i_private); +} + +static int avs_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, avs_read, inode->i_private); +} + +static int prcmu_data_mem_open_file(struct inode *inode, struct file *file) +{ + int err; + struct seq_file *s; + + err = single_open(file, prcmu_debugfs_data_mem_read, inode->i_private); + if (!err) { + /* Default buf size in seq_read is not enough */ + s = (struct seq_file *)file->private_data; + s->size = (PAGE_SIZE * 4); + s->buf = kmalloc(s->size, GFP_KERNEL); + if (!s->buf) { + single_release(inode, file); + err = -ENOMEM; + } + } + return err; +} + +static int prcmu_regs_open_file(struct inode *inode, struct file *file) +{ + int err; + struct seq_file *s; + + err = single_open(file, prcmu_debugfs_regs_read, inode->i_private); + if (!err) { + /* Default buf size in seq_read is not enough */ + s = (struct seq_file *)file->private_data; + s->size = (PAGE_SIZE * 2); + s->buf = kmalloc(s->size, GFP_KERNEL); + if (!s->buf) { + single_release(inode, file); + err = -ENOMEM; + } + } + return err; +} + +static int interrupt_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, interrupt_read, inode->i_private); +} + +static const struct file_operations opp_fops = { + .open = opp_open_file, + .write = opp_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ape_stats_fops = { + .open = ape_stats_open_file, + .write = ape_stats_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ddr_stats_fops = { + .open = ddr_stats_open_file, + .write = ddr_stats_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations arm_stats_fops = { + .open = arm_stats_open_file, + .write = arm_stats_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations cpufreq_delay_fops = { + .open = cpufreq_delay_open_file, + .write = cpufreq_delay_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ape_voltage_fops = { + .open = ape_voltage_open_file, + .write = ape_voltage_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations avs_fops = { + .open = avs_open_file, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations prcmu_data_mem_fops = { + .open = prcmu_data_mem_open_file, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations prcmu_regs_fops = { + .open = prcmu_regs_open_file, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations interrupts_fops = { + .open = interrupt_open_file, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int setup_debugfs(void) +{ + struct dentry *dir; + struct dentry *file; + + dir = debugfs_create_dir("prcmu", NULL); + if (IS_ERR_OR_NULL(dir)) + goto fail; + + file = debugfs_create_file("ape_stats", (S_IRUGO | S_IWUGO), + dir, NULL, &ape_stats_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("ddr_stats", (S_IRUGO | S_IWUGO), + dir, NULL, &ddr_stats_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("arm_stats", (S_IRUGO | S_IWUGO), + dir, NULL, &arm_stats_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("ape_opp", (S_IRUGO), + dir, (void *)&ape_sh, + &opp_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("ddr_opp", (S_IRUGO), + dir, (void *)&ddr_sh, + &opp_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("arm_opp", (S_IRUGO), + dir, (void *)&arm_sh, + &opp_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("opp_cpufreq_delay", (S_IRUGO), + dir, NULL, &cpufreq_delay_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("ape_voltage", (S_IRUGO), + dir, NULL, &ape_voltage_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("avs", + (S_IRUGO), + dir, NULL, &avs_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("data_mem", (S_IRUGO), + dir, NULL, + &prcmu_data_mem_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("regs", (S_IRUGO), + dir, NULL, + &prcmu_regs_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("interrupts", + (S_IRUGO), + dir, NULL, &interrupts_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + return 0; +fail: + if (!IS_ERR_OR_NULL(dir)) + debugfs_remove_recursive(dir); + + pr_err("prcmu debug: debugfs entry failed\n"); + return -ENOMEM; +} + +static __init int prcmu_debug_init(void) +{ + spin_lock_init(&ape_sh.lock); + spin_lock_init(&ddr_sh.lock); + spin_lock_init(&arm_sh.lock); + ape_sh.start = ktime_get(); + ddr_sh.start = ktime_get(); + arm_sh.start = ktime_get(); + return 0; +} +arch_initcall(prcmu_debug_init); + +static __init int prcmu_debug_debugfs_init(void) +{ + setup_debugfs(); + return 0; +} +late_initcall(prcmu_debug_debugfs_init); diff --git a/arch/arm/mach-ux500/product.c b/arch/arm/mach-ux500/product.c new file mode 100644 index 00000000000..f4647b3ab29 --- /dev/null +++ b/arch/arm/mach-ux500/product.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Jens Wiklander <jens.wiklander@stericsson.com> + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + * Author: Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com> + * + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/tee.h> +#include <linux/module.h> +#include <mach/hardware.h> +#include <asm/mach-types.h> + +#define STATIC_TEE_TA_START_LOW 0xBC765EDE +#define STATIC_TEE_TA_START_MID 0x6724 +#define STATIC_TEE_TA_START_HIGH 0x11DF +#define STATIC_TEE_TA_START_CLOCKSEQ \ + {0x8E, 0x12, 0xEC, 0xDB, 0xDF, 0xD7, 0x20, 0x85} + +#define U5500_PRCMU_DBG_PWRCTRL (U5500_PRCMU_BASE + 0x4AC) +#define PRCMU_DBG_PWRCTRL_A9DBGCLKEN (1 << 4) + +static struct tee_product_config product_config; + +bool ux500_jtag_enabled(void) +{ +#ifdef CONFIG_UX500_DEBUG_NO_LAUTERBACH + return false; +#else + if (machine_is_snowball()) + return true; + if (cpu_is_u5500()) + return readl_relaxed(__io_address(U5500_PRCMU_DBG_PWRCTRL)) + & PRCMU_DBG_PWRCTRL_A9DBGCLKEN; + + if (cpu_is_u8500() || cpu_is_u9540()) + return (product_config.rt_flags & TEE_RT_FLAGS_JTAG_ENABLED) == + TEE_RT_FLAGS_JTAG_ENABLED; + + return true; +#endif +} + +static int __init product_detect(void) +{ + int err; + int origin_err; + struct tee_operation operation = { { { 0 } } }; + struct tee_context context; + struct tee_session session; + + /* Selects trustzone application needed for the job. */ + struct tee_uuid static_uuid = { + STATIC_TEE_TA_START_LOW, + STATIC_TEE_TA_START_MID, + STATIC_TEE_TA_START_HIGH, + STATIC_TEE_TA_START_CLOCKSEQ, + }; + + if (cpu_is_u5500()) + return -ENODEV; + + err = teec_initialize_context(NULL, &context); + if (err) { + pr_err("ux500-product: unable to initialize tee context," + " err = %d\n", err); + err = -EINVAL; + goto error0; + } + + err = teec_open_session(&context, &session, &static_uuid, + TEEC_LOGIN_PUBLIC, NULL, NULL, &origin_err); + if (err) { + pr_err("ux500-product: unable to open tee session," + " tee error = %d, origin error = %d\n", + err, origin_err); + err = -EINVAL; + goto error1; + } + + operation.shm[0].buffer = &product_config; + operation.shm[0].size = sizeof(product_config); + operation.shm[0].flags = TEEC_MEM_OUTPUT; + operation.flags = TEEC_MEMREF_0_USED; + + err = teec_invoke_command(&session, + TEE_STA_GET_PRODUCT_CONFIG, + &operation, &origin_err); + if (err) { + pr_err("ux500-product: fetching product settings failed, err=%d", + err); + err = -EINVAL; + goto error1; + } + + switch (product_config.product_id) { + case TEE_PRODUCT_ID_8400: + pr_info("ux500-product: u8400 detected\n"); + break; + case TEE_PRODUCT_ID_8500: + pr_info("ux500-product: u8500 detected\n"); + break; + case TEE_PRODUCT_ID_9500: + pr_info("ux500-product: u9500 detected\n"); + break; + case TEE_PRODUCT_ID_5500: + pr_info("ux500-product: u5500 detected\n"); + break; + case TEE_PRODUCT_ID_7400: + pr_info("ux500-product: u7400 detected\n"); + break; + case TEE_PRODUCT_ID_8500C: + pr_info("ux500-product: u8500C detected\n"); + break; + case TEE_PRODUCT_ID_UNKNOWN: + default: + pr_info("ux500-product: UNKNOWN! (0x%x) detected\n", + product_config.product_id); + break; + } + pr_info("ux500-product: JTAG is %s\n", + ux500_jtag_enabled() ? "enabled" : "disabled"); +error1: + (void) teec_finalize_context(&context); +error0: + return err; +} +device_initcall(product_detect); diff --git a/arch/arm/mach-ux500/product.h b/arch/arm/mach-ux500/product.h new file mode 100644 index 00000000000..502eff4df14 --- /dev/null +++ b/arch/arm/mach-ux500/product.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Jens Wiklander <jens.wiklander@stericsson.com> + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + * + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#ifndef UX500_PRODUCT_H +#define UX500_PRODUCT_H + +#ifdef CONFIG_TEE_UX500 + +bool ux500_jtag_enabled(void); + +#else + +static inline bool ux500_jtag_enabled(void) +{ + return true; +} + +#endif +#endif diff --git a/arch/arm/mach-ux500/regulator-u5500.h b/arch/arm/mach-ux500/regulator-u5500.h new file mode 100644 index 00000000000..cf3eeed9366 --- /dev/null +++ b/arch/arm/mach-ux500/regulator-u5500.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + */ + +#ifndef __REGULATOR_U5500_H +#define __REGULATOR_U5500_H + +enum u5500_regulator_id { + U5500_REGULATOR_VAPE, + U5500_REGULATOR_SWITCH_SGA, + U5500_REGULATOR_SWITCH_HVA, + U5500_REGULATOR_SWITCH_SIA, + U5500_REGULATOR_SWITCH_DISP, + U5500_REGULATOR_SWITCH_ESRAM12, + U5500_NUM_REGULATORS +}; + +#endif diff --git a/arch/arm/mach-ux500/test/Kconfig b/arch/arm/mach-ux500/test/Kconfig new file mode 100644 index 00000000000..a071166d092 --- /dev/null +++ b/arch/arm/mach-ux500/test/Kconfig @@ -0,0 +1,6 @@ + +config DB8500_PWR_TEST + bool "Power usage module test" + depends on (UX500_SOC_DB8500 && DEBUG_FS && REGULATOR_AB8500_DEBUG) + help + Add power module tests for idle diff --git a/arch/arm/mach-ux500/test/Makefile b/arch/arm/mach-ux500/test/Makefile new file mode 100644 index 00000000000..58f1f2f3be6 --- /dev/null +++ b/arch/arm/mach-ux500/test/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_DB8500_PWR_TEST) += pwr.o diff --git a/arch/arm/mach-ux500/test/pwr.c b/arch/arm/mach-ux500/test/pwr.c new file mode 100644 index 00000000000..5d5d24a38ab --- /dev/null +++ b/arch/arm/mach-ux500/test/pwr.c @@ -0,0 +1,828 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + * License terms: GNU General Public License (GPL) version 2 + * + * This is a module test for clocks and regulators. + */ + +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/seq_file.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/mfd/abx500/ab8500-sysctrl.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/db8500-prcmu.h> + +#include <mach/hardware.h> +#include <mach/pm.h> + +#include "../../../drivers/regulator/dbx500-prcmu.h" +#include "../../../drivers/regulator/ab8500-debug.h" + +#define PRCC_PCKSR 0x010 +#define PRCC_KCKSR 0x014 + +#define PRCM_PLLSOC0_ENABLE 0x090 +#define PRCM_PLLSOC1_ENABLE 0x094 +#define PRCM_PLL32K_ENABLE 0x10C +#define PRCM_PLLARM_ENABLE 0x098 +#define PRCM_PLLDDR_ENABLE 0x09C +#define PRCM_RNG_ENABLE 0x50C +#define PRCM_PLLDSI_ENABLE 0x504 + +#define PRCM_CLKOCR 0x1CC + +#define PRCM_ARMCLKFIX_MGT 0x000 +#define PRCM_ACLK_MGT 0x004 +#define PRCM_SGACLK_MGT 0x014 +#define PRCM_UARTCLK_MGT 0x018 +#define PRCM_MSP02CLK_MGT 0x01C +#define PRCM_MSP1CLK_MGT 0x288 +#define PRCM_I2CCLK_MGT 0x020 +#define PRCM_SDMMCCLK_MGT 0x024 +#define PRCM_SLIMCLK_MGT 0x028 +#define PRCM_PER1CLK_MGT 0x02C +#define PRCM_PER2CLK_MGT 0x030 +#define PRCM_PER3CLK_MGT 0x034 +#define PRCM_PER5CLK_MGT 0x038 +#define PRCM_PER6CLK_MGT 0x03C +#define PRCM_PER7CLK_MGT 0x040 +#define PRCM_LCDCLK_MGT 0x044 +#define PRCM_BMLCLK_MGT 0x04C +#define PRCM_HSITXCLK_MGT 0x050 +#define PRCM_HSIRXCLK_MGT 0x054 +#define PRCM_HDMICLK_MGT 0x058 +#define PRCM_APEATCLK_MGT 0x05C +#define PRCM_APETRACECLK_MGT 0x060 +#define PRCM_MCDECLK_MGT 0x064 +#define PRCM_IPI2CCLK_MGT 0x068 +#define PRCM_DSIALTCLK_MGT 0x06C +#define PRCM_SPARE2CLK_MGT 0x070 +#define PRCM_SPARE1CLK_MGT 0x048 +#define PRCM_DMACLK_MGT 0x074 +#define PRCM_B2R2CLK_MGT 0x078 +#define PRCM_TVCLK_MGT 0x07C +#define PRCM_SSPCLK_MGT 0x280 +#define PRCM_RNGCLK_MGT 0x284 +#define PRCM_UICCCLK_MGT 0x27C + +#define PRCM_DSITVCLK_DIV 0x52C +#define PRCM_DSI_PLLOUT_SEL 0x530 + +#define PRCM_YYCLKEN0_MGT_VAL 0x520 + +enum acc_type { + RAW = 0, + CLK_PRCMU, + CLK_PAR_PRCMU, + CLK_ABB_SYS, + REG_DB8500, + REG_AB8500, +}; + +struct pwr_test { + enum acc_type type; + u32 base; + u32 off; + u32 mask; + u32 val; + + u32 par_off; + u32 par_mask; + u32 par_val; + + char *txt; + char *txt2; + + /* For AB8500 */ + enum ab8500_regulator_mode mode; + enum ab8500_regulator_hwmode hwmode; + enum hwmode_auto hwmode_auto[4]; + int volt_selected; + int alt_volt_selected; + int volt_len; + int volt[4]; + int alt_volt[4]; +}; + +#define RAW_TEST(_base, _off, _mask, _val) \ + { \ + .type = RAW, \ + .base = _base, \ + .off = _off, \ + .mask = _mask, \ + .val = _val, \ + .txt = #_base, \ + .txt2 = #_off \ + } + +#define CLK_TEST_PAR_PRCMU(_base, _off, _mask, _val, _par_off, \ + _par_mask, _par_val) \ + { \ + .type = CLK_PAR_PRCMU, \ + .base = _base, \ + .off = _off, \ + .mask = _mask, \ + .val = _val, \ + .par_off = _par_off, \ + .par_mask = _par_mask, \ + .par_val = _par_val, \ + .txt = #_base, \ + .txt2 = #_off \ + } + +#define CLK_TEST_PRCMU(_off, _mask, _val) \ + { \ + .type = CLK_PRCMU, \ + .off = _off, \ + .mask = _mask, \ + .val = _val, \ + .txt = #_off, \ + } + +#define CLK_TEST_ABB_SYS(_off, _mask, _val) \ + { \ + .type = CLK_ABB_SYS, \ + .off = _off, \ + .mask = _mask, \ + .val = _val, \ + .txt = #_off, \ + } +#define REG_TEST_DB8500(_reg, _val) \ + { \ + .type = REG_DB8500, \ + .off = _reg, \ + .val = _val, \ + } + + +static struct u8500_regulators +{ + struct dbx500_regulator_info *db8500_reg; + int db8500_num; +} u8500_reg; + +/* + * Idle - Note: this is not suspend. + * This test shall pass when the screen is black, before + * suspend is executed. + */ + +static struct pwr_test idle_test[] = { + + /* Test all periph kernel and bus clocks */ + /* bus: gpioctrl */ + CLK_TEST_PAR_PRCMU(U8500_CLKRST1_BASE, PRCC_PCKSR, 0x07ff, BIT(9), + PRCM_PER1CLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PAR_PRCMU(U8500_CLKRST1_BASE, PRCC_KCKSR, 0x037f, 0x0000, + PRCM_PER1CLK_MGT, BIT(8), BIT(8)), + /* bus: gpioctrl */ + CLK_TEST_PAR_PRCMU(U8500_CLKRST2_BASE, PRCC_PCKSR, 0x0fff, BIT(11), + PRCM_PER2CLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PAR_PRCMU(U8500_CLKRST2_BASE, PRCC_KCKSR, 0x00ff, 0x0000, + PRCM_PER2CLK_MGT, BIT(8), BIT(8)), + /* bus: uart2, gpioctrl - users??, kernel: uart2 */ + CLK_TEST_PAR_PRCMU(U8500_CLKRST3_BASE, PRCC_PCKSR, 0x01ff, BIT(8) | BIT(6), + PRCM_PER3CLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PAR_PRCMU(U8500_CLKRST3_BASE, PRCC_KCKSR, 0x00fe, BIT(6), + PRCM_PER3CLK_MGT, BIT(8), BIT(8)), + /* No user on periph5 */ + CLK_TEST_PAR_PRCMU(U8500_CLKRST5_BASE, PRCC_PCKSR, 0x0003, 0x0000, + PRCM_PER5CLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PAR_PRCMU(U8500_CLKRST5_BASE, PRCC_KCKSR, 0x0000, 0x0000, + PRCM_PER5CLK_MGT, BIT(8), BIT(8)), + /* bus: MTU0 used for scheduling ApIdle */ + CLK_TEST_PAR_PRCMU(U8500_CLKRST6_BASE, PRCC_PCKSR, 0x00ff, BIT(6), + PRCM_PER6CLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PAR_PRCMU(U8500_CLKRST6_BASE, PRCC_KCKSR, 0x0001, 0x0000, + PRCM_PER6CLK_MGT, BIT(8), BIT(8)), + + /* Test that clkout 1/2 is off */ + CLK_TEST_PRCMU(PRCM_CLKOCR, 0x3f003f, 0x0000), + + /* Test that ulp clock is off */ + CLK_TEST_ABB_SYS(AB8500_SYSULPCLKCTRL1, 0xfc, 0x00), + + /* Test that prcm clks are in proper state */ + CLK_TEST_PRCMU(PRCM_ARMCLKFIX_MGT, BIT(8), BIT(8)), + CLK_TEST_PRCMU(PRCM_ACLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PRCMU(PRCM_SGACLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_UARTCLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PRCMU(PRCM_MSP02CLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_MSP1CLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_I2CCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_SDMMCCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_SLIMCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_PER1CLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PRCMU(PRCM_PER2CLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PRCMU(PRCM_PER3CLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PRCMU(PRCM_PER5CLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_PER6CLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PRCMU(PRCM_PER7CLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_LCDCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_BMLCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_HSITXCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_HSIRXCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_HDMICLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_APEATCLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PRCMU(PRCM_APETRACECLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PRCMU(PRCM_MCDECLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_IPI2CCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_DSIALTCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_DMACLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PRCMU(PRCM_B2R2CLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_TVCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_SSPCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_RNGCLK_MGT, BIT(8), BIT(8)), + CLK_TEST_PRCMU(PRCM_UICCCLK_MGT, BIT(8), 0x000), + CLK_TEST_PRCMU(PRCM_DSITVCLK_DIV, BIT(24) | BIT(25) | BIT(26), + 0x000), + CLK_TEST_PRCMU(PRCM_DSI_PLLOUT_SEL, (BIT(0) | BIT(1) | BIT(2) | + BIT(8) | BIT(9) | BIT(10)), + 0x200), /* Will change */ + CLK_TEST_PRCMU(PRCM_YYCLKEN0_MGT_VAL, 0xBFFFFFFF, + (BIT(27) | BIT(23) | BIT(22) | BIT(15) | + BIT(13) | BIT(12) | BIT(11) | BIT(5) | + BIT(1) | BIT(0))), + + /* Check db8500 regulator settings - enable */ + REG_TEST_DB8500(DB8500_REGULATOR_VAPE, 0), + REG_TEST_DB8500(DB8500_REGULATOR_VARM, 0), + REG_TEST_DB8500(DB8500_REGULATOR_VMODEM, 0), + REG_TEST_DB8500(DB8500_REGULATOR_VPLL, 0), + REG_TEST_DB8500(DB8500_REGULATOR_VSMPS1, 0), + REG_TEST_DB8500(DB8500_REGULATOR_VSMPS2, 1), + REG_TEST_DB8500(DB8500_REGULATOR_VSMPS3, 0), + REG_TEST_DB8500(DB8500_REGULATOR_VRF1, 0), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_SVAMMDSP, 0), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_SVAMMDSPRET, 0), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_SVAPIPE, 0), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_SIAMMDSP, 0), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_SIAMMDSPRET, 0), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_SIAPIPE, 0), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_SGA, 0), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_B2R2_MCDE, 0), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_ESRAM12, 0), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_ESRAM12RET, 0), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_ESRAM34, 1), + REG_TEST_DB8500(DB8500_REGULATOR_SWITCH_ESRAM34RET, 0), + + /* ab8500 regulators */ + { + .type = REG_AB8500, + .off = AB8500_VARM, + .mode = AB8500_MODE_ON, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_OFF, HWM_INVAL, HWM_INVAL, HWM_OFF}, + .volt_selected = 2, + /* Voltage and voltage selection depends on ARM_OPP */ + .alt_volt_selected = 1, + .volt_len = 3, + .volt = {1350000, 1025000, 750000}, + .alt_volt = {1250000, 1025000, 750000}, + }, + { + .type = REG_AB8500, + .off = AB8500_VBBP, + .mode = AB8500_MODE_ON, + .hwmode_auto = {HWM_OFF, HWM_INVAL, HWM_INVAL, HWM_INVAL}, + .volt_selected = 1, + .volt_len = 2, + .volt = {-300000, 0}, + }, + { + .type = REG_AB8500, + .off = AB8500_VBBN, + .mode = AB8500_MODE_ON, + .hwmode_auto = {HWM_OFF, HWM_INVAL, HWM_INVAL, HWM_INVAL}, + .volt_selected = 1, + .volt_len = 2, + .volt = {300000, 0}, + }, + { + .type = REG_AB8500, + .off = AB8500_VAPE, + .mode = AB8500_MODE_ON, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_OFF, HWM_INVAL, HWM_INVAL, HWM_OFF}, + .volt_selected = 2, + /* APE_OPP can get changes by cpufreq */ + .alt_volt_selected = 1, + .volt_len = 3, + .volt = {1225000, 1000000, 1200000}, + }, + { + .type = REG_AB8500, + .off = AB8500_VSMPS1, + .mode = AB8500_MODE_HW, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_ON, HWM_OFF, HWM_OFF, HWM_OFF}, + .volt_selected = 2, + .volt_len = 3, + .volt = {1200000, 1200000, 1200000}, + }, + { + .type = REG_AB8500, + .off = AB8500_VSMPS2, + .mode = AB8500_MODE_HW, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_ON, HWM_ON, HWM_ON, HWM_OFF}, + .volt_selected = 2, + .volt_len = 3, + .volt = {1800000, 1800000, 1800000}, + }, + { + .type = REG_AB8500, + .off = AB8500_VSMPS3, + .mode = AB8500_MODE_HW, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_ON, HWM_OFF, HWM_OFF, HWM_OFF}, + .volt_selected = 2, + .volt_len = 3, + .volt = {925000, 1212500, 1200000}, + }, + { + .type = REG_AB8500, + .off = AB8500_VPLL, + .mode = AB8500_MODE_HW, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_ON, HWM_OFF, HWM_OFF, HWM_OFF}, + }, + { + .type = REG_AB8500, + .off = AB8500_VREFDDR, + .mode = AB8500_MODE_OFF, + }, + { + .type = REG_AB8500, + .off = AB8500_VMOD, + .mode = AB8500_MODE_OFF, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_OFF, HWM_OFF, HWM_OFF, HWM_OFF}, + .volt_selected = 2, + .volt_len = 2, + .volt = {1125000, 1025000}, + }, + { + .type = REG_AB8500, + .off = AB8500_VEXTSUPPLY1, + .mode = AB8500_MODE_LP, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_OFF, HWM_OFF, HWM_OFF, HWM_OFF}, + }, + { + .type = REG_AB8500, + .off = AB8500_VEXTSUPPLY2, + .mode = AB8500_MODE_OFF, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_OFF, HWM_OFF, HWM_OFF, HWM_OFF}, + }, + { + .type = REG_AB8500, + .off = AB8500_VEXTSUPPLY3, + .mode = AB8500_MODE_ON, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_ON, HWM_OFF, HWM_ON, HWM_OFF}, + }, + { + .type = REG_AB8500, + .off = AB8500_VRF1, + .mode = AB8500_MODE_HW, + .volt_len = 1, + .volt = {2150000}, + }, + { + .type = REG_AB8500, + .off = AB8500_VANA, + .mode = AB8500_MODE_OFF, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_OFF, HWM_OFF, HWM_OFF, HWM_OFF}, + }, + { + .type = REG_AB8500, + .off = AB8500_VAUX1, + .mode = AB8500_MODE_ON, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_OFF, HWM_OFF, HWM_OFF, HWM_OFF}, + .volt_len = 1, + .volt = {2800000}, + }, + { + .type = REG_AB8500, + .off = AB8500_VAUX2, + .mode = AB8500_MODE_ON, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_OFF, HWM_OFF, HWM_OFF, HWM_OFF}, + .volt_len = 1, + .volt = {3300000}, + }, + { + .type = REG_AB8500, + .off = AB8500_VAUX3, + .mode = AB8500_MODE_OFF, + .hwmode = AB8500_HWMODE_HPLP, + .hwmode_auto = {HWM_OFF, HWM_OFF, HWM_OFF, HWM_OFF}, + .volt_len = 1, + .volt = {2910000}, + }, + { + .type = REG_AB8500, + .off = AB8500_VINTCORE, + .mode = AB8500_MODE_OFF, + .volt_len = 1, + .volt = {1250000}, + }, + { + .type = REG_AB8500, + .off = AB8500_VTVOUT, + .mode = AB8500_MODE_OFF, + }, + { + .type = REG_AB8500, + .off = AB8500_VAUDIO, + .mode = AB8500_MODE_OFF, + }, + { + .type = REG_AB8500, + .off = AB8500_VANAMIC1, + .mode = AB8500_MODE_OFF, + }, + { + .type = REG_AB8500, + .off = AB8500_VANAMIC2, + .mode = AB8500_MODE_OFF, + }, + { + .type = REG_AB8500, + .off = AB8500_VDMIC, + .mode = AB8500_MODE_OFF, + }, + { + .type = REG_AB8500, + .off = AB8500_VUSB, + .mode = AB8500_MODE_OFF, + }, + { + .type = REG_AB8500, + .off = AB8500_VOTG, + .mode = AB8500_MODE_OFF, + }, + { + .type = REG_AB8500, + .off = AB8500_VBUSBIS, + .mode = AB8500_MODE_OFF, + }, +}; + +static int read_raw(struct pwr_test *t) +{ + u32 res; + int i; + + for (i = 0 ; i < 50; i++) { + res = readl(__io_address(t->base) + t->off); + if ((res & t->mask) == t->val) + return 0; + msleep(10); + } + + pr_err("pwr_test: ERROR: %s %s 0x%x, 0x%x reads 0x%x (masked: 0x%x), " + "expected 0x%x (mask: 0x%x)\n", + t->txt, t->txt2, t->base, t->off, res, + res & t->mask, t->val, t->mask); + return -EINVAL; +} + +static int clk_read_par_prcmu(struct pwr_test *t) + { + u32 res; + int i; + bool parent_enabled = false; + + /* check if parent clock is in expected configuration (usually PRCC) */ + for (i = 0 ; i < 50; i++) { + res = prcmu_read(t->par_off); + if ((res & t->par_mask) == t->par_val) { + parent_enabled = true; + break; + } + msleep(10); + } + + /* If parent clock is off, but clock is expected to be on --> fail */ + if (!parent_enabled && t->val != 0) { + pr_err("pwr_test: ERROR: PRCMU parent clk of %s, " + "0x%x reads 0x%x (masked: 0x%x), " + "expected to be on 0x%x (mask: 0x%x)\n", + t->txt, t->par_off, res, res & t->par_mask, + t->par_val, t->par_mask); + return -EINVAL; + } + + /* It's ok if parent clock is off and clock is off as well */ + if (!parent_enabled && t->val == 0) + return 0; + + return read_raw(t); +} + +static int clk_read_prcmu(struct pwr_test *t) +{ + u32 res; + int i; + + for (i = 0 ; i < 50; i++) { + res = prcmu_read(t->off); + if ((res & t->mask) == t->val) + return 0; + msleep(10); + } + + pr_err("pwr_test: ERROR: %s PRCMU, 0x%x reads 0x%x (masked: 0x%x), " + "expected 0x%x (mask: 0x%x)\n", + t->txt, t->off, res, res & t->mask, t->val, t->mask); + return -EINVAL; +} + +static int clk_read_abb_sys(struct pwr_test *t) +{ + int ret; + u8 val = 0; + int i; + + for (i = 0; i < 50; i++) { + ret = ab8500_sysctrl_read(t->off, &val); + if (ret < 0) { + pr_err("pwr_test: AB8500 access error: %d of " + "reg: 0x%x\n", ret, t->off); + return ret; + } + if ((val & (u8)t->mask) == t->val) + return 0; + msleep(10); + } + + pr_err("pwr_test: ERROR: AB8500 register 0x%x %s, reads 0x%x," + " (masked 0x%x) expected 0x%x (mask: 0x%x)\n", + t->base, t->txt, val, val & (u8)t->mask, t->val, t->mask); + + return -EINVAL; +} + +static int reg_read_db8500(struct pwr_test *t) +{ + int i, j; + + for (j = 0 ; j < u8500_reg.db8500_num ; j++) + if (u8500_reg.db8500_reg[j].desc.id == t->off) + break; + + if (j == u8500_reg.db8500_num) { + pr_err("pwr_test: Invalid db8500 regulator\n"); + return -EINVAL; + } + + for (i = 0; i < 50; i++) { + + /* powerstate is a special case */ + if (t->off == DB8500_REGULATOR_VAPE) { + if ((u8500_reg.db8500_reg[j].is_enabled || + power_state_active_is_enabled()) == t->val) + return 0; + } else { + if (u8500_reg.db8500_reg[j].is_enabled == t->val) + return 0; + } + msleep(10); + } + + pr_err("pwr_test: ERROR: DB8500 regulator %s is 0x%x expected 0x%x\n", + u8500_reg.db8500_reg[j].desc.name, + t->off != DB8500_REGULATOR_VAPE ? + u8500_reg.db8500_reg[j].is_enabled : + u8500_reg.db8500_reg[j].is_enabled || + power_state_active_is_enabled(), + t->val); + + return -EINVAL; +} + +static int reg_read_ab8500(struct pwr_test *t) +{ + int i, j; + int ret; + struct ab8500_debug_regulator_status s; + + for (i = 0; i < 50; i++) { + ret = ab8500_regulator_debug_read(t->off, &s); + + if (ret) { + pr_err("pwr_test: Fail to read ab8500 regulator\n"); + return -EINVAL; + } + + if (t->mode != s.mode) + goto repeat; + + if (t->hwmode != s.hwmode) + goto repeat; + + if (t->volt_selected != s.volt_selected && + t->alt_volt_selected != s.volt_selected) + goto repeat; + + if (t->volt_len != s.volt_len) + goto repeat; + + for (j = 0; j < 4 && s.hwmode != AB8500_HWMODE_NONE; j++) + if (s.hwmode_auto[j] != t->hwmode_auto[j]) + goto repeat; + + for (j = 0; j < s.volt_len; j++) + if (s.volt[j] != t->volt[j] && + s.volt[j] != t->alt_volt[j]) + goto repeat; + return 0; + +repeat: + msleep(10); + } + + pr_err("pwr test: ERROR: AB8500 regulator %s ", s.name); + + if (t->mode != s.mode) + printk("mode is: %d expected: %d ", + s.mode, t->mode); + + if (t->hwmode != s.hwmode) + printk("hwmode is: %d expected: %d ", + s.hwmode, t->hwmode); + + if (t->volt_selected != s.volt_selected && + t->alt_volt_selected != s.volt_selected) { + printk("volt selected is: %d expected: %d ", + s.volt_selected, t->volt_selected); + if (t->alt_volt_selected) + printk("(alt volt: %d) ", t->alt_volt_selected); + } + + if (t->volt_len != s.volt_len) + printk("volt len is: %d expected: %d ", + s.volt_len, t->volt_len); + + for (j = 0; j < 4; j++) + if (s.hwmode_auto[j] != t->hwmode_auto[j]) + break; + if (j != 4 && s.hwmode != AB8500_HWMODE_NONE) { + + printk("hwmode auto:: {"); + for (j = 0; j < 4; j++) { + switch(s.hwmode_auto[j]) { + case HWM_OFF: { printk("OFF "); break; } + case HWM_ON: { printk("ON "); break; } + case HWM_INVAL: { printk("INVAL "); break; } + } + } + printk("}, expected: {"); + for (j = 0; j < 4; j++) { + switch(t->hwmode_auto[j]) { + case HWM_OFF: { printk("OFF "); break; } + case HWM_ON: { printk("ON "); break; } + case HWM_INVAL: { printk("INVAL "); break; } + } + } + printk("} "); + } + + if (s.volt_len == t->volt_len) { + for (j = 0; j < s.volt_len; j++) + if (s.volt[j] != t->volt[j] && + t->alt_volt[j] != s.volt[j]) + break; + + } else { + j = 0; + } + + if (j != s.volt_len) { + printk("voltage: {"); + for (j = 0; j < s.volt_len; j++) + printk("%d ", s.volt[j]); + + if (t->alt_volt) { + printk("} alt voltage: {"); + for (j = 0; j < s.volt_len; j++) + printk("%d ", t->alt_volt[j]); + } + printk("} expected: {"); + for (j = 0; j < t->volt_len; j++) + printk("%d ", t->volt[j]); + printk("} "); + } + + printk("\n"); + + return -EINVAL; +} + + +static int test_execute(struct pwr_test *t, int len) +{ + int err = 0; + int i; + + for (i = 0 ; i < len ; i++) { + switch (t[i].type) { + case CLK_ABB_SYS: + err |= clk_read_abb_sys(&t[i]); + break; + case CLK_PRCMU: + err |= clk_read_prcmu(&t[i]); + break; + case CLK_PAR_PRCMU: + err |= clk_read_par_prcmu(&t[i]); + break; + case RAW: + err |= read_raw(&t[i]); + break; + case REG_DB8500: + err |= reg_read_db8500(&t[i]); + break; + case REG_AB8500: + err |= reg_read_ab8500(&t[i]); + break; + default: + break; + } + + } + return err; +} + +static int pwr_test_idle(struct seq_file *s, void *data) +{ + int err; + + err = test_execute(idle_test, ARRAY_SIZE(idle_test)); + + seq_printf(s, "%s\n", err == 0 ? "PASS" : "FAIL"); + + return 0; +} + + +int dbx500_regulator_testcase(struct dbx500_regulator_info *regulator_info, + int num_regulators) +{ + u8500_reg.db8500_reg = regulator_info; + u8500_reg.db8500_num = num_regulators; + return 0; +} + +static int pwr_test_debugfs_open(struct inode *inode, + struct file *file) +{ + return single_open(file, + inode->i_private, + NULL); +} + +static const struct file_operations pwr_test_debugfs_ops = { + .open = pwr_test_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *debugfs_dir; + +static int __init pwr_test_init(void) +{ + int err = 0; + void *err_ptr; + + debugfs_dir = debugfs_create_dir("pwr_test", NULL); + if (IS_ERR(debugfs_dir)) + return PTR_ERR(debugfs_dir); + + err_ptr = debugfs_create_file("idle", + S_IFREG | S_IRUGO, + debugfs_dir, (void *)pwr_test_idle, + &pwr_test_debugfs_ops); + if (IS_ERR(err_ptr)) { + err = PTR_ERR(err_ptr); + goto out; + } + return 0; +out: + debugfs_remove_recursive(debugfs_dir); + return err; +} +late_initcall(pwr_test_init); diff --git a/arch/arm/mach-ux500/timer.c b/arch/arm/mach-ux500/timer.c index e69ca9b50a2..438c9715aab 100644 --- a/arch/arm/mach-ux500/timer.c +++ b/arch/arm/mach-ux500/timer.c @@ -7,6 +7,7 @@ #include <linux/io.h> #include <linux/errno.h> #include <linux/clksrc-dbx500-prcmu.h> +#include <linux/clksrc-db5500-mtimer.h> #include <linux/of.h> #include <asm/smp_twd.h> @@ -85,12 +86,18 @@ static void __init ux500_timer_init(void) * depending on delay which is not yet calibrated. RTC-RTT is in the * always-on powerdomain and is used as clockevent instead of twd when * sleeping. - * The PRCMU timer 4(3 for DB5500) register a clocksource and - * sched_clock with higher rating then MTU since is always-on. * + * The PRCMU timer 4 (3 for DB5500) registers a clocksource and + * sched_clock with higher rating than the MTU since it is + * always-on. + * + * On DB5500, the MTIMER is the best clocksource since, unlike the + * PRCMU timer, it doesn't occasionally go backwards. */ nmdk_timer_init(mtu_timer_base); + if (cpu_is_u5500()) + db5500_mtimer_init(__io_address(U5500_MTIMER_BASE)); clksrc_dbx500_prcmu_init(prcmu_timer_base); ux500_twd_init(); diff --git a/arch/arm/mach-ux500/uart-db8500.c b/arch/arm/mach-ux500/uart-db8500.c new file mode 100644 index 00000000000..fad9b9a13df --- /dev/null +++ b/arch/arm/mach-ux500/uart-db8500.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Arun R Murthy <arun.murthy@stericsson.com>, + * Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/amba/serial.h> +#include <mach/setup.h> +#include <mach/hardware.h> +#include <mach/context.h> + +#ifdef CONFIG_UX500_CONTEXT + +static struct { + struct clk *uart_clk; + void __iomem *base; + /* dr */ + /* rsr_err */ + u32 dma_wm; + u32 timeout; + /* fr */ + u32 lcrh_rx; + u32 ilpr; + u32 ibrd; + u32 fbrd; + u32 lcrh_tx; + u32 cr; + u32 ifls; + u32 imsc; + /* ris */ + /* mis */ + /* icr */ + u32 dmacr; + u32 xfcr; + u32 xon1; + u32 xon2; + u32 xoff1; + u32 xoff2; + /* itcr */ + /* itip */ + /* itop */ + /* tdr */ + u32 abcr; + /* absr */ + /* abfmt */ + /* abdr */ + /* abdfr */ + /* abmr */ + u32 abimsc; + /* abris */ + /* abmis */ + /* abicr */ + /* id_product_h_xy */ + /* id_provider */ + /* periphid0 */ + /* periphid1 */ + /* periphid2 */ + /* periphid3 */ + /* pcellid0 */ + /* pcellid1 */ + /* pcellid2 */ + /* pcellid3 */ +} context_uart; + +static void save_uart(void) +{ + void __iomem *membase; + + membase = context_uart.base; + + clk_enable(context_uart.uart_clk); + + context_uart.dma_wm = readl_relaxed(membase + ST_UART011_DMAWM); + context_uart.timeout = readl_relaxed(membase + ST_UART011_TIMEOUT); + context_uart.lcrh_rx = readl_relaxed(membase + ST_UART011_LCRH_RX); + context_uart.ilpr = readl_relaxed(membase + UART01x_ILPR); + context_uart.ibrd = readl_relaxed(membase + UART011_IBRD); + context_uart.fbrd = readl_relaxed(membase + UART011_FBRD); + context_uart.lcrh_tx = readl_relaxed(membase + ST_UART011_LCRH_TX); + context_uart.cr = readl_relaxed(membase + UART011_CR); + context_uart.ifls = readl_relaxed(membase + UART011_IFLS); + context_uart.imsc = readl_relaxed(membase + UART011_IMSC); + context_uart.dmacr = readl_relaxed(membase + UART011_DMACR); + context_uart.xfcr = readl_relaxed(membase + ST_UART011_XFCR); + context_uart.xon1 = readl_relaxed(membase + ST_UART011_XON1); + context_uart.xon2 = readl_relaxed(membase + ST_UART011_XON2); + context_uart.xoff1 = readl_relaxed(membase + ST_UART011_XOFF1); + context_uart.xoff2 = readl_relaxed(membase + ST_UART011_XOFF2); + context_uart.abcr = readl_relaxed(membase + ST_UART011_ABCR); + context_uart.abimsc = readl_relaxed(membase + ST_UART011_ABIMSC); + + clk_disable(context_uart.uart_clk); +} + +static void restore_uart(void) +{ + int cnt; + int retries = 100; + unsigned int cr; + void __iomem *membase; + u16 dummy; + bool show_warn = false; + + membase = context_uart.base; + clk_enable(context_uart.uart_clk); + + writew_relaxed(context_uart.ifls, membase + UART011_IFLS); + cr = UART01x_CR_UARTEN | UART011_CR_TXE | UART011_CR_LBE; + + writew_relaxed(cr, membase + UART011_CR); + writew_relaxed(0, membase + UART011_FBRD); + writew_relaxed(1, membase + UART011_IBRD); + writew_relaxed(0, membase + ST_UART011_LCRH_RX); + if (context_uart.lcrh_tx != ST_UART011_LCRH_RX) { + int i; + /* + * Wait 10 PCLKs before writing LCRH_TX register, + * to get this delay write read only register 10 times + */ + for (i = 0; i < 10; ++i) + dummy = readw(membase + ST_UART011_LCRH_RX); + writew_relaxed(0, membase + ST_UART011_LCRH_TX); + } + writew(0, membase + UART01x_DR); + do { + if (!(readw(membase + UART01x_FR) & UART01x_FR_BUSY)) + break; + cpu_relax(); + } while (retries-- > 0); + if (retries < 0) + /* + * We can't print out a warning here since the uart is + * not fully restored. Do it later. + */ + show_warn = true; + + writel_relaxed(context_uart.dma_wm, membase + ST_UART011_DMAWM); + writel_relaxed(context_uart.timeout, membase + ST_UART011_TIMEOUT); + writel_relaxed(context_uart.lcrh_rx, membase + ST_UART011_LCRH_RX); + writel_relaxed(context_uart.ilpr, membase + UART01x_ILPR); + writel_relaxed(context_uart.ibrd, membase + UART011_IBRD); + writel_relaxed(context_uart.fbrd, membase + UART011_FBRD); + /* + * Wait 10 PCLKs before writing LCRH_TX register, + * to get this delay write read only register 10-3 + * times, as already there are 3 writes after + * ST_UART011_LCRH_RX + */ + for (cnt = 0; cnt < 7; cnt++) + dummy = readw(membase + ST_UART011_LCRH_RX); + + writel_relaxed(context_uart.lcrh_tx, membase + ST_UART011_LCRH_TX); + writel_relaxed(context_uart.ifls, membase + UART011_IFLS); + writel_relaxed(context_uart.dmacr, membase + UART011_DMACR); + writel_relaxed(context_uart.xfcr, membase + ST_UART011_XFCR); + writel_relaxed(context_uart.xon1, membase + ST_UART011_XON1); + writel_relaxed(context_uart.xon2, membase + ST_UART011_XON2); + writel_relaxed(context_uart.xoff1, membase + ST_UART011_XOFF1); + writel_relaxed(context_uart.xoff2, membase + ST_UART011_XOFF2); + writel_relaxed(context_uart.abcr, membase + ST_UART011_ABCR); + writel_relaxed(context_uart.abimsc, membase + ST_UART011_ABIMSC); + writel_relaxed(context_uart.cr, membase + UART011_CR); + writel(context_uart.imsc, membase + UART011_IMSC); + + clk_disable(context_uart.uart_clk); + + if (show_warn) + pr_warning("%s:uart tx busy\n", __func__); +} + +static int uart_context_notifier_call(struct notifier_block *this, + unsigned long event, void *data) +{ + switch (event) { + case CONTEXT_APE_SAVE: + save_uart(); + break; + + case CONTEXT_APE_RESTORE: + restore_uart(); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block uart_context_notifier = { + .notifier_call = uart_context_notifier_call, +}; + +#define __UART_BASE(soc, x) soc##_UART##x##_BASE +#define UART_BASE(soc, x) __UART_BASE(soc, x) + +static int __init uart_context_notifier_init(void) +{ + unsigned long base; + static const char clkname[] __initconst + = "uart" __stringify(CONFIG_UX500_DEBUG_UART); + + if (cpu_is_u8500()) + base = UART_BASE(U8500, CONFIG_UX500_DEBUG_UART); + else if (cpu_is_u5500()) + base = UART_BASE(U5500, CONFIG_UX500_DEBUG_UART); + else + ux500_unknown_soc(); + + context_uart.base = ioremap(base, SZ_4K); + context_uart.uart_clk = clk_get_sys(clkname, NULL); + + if (IS_ERR(context_uart.uart_clk)) { + pr_err("%s:unable to get clk-uart%d\n", __func__, + CONFIG_UX500_DEBUG_UART); + return -EINVAL; + } + + return WARN_ON(context_ape_notifier_register(&uart_context_notifier)); +} +arch_initcall(uart_context_notifier_init); +#endif |