| #include <common.h> |
| #include <dm.h> |
| #include <efi.h> |
| #include <fs.h> |
| #include <i2c.h> |
| #include <led_aw2015.h> |
| #include <linux/printk.h> |
| #include <linux/ctype.h> |
| #include <vsprintf.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| // Copied from |
| // http://eureka-partner/I746d141b78040e0772a4c1f4111eb5afc12d70e6 |
| // Reads "led_calibration_LUT.txt" from the factory partition. |
| // Forwards errors, returns 0 on success. |
| int get_cal_string(char *buf, int len) |
| { |
| int ret; |
| loff_t len_read; |
| const loff_t seek = 21; |
| |
| ret = fs_set_blk_dev("mmc", "0:3", FS_TYPE_EXT); |
| if (ret) { |
| pr_err("LED: fs_set_blk_dev error=%d\n", ret); |
| return ret; |
| } |
| // Leave space for terminator |
| ret = fs_read("led_calibration_LUT.txt", (ulong) buf, seek, len - 1, |
| &len_read); |
| if (ret) { |
| pr_err("LED: fs_read error=%d\n", ret); |
| return ret; |
| } |
| // fs_read does not add a null terminator |
| buf[len_read] = '\0'; |
| |
| return 0; |
| } |
| |
| // Copied from |
| // http://eureka-partner/I746d141b78040e0772a4c1f4111eb5afc12d70e6 |
| // Gets calibration settings for the status LED: |
| // pwm_r, pwm_g, pwm_b, current_r, current_g, current_b |
| // On failure, |settings| is not modified. |
| int get_cal_settings(unsigned int *settings, const char color_header[]) |
| { |
| int i; |
| unsigned long settings_[N_CAL_SETTINGS]; |
| |
| // Max length to cover all color lines |
| // 4 colors * (color_name + ":4095:" + xxx[,|;|\n] * 6) |
| // 4 * (14 + 6 + 4 * 6 ) |
| const int max_chars = 176; |
| // Add one for null terminator |
| char buf[max_chars + 1]; |
| int ret; |
| |
| ret = get_cal_string(buf, max_chars); |
| if (ret != 0) { |
| pr_err("LED: failed to get_cal_string\n"); |
| return ret; |
| } |
| |
| char *p, *p_next; |
| |
| p = strstr(buf, color_header); |
| if (!p) { |
| pr_err("LED: \"%s\" not found in cal file segment: %s\n", |
| color_header, buf); |
| return -1; |
| } |
| p += strlen(color_header); |
| |
| for (i = 0; i < N_CAL_SETTINGS; ++i) { |
| unsigned long value = simple_strtoul(p, &p_next, 10); |
| |
| if (value > 255) { |
| pr_err("LED: value %ld is too large\n", |
| settings_[i]); |
| return -1; |
| } |
| settings_[i] = value; |
| |
| // Skip check that |p_next| is valid on last iteration |
| if (i == 5) |
| break; |
| // Is there not a separator, or not another number |
| if ((*p_next != ',' && *p_next != ';') || |
| !isdigit(p_next[1])) { |
| // Is this the first current |
| if (i == 3) { |
| // One current instead of RGB current |
| settings_[4] = value; |
| settings_[5] = value; |
| break; |
| } |
| // Invalid |
| pr_err("LED calibration file is malformed\n"); |
| return -1; |
| } |
| |
| p = p_next + 1; |
| } |
| |
| for (i = 0; i < N_CAL_SETTINGS; ++i) |
| settings[i] = (unsigned int)settings_[i]; |
| |
| return 0; |
| } |
| |
| void reg_write(struct udevice *led_devp, int reg, int mask, int val) { |
| int old_val; |
| int new_val; |
| |
| old_val = dm_i2c_reg_read(led_devp, reg); |
| if (old_val < 0) { |
| pr_err("LED: read %d reg failed\n", reg); |
| return; |
| } |
| new_val = (old_val & ~mask) | (val & mask); |
| dm_i2c_reg_write(led_devp, reg, new_val); |
| } |
| |
| void turn_on_white_led(struct udevice *led_devp) { |
| /* default calibration settings */ |
| unsigned int cal_settings[N_CAL_SETTINGS] = |
| {128, 128, 128, 100, 100, 100}; |
| int i; |
| |
| /* TODO(b/195092280 uncomment once the calibration file is ready) */ |
| if (get_cal_settings(cal_settings, "White:4095:")) { |
| pr_err("LED: failed to get_cal_settings for white led\n"); |
| return; |
| } |
| for(i = 0; i < 3; ++i) { |
| reg_write(led_devp, AW2015_REG_LCFG1 + i, AW2015_LED_LEDMD_MASK, |
| AW2015_LED_ON_MODE); |
| reg_write(led_devp, AW2015_REG_PWM1 + i, AW2015_LED_PWM_MASK, |
| cal_settings[i]); |
| reg_write(led_devp, AW2015_REG_ILED1 + i, AW2015_LED_ILED_MASK, |
| cal_settings[3+i]); |
| } |
| } |
| |
| void enable_led_chip(void) |
| { |
| int gpio_node; |
| int led_enable_gpio; |
| pr_info("%s\n", __func__); |
| |
| /* pull high the LED enabling ping */ |
| gpio_node = fdt_path_offset(gd->fdt_blob, "/led_enable_gpio"); |
| if (gpio_node >= 0) { |
| qca_gpio_init(gpio_node); |
| led_enable_gpio = fdtdec_get_int(gd->fdt_blob, gpio_node, "led_enable_gpio", 0); |
| if (led_enable_gpio >= 0) { |
| gpio_set_value(led_enable_gpio, GPIO_OUT_HIGH); |
| pr_info("enabled LED pin %d\n", led_enable_gpio) ; |
| } |
| } |
| } |
| |
| void sys_led_init(enum LED_ANIMATION led_animation) |
| { |
| int ret; |
| struct udevice *bus, *dev; |
| |
| enable_led_chip(); |
| |
| ret = uclass_get_device_by_seq(UCLASS_I2C, BUS_NUM, &bus); |
| if (ret) { |
| pr_err("LED: i2c get bus failed\n"); |
| return; |
| } |
| ret = dm_i2c_probe(bus, I2C_LED_REG, 0, &dev); |
| if (ret) { |
| pr_err("LED: dm_i2c_probe failed\n"); |
| return; |
| } |
| |
| reg_write(dev, AW2015_REG_RSTIDR, AW2015_LED_RSTIDR_MASK, |
| AW2015_LED_RSTIDR_RESET); |
| reg_write(dev, AW2015_REG_GCR, AW2015_LED_CHIPEN_MASK, |
| AW2015_LED_CHIP_ENABLE); |
| reg_write(dev, AW2015_REG_GCR, AW2015_LED_CHARGE_DISABLE_MASK, |
| AW2015_LED_CHARGE_DISABLE); |
| reg_write(dev, AW2015_REG_IMAX, AW2015_LED_IMX_MASK, |
| AW2015_LED_12_75mA); |
| reg_write(dev, AW2015_REG_LEDCTR, AW2015_LED_PWMLOG_MASK, |
| AW2015_LED_PWMLOG_LEANER); |
| /* disable LEDs to avoid red lighting first */ |
| reg_write(dev, AW2015_REG_LEDEN, AW2015_LED_LEDEN_MASK, |
| AW2015_LED_LEDEN_TURN_OFF_ALL); |
| |
| switch(led_animation) { |
| case WHITE: |
| turn_on_white_led(dev); |
| break; |
| default: |
| pr_err("LED: unknown led animation\n"); |
| } |
| /* enable 3 LEDs at once after all set */ |
| reg_write(dev, AW2015_REG_LEDEN, AW2015_LED_LEDEN_MASK, |
| AW2015_LED_LEDEN_TURN_ON_ALL); |
| } |