/* * Copyright (C) 2019 Allwinner. * weidonghui * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include #include /* * Global data (for the gd->bd) */ DECLARE_GLOBAL_DATA_PTR; int set_gpio_bias(void) { char bias_name[32]; int bias_vol; int old_bias = 0, new_bias = 0; int nodeoffset = -1, offset; const struct fdt_property *prop; const char *pname; const int *pdata; nodeoffset = fdt_path_offset(working_fdt, FDT_PATH_GPIO_BIAS); for (offset = fdt_first_property_offset(working_fdt, nodeoffset); offset > 0; offset = fdt_next_property_offset(working_fdt, offset)) { prop = fdt_get_property_by_offset(working_fdt, offset, NULL); pname = fdt_string(working_fdt, fdt32_to_cpu(prop->nameoff)); pdata = (const int *)prop->data; bias_vol = fdt32_to_cpu(pdata[0]); memset(bias_name, 0, sizeof(bias_name)); strcpy(bias_name, pname); if (strstr((const char *)bias_name, "bias") == NULL) { continue; } printf("bias_name:%s\t bias_vol:%d\n", bias_name, bias_vol); if (bias_name[1] == 'l') { old_bias = readl(SUNXI_R_PIO_BASE + 0x340); new_bias = old_bias; if (bias_vol <= 1800) new_bias |= (1 << 0); else new_bias &= ~(1 << 0); if (old_bias != new_bias) writel(new_bias, SUNXI_R_PIO_BASE + 0x340); } else if ((bias_name[1] <= 'j') && (bias_name[1] >= 'a')) { old_bias = readl(SUNXI_PIO_BASE + 0x340); new_bias = old_bias; if (bias_vol <= 1800) new_bias |= (1 << (bias_name[1] - 'a')); else new_bias &= ~(1 << (bias_name[1] - 'a')); if (old_bias != new_bias) writel(new_bias, SUNXI_PIO_BASE + 0x340); } } return 0; } int axp_set_power_supply_output(void) { int onoff, val = 0, ret = 0; int power_delay = 0; char delay_name[32]; char power_name[32]; int power_vol; int power_vol_d; int nodeoffset = -1, nodeoffset2 = -1, offset; int twi_nodeoffset = -1; int pmu_nodeoffset = -1; const struct fdt_property *prop; const char *pname; const int *pdata; const char *twi_regu_name; char pmu_node_str[8] = {0}; char twi_path[128] = {0}; #ifdef CONFIG_SUNXI_TRY_POWER_SPLY char axp_name[16] = {0}, chipid; char axp_sply_path[64] = {0}; pmu_get_info(axp_name, (unsigned char *)&chipid); sprintf(axp_sply_path, "/soc/%s_power_sply", axp_name); nodeoffset = fdt_path_offset(working_fdt, axp_sply_path); #endif if (nodeoffset < 0) { nodeoffset = fdt_path_offset(working_fdt, FDT_PATH_POWER_SPLY); } nodeoffset2 = fdt_path_offset(working_fdt, FDT_PATH_GPIO_BIAS); if (nodeoffset2 < 0) { pr_msg("%s get gpio bias information fail!\n", __func__); } char sply_node[32], bias_node[32]; int reg_base, pin_base; int i = 0, ret1, ret2; struct pin_bias_t { const char *pin_name; char *supply_name; int gpio_bias; } pin_bias[] = { {"pc"}, {"pl"}, }; /* Get pmu node and its parent twi node*/ sprintf(pmu_node_str, "pmu0"); pmu_nodeoffset = fdt_path_offset(working_fdt, pmu_node_str); twi_nodeoffset = fdt_parent_offset(working_fdt, pmu_nodeoffset); fdt_get_path(working_fdt, twi_nodeoffset, twi_path, sizeof(twi_path)); /* Get the regulator for pmu's twi, for changing pinctrl when change voltage*/ twi_regu_name = fdt_get_regulator_name(twi_nodeoffset, "twi-for-pmu"); /* For change GPIO[x] bias when gpio voltage is changed */ for (i = 0; i < sizeof(pin_bias)/sizeof(pin_bias[0]); i++) { sprintf(sply_node, "%s_supply", pin_bias[i].pin_name); sprintf(bias_node, "%s_bias", pin_bias[i].pin_name); ret1 = fdt_getprop_string(working_fdt, nodeoffset2, sply_node, (char **)(&(pin_bias[i].supply_name))); ret2 = script_parser_fetch(FDT_PATH_GPIO_BIAS, bias_node, &(pin_bias[i].gpio_bias), 0); pr_msg("gpio_bias, %s: %4d, %s: %-9s\n", bias_node, ret2 < 0 ? -1 : pin_bias[i].gpio_bias, sply_node, ret1 < 0 ? "not set" : pin_bias[i].supply_name); }; for (offset = fdt_first_property_offset(working_fdt, nodeoffset); offset > 0; offset = fdt_next_property_offset(working_fdt, offset)) { prop = fdt_get_property_by_offset(working_fdt, offset, NULL); pname = fdt_string(working_fdt, fdt32_to_cpu(prop->nameoff)); pdata = (const int *)prop->data; power_vol = fdt32_to_cpu(pdata[0]); memset(power_name, 0, sizeof(power_name)); strcpy(power_name, pname); if ((strstr((const char *)power_name, "vol") == NULL)) { continue; } onoff = -1; power_vol_d = 0; if (power_vol > 10000) { onoff = 1; power_vol_d = power_vol % 10000; } else if (power_vol >= 0) { onoff = 0; power_vol_d = power_vol; } debug("%s = %d, onoff=%d\n", power_name, power_vol_d, onoff); if (pmu_set_voltage(power_name, power_vol_d, onoff)) { debug("axp set %s to %d failed\n", power_name, power_vol_d); } /*set delay for each output*/ sprintf(delay_name, "%s_delay", power_name); ret = script_parser_fetch(FDT_PATH_POWER_DELAY, delay_name, &power_delay, 0); if (ret < 0) power_delay = 0; if (power_delay != 0) { pr_msg("%s need to wait stable!\n", power_name); /* change twi pinctrl to shorten delay time */ if (!strncmp(twi_regu_name, power_name, strlen(twi_regu_name))) { fdt_set_all_pin(twi_path, "pinctrl-1"); pr_msg("%s has set pinctrl-1\n", twi_path); } mdelay(power_delay / 1000); if (!strncmp(twi_regu_name, power_name, strlen(twi_regu_name))) { fdt_set_all_pin(twi_path, "pinctrl-0"); pr_msg("%s has set pinctrl-0\n", twi_path); } } for (i = 0; i < sizeof(pin_bias)/sizeof(pin_bias[0]); i++) { if (!strncmp(pin_bias[i].supply_name, power_name, sizeof(power_name))) { if (pin_bias[i].gpio_bias == 0) pin_bias[i].gpio_bias = power_vol_d; if (pin_bias[i].pin_name[1] >= 'l') { reg_base = SUNXI_R_PIO_BASE; pin_base = 'l'; } else { reg_base = SUNXI_PIO_BASE; pin_base = 'a'; } val = readl(reg_base + 0x340); if (pin_bias[i].gpio_bias <= 1800) val |= (1 << (pin_bias[i].pin_name[1] - pin_base)); else val &= ~(1 << (pin_bias[i].pin_name[1] - pin_base)); writel(val, reg_base + 0x340); pr_msg("GPIO%c change bias done!\n", pin_bias[i].pin_name[1] - 'a' + 'A'); } } pr_msg("%s = %d, onoff=%d\n", power_name, pmu_get_voltage(power_name), onoff); } #ifndef CONFIG_GPIO_BIAS_SKIP set_gpio_bias(); #endif return 0; } int axp_set_charge_vol_limit(char *dev) { int limit_vol = 0; if (strstr(dev, "vol") == NULL) { debug("Illegal string"); return -1; } if (script_parser_fetch(FDT_PATH_CHARGER0, dev, &limit_vol, 1)) { return -1; } pmu_set_bus_vol_limit(limit_vol); return 0; } int axp_set_current_limit(char *dev) { int limit_cur = 0; if (strstr(dev, "cur") == NULL) { debug("Illegal string"); return -1; } if (script_parser_fetch(FDT_PATH_CHARGER0, dev, &limit_cur, 1)) { return -1; } if (!strncmp(dev, "pmu_runtime_chgcur", sizeof("pmu_runtime_chgcur")) || !strncmp(dev, "pmu_suspend_chgcur", sizeof("pmu_suspend_chgcur"))) { bmu_set_charge_current_limit(limit_cur); } else { bmu_set_vbus_current_limit(limit_cur); } return 0; } int axp_get_battery_status(void) { int dcin_exist, bat_vol; int ratio; int safe_vol = 0; dcin_exist = bmu_get_axp_bus_exist(); bat_vol = bmu_get_battery_vol(); script_parser_fetch(FDT_PATH_CHARGER0, "pmu_safe_vol", &safe_vol, -1); ratio = bmu_get_battery_capacity(); pr_msg("bat_vol=%d, ratio=%d\n", bat_vol, ratio); if (ratio < 1) { if (dcin_exist) { return BATTERY_RATIO_TOO_LOW_WITH_DCIN; } return BATTERY_RATIO_TOO_LOW_WITHOUT_DCIN; } if (bat_vol < safe_vol) { return BATTERY_VOL_TOO_LOW; } return BATTERY_RATIO_ENOUGH; } int sunxi_bat_low_vol_handle(void) { int i = 0, safe_vol = 0; int onoff = DISP_LCD_BACKLIGHT_ENABLE; __maybe_unused char arg[3] = {0}; int bat_vol = bmu_get_battery_vol(); int dcin_exist = bmu_get_axp_bus_exist(); int bat_ratio = bmu_get_battery_capacity(); script_parser_fetch(FDT_PATH_CHARGER0, "pmu_safe_vol", &safe_vol, -1); while (bat_vol < safe_vol || bat_ratio < 1) { bat_vol = bmu_get_battery_vol(); bat_ratio = bmu_get_battery_capacity(); dcin_exist = bmu_get_axp_bus_exist(); if (onoff == DISP_LCD_BACKLIGHT_ENABLE) { if (i++ >= 500) { i = 0; onoff = DISP_LCD_BACKLIGHT_DISABLE; pr_notice("onoff:DISP_LCD_BACKLIGHT_DISABLE\n"); pr_force("bat_ratio:%d%\tbat_vol:%dmV\tsafe_vol:%dmV\n", bat_ratio, bat_vol, safe_vol); #ifdef CONFIG_DISP2_SUNXI disp_ioctl(NULL, onoff, (void *)arg); #endif } } else { if (pmu_get_key_irq() > 0) { i = 0; onoff = DISP_LCD_BACKLIGHT_ENABLE; pr_notice("onoff:DISP_LCD_BACKLIGHT_ENABLE\n"); pr_force("bat_ratio:%d%\tbat_vol:%dmV\tsafe_vol:%dmV\n", bat_ratio, bat_vol, safe_vol); #ifdef CONFIG_DISP2_SUNXI disp_ioctl(NULL, onoff, (void *)arg); #endif } } if (!dcin_exist) { #ifdef CONFIG_CMD_SUNXI_BMP sunxi_bmp_display("bat\\low_pwr.bmp"); #endif tick_printf("battery ratio is low without dcin,to be shutdown\n"); mdelay(3000); sunxi_board_shutdown(); } if (ctrlc()) break; mdelay(10); } return 0; } /* reset bat capacity when system is writing firmware*/ int axp_reset_capacity(void) { return bmu_reset_capacity(); } /* set dcdc pwm mode */ int axp_set_dcdc_mode(void) { const struct fdt_property *prop; int nodeoffset = -1, offset, mode; const char *pname; const int *pdata; if (nodeoffset < 0) { nodeoffset = fdt_path_offset(working_fdt, FDT_PATH_POWER_SPLY); } for (offset = fdt_first_property_offset(working_fdt, nodeoffset); offset > 0; offset = fdt_next_property_offset(working_fdt, offset)) { prop = fdt_get_property_by_offset(working_fdt, offset, NULL); pname = fdt_string(working_fdt, fdt32_to_cpu(prop->nameoff)); pdata = (const int *)prop->data; mode = fdt32_to_cpu(pdata[0]); if (strstr(pname, "mode") == NULL) { continue; } if (pmu_set_dcdc_mode(pname, mode) < 0) { debug("set %s fail!\n", pname); } } return 0; } int axp_battery_status_handle(void) { int battery_status; int ret = 0, bat_exist = 0; ret = script_parser_fetch(FDT_PATH_POWER_SPLY, "battery_exist", &bat_exist, 1); if (ret < 0) bat_exist = 1; if (!bat_exist) return 0; #ifdef CONFIG_AXP_LATE_INFO battery_status = axp_get_battery_status(); #else battery_status = gd->pmu_runtime_chgcur; #endif if (gd->chargemode == 1) { if ((battery_status == BATTERY_RATIO_TOO_LOW_WITH_DCIN) || (battery_status == BATTERY_RATIO_TOO_LOW_WITHOUT_DCIN) || (battery_status == BATTERY_VOL_TOO_LOW)) { #ifdef CONFIG_CMD_SUNXI_BMP sunxi_bmp_display("bat\\bat0.bmp"); #endif #if 0 tick_printf("battery ratio is low with dcin,to be shutdown\n"); mdelay(3000); sunxi_board_shutdown(); #else sunxi_bat_low_vol_handle(); #endif } else { #ifdef CONFIG_CMD_SUNXI_BMP sunxi_bmp_display("bat\\battery_charge.bmp"); #endif } } else if ((battery_status == BATTERY_RATIO_TOO_LOW_WITHOUT_DCIN) || (battery_status == BATTERY_VOL_TOO_LOW)) { #ifdef CONFIG_CMD_SUNXI_BMP sunxi_bmp_display("bat\\low_pwr.bmp"); #endif tick_printf("battery ratio or vol is low ,to be shutdown\n"); mdelay(3000); sunxi_board_shutdown(); } return 0; } int axp_set_vol(char *name, uint onoff) { return pmu_set_voltage(name, 0, onoff); } int pmu_axp707_get_poweron_source(void) { int reg_value = 0; reg_value = pmu_get_reg_value(AXP81X_OTG_STATUS); printf("kickpi axp707 reason %x \n", reg_value); pmu_set_reg_value(AXP81X_OTG_STATUS, 0xff); printf("kickpi after clean reason %x \n", pmu_get_reg_value(AXP81X_OTG_STATUS)); if (reg_value & (1 << 0)) { return AXP_BOOT_SOURCE_BUTTON; } else if (reg_value & (1 << 1)) { return AXP_BOOT_SOURCE_CHARGER; } else if (reg_value & (1 << 2)) { return AXP_BOOT_SOURCE_BATTERY; } return -1; } int runtime_tick(void); int pmu_axp707_get_battery_probe(void) { int reg_value = 0; int old_time = runtime_tick(); while ((runtime_tick() - old_time) < 2000) { reg_value = pmu_get_reg_value(AXP81X_MODE_CHGSTATUS); } /*bit4 determines whether bit5 is valid*/ /*bit5 determines whether bat is exist*/ if ((reg_value & (1<<4)) && (reg_value & (1<<5))) return 1; return 0; } int sunxi_update_axp_info(void) { int val = -1; char bootreason[16] = {0}; int ret = 0, bat_exist = 0; int dcin_exist = pmu_get_reg_value(AXP81X_STATUS); ret = script_parser_fetch(FDT_PATH_POWER_SPLY, "battery_exist", &bat_exist, 1); if (ret < 0) { bat_exist = pmu_axp707_get_battery_probe(); printf("kickpi detect battery_exist = %d, AXP81X_STATUS = %x\n", bat_exist, dcin_exist); } else { printf("force set battery_exist = %d\n", bat_exist); } #ifdef CONFIG_SUNXI_BMU #ifdef CONFIG_AXP_LATE_INFO val = bmu_get_poweron_source(); #else val = gd->pmu_saved_status; #endif #endif printf("kickpi pmu_get_sys_mode = %d\n", pmu_get_sys_mode()); if ((val == -1) && (pmu_get_sys_mode() == SUNXI_CHARGING_FLAG)) { val = AXP_BOOT_SOURCE_CHARGER; pmu_set_sys_mode(0); } if (bat_exist == 1) { /* When the battery exists, it can only be turned on by power key */ if (pmu_axp707_get_poweron_source() != AXP_BOOT_SOURCE_BUTTON) { val = AXP_BOOT_SOURCE_CHARGER; pmu_set_sys_mode(0); printf("kickpi[bat_exist] not power key up when boot on \n"); } } switch (val) { case AXP_BOOT_SOURCE_BUTTON: strncpy(bootreason, "button", sizeof("button")); break; case AXP_BOOT_SOURCE_IRQ_LOW: strncpy(bootreason, "irq", sizeof("irq")); break; case AXP_BOOT_SOURCE_VBUS_USB: strncpy(bootreason, "usb", sizeof("usb")); break; case AXP_BOOT_SOURCE_CHARGER: strncpy(bootreason, "charger", sizeof("charger")); if (bat_exist) gd->chargemode = 1; break; case AXP_BOOT_SOURCE_BATTERY: strncpy(bootreason, "battery", sizeof("battery")); break; default: strncpy(bootreason, "unknow", sizeof("unknow")); break; } env_set("bootreason", bootreason); printf("kickpi bootreason = %s / val = %d \n", bootreason, val); return 0; } int do_sunxi_axp(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) { u8 reg_addr; u8 reg_value; if (argc < 4) return -1; reg_addr = (u8)simple_strtoul(argv[3], NULL, 16); if (!strncmp(argv[1], "pmu", 3)) { #ifdef CONFIG_SUNXI_PMU if (!strncmp(argv[2], "read", 4)) { printf("pmu_value:0x%x\n", pmu_get_reg_value(reg_addr)); } else if (!strncmp(argv[2], "write", 5)) { reg_value = (u8)simple_strtoul(argv[4], NULL, 16); printf("pmu_value:0x%x\n", pmu_set_reg_value(reg_addr, reg_value)); } else { printf("input error\n"); return -1; } #endif } else if (!strncmp(argv[1], "bmu", 3)) { #ifdef CONFIG_SUNXI_BMU if (!strncmp(argv[2], "read", 4)) { printf("bmu_value:0x%x\n", bmu_get_reg_value(reg_addr)); } else if (!strncmp(argv[2], "write", 5)) { reg_value = (u8)simple_strtoul(argv[4], NULL, 16); printf("bmu_value:0x%x\n", bmu_set_reg_value(reg_addr, reg_value)); } else { printf("input error\n"); return -1; } #endif } else { printf("input error\n"); return -1; } return 0; } U_BOOT_CMD(sunxi_axp, 6, 1, do_sunxi_axp, "sunxi_axp sub-system", "sunxi_axp \n" "sunxi_axp \n");