/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2021 MediaTek Inc. */ #include #include #include #include #include #include #include #include "inc/mt6370_pmu.h" #include "inc/mt6370_pmu_rgbled.h" #define MT6370_PMU_RGBLED_DRV_VERSION "1.0.1_MTK" enum { MT6370_PMU_LED_PWMMODE = 0, MT6370_PMU_LED_BREATHMODE, MT6370_PMU_LED_REGMODE, MT6370_PMU_LED_MAXMODE, }; struct mt6370_led_classdev { struct led_classdev led_dev; int led_index; }; struct mt6370_pmu_rgbled_data { struct mt6370_pmu_chip *chip; struct device *dev; struct delayed_work dwork; }; #define MT_LED_ATTR(_name) {\ .attr = {\ .name = #_name,\ .mode = 0644,\ },\ .show = mt_led_##_name##_attr_show,\ .store = mt_led_##_name##_attr_store,\ } #define MT_LED_ATTR2(_name) {\ .attr = {\ .name = #_name,\ .mode = 0644,\ },\ .show = mt_led_##_name##_attr_show2,\ .store = mt_led_##_name##_attr_store2,\ } static const u8 rgbled_init_data[] = { 0x60, /* MT6370_PMU_REG_RGB1DIM: 0x82 */ 0x60, /* MT6370_PMU_REG_RGB2DIM: 0x83 */ 0x60, /* MT6370_PMU_REG_RGB3DIM: 0x84 */ 0x0F, /* MT6370_PMU_REG_RGBEN: 0x85 */ 0x08, /* MT6370_PMU_REG_RGB1ISINK: 0x86 */ 0x08, /* MT6370_PMU_REG_RGB2ISINK: 0x87 */ 0x08, /* MT6370_PMU_REG_RGB3ISINK: 0x88 */ 0x52, /* MT6370_PMU_REG_RGB1TR: 0x89 */ 0x25, /* MT6370_PMU_REG_RGB1TF: 0x8A */ 0x11, /* MT6370_PMU_REG_RGB1TONTOFF: 0x8B */ 0x52, /* MT6370_PMU_REG_RGB2TR: 0x8C */ 0x25, /* MT6370_PMU_REG_RGB2TF: 0x8D */ 0x11, /* MT6370_PMU_REG_RGB2TONTOFF: 0x8E */ 0x52, /* MT6370_PMU_REG_RGB3TR: 0x8F */ 0x25, /* MT6370_PMU_REG_RGB3TF: 0x90 */ 0x11, /* MT6370_PMU_REG_RGB3TONTOFF: 0x91 */ 0x60, /* MT6370_PMU_REG_RGBCHRINDDIM: 0x92 */ 0x07, /* MT6370_PMU_REG_RGBCHRINDCTRL: 0x93 */ 0x52, /* MT6370_PMU_REG_RGBCHRINDTR: 0x94 */ 0x25, /* MT6370_PMU_REG_RGBCHRINDTF: 0x95 */ 0x11, /* MT6370_PMU_REG_RGBCHRINDTONTOFF: 0x96 */ 0xFF, /* MT6370_PMU_REG_RGBOPENSHORTEN: 0x97 */ }; static const u8 rgbled_init_data2[] = { 0x00, /* 0x82 */ 0xCA, /* 0x83 */ 0xCA, /* 0x84 */ 0xCA, /* 0x85 */ 0xCA, /* 0x86 */ 0x1F, /* 0x87 */ 0x1F, /* 0x88 */ 0x1F, /* 0x89 */ 0x1F, /* 0x8A */ 0x48, /* 0x8B */ 0x48, /* 0x8C */ 0x11, /* 0x8D */ 0x11, /* 0x8E */ 0x11, /* 0x8F */ 0x11, /* 0x90 */ 0x11, /* 0x91 */ 0x11, /* 0x92 */ 0x11, /* 0x93 */ 0x11, /* 0x94 */ 0x11, /* 0x95 */ 0x11, /* 0x96 */ 0x11, /* 0x97 */ 0x11, /* 0x98 */ }; static inline int mt6370_pmu_led_get_index(struct led_classdev *led_cdev) { struct mt6370_led_classdev *mt_led_cdev = (struct mt6370_led_classdev *)led_cdev; return mt_led_cdev->led_index; } static inline int mt6370_pmu_led_update_bits(struct led_classdev *led_cdev, uint8_t reg_addr, uint8_t reg_mask, uint8_t reg_data) { struct mt6370_pmu_rgbled_data *rgbled_data = dev_get_drvdata(led_cdev->dev->parent); return mt6370_pmu_reg_update_bits(rgbled_data->chip, reg_addr, reg_mask, reg_data); } static inline int mt6370_pmu_led_reg_read(struct led_classdev *led_cdev, uint8_t reg_addr) { struct mt6370_pmu_rgbled_data *rgbled_data = dev_get_drvdata(led_cdev->dev->parent); return mt6370_pmu_reg_read(rgbled_data->chip, reg_addr); } static inline void mt6370_pmu_led_enable_dwork(struct led_classdev *led_cdev) { struct mt6370_pmu_rgbled_data *rgbled_data = dev_get_drvdata(led_cdev->dev->parent); cancel_delayed_work_sync(&rgbled_data->dwork); schedule_delayed_work(&rgbled_data->dwork, msecs_to_jiffies(100)); } /* 6372 start */ static void mt6370_pmu_led_bright_set2(struct led_classdev *led_cdev, enum led_brightness bright) { int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_mask = 0xF, reg_shift = 0, en_mask = 0; bool need_enable_timer = true; int ret = 0; switch (led_index) { case MT6370_PMU_LED1: reg_addr = 0x83; en_mask = 0x80; break; case MT6370_PMU_LED2: reg_addr = 0x84; en_mask = 0x40; break; case MT6370_PMU_LED3: reg_addr = 0x85; en_mask = 0x20; break; case MT6370_PMU_LED4: reg_addr = 0x86; en_mask = 0x10; need_enable_timer = false; break; default: dev_err(led_cdev->dev, "invalid mt led index\n"); return; } ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, reg_mask, (bright & reg_mask) << reg_shift); if (ret < 0) { dev_err(led_cdev->dev, "update brightness fail\n"); return; } if (!bright) need_enable_timer = false; if (need_enable_timer) { mt6370_pmu_led_enable_dwork(led_cdev); return; } ret = mt6370_pmu_led_update_bits(led_cdev, 0x82, en_mask, (bright > 0) ? en_mask : ~en_mask); if (ret < 0) dev_err(led_cdev->dev, "update enable bit fail\n"); } static enum led_brightness mt6370_pmu_led_bright_get2( struct led_classdev *led_cdev) { int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_mask = 0xF, reg_shift = 0, en_mask = 0; bool need_enable_timer = true; int ret = 0; switch (led_index) { case MT6370_PMU_LED1: reg_addr = 0x83; en_mask = 0x80; break; case MT6370_PMU_LED2: reg_addr = 0x84; en_mask = 0x40; break; case MT6370_PMU_LED3: reg_addr = 0x85; en_mask = 0x20; break; case MT6370_PMU_LED4: reg_addr = 0x86; en_mask = 0x10; need_enable_timer = false; break; default: dev_err(led_cdev->dev, "invalid mt led index\n"); return -EINVAL; } ret = mt6370_pmu_led_reg_read(led_cdev, 0x82); if (ret < 0) return ret; if (!(ret & en_mask)) return LED_OFF; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; return (ret & reg_mask) >> reg_shift; } static inline int mt6370_pmu_led_config_pwm2(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { const ulong dim_time[] = { 8000, 4000, 2000, 1000, 500, 250, 8, 4}; const unsigned long ton = *delay_on, toff = *delay_off; int led_index = mt6370_pmu_led_get_index(led_cdev); int reg_addr, reg_mask, reg_shift; int i, j, ret = 0; dev_dbg(led_cdev->dev, "%s, on %lu, off %lu\n", __func__, ton, toff); /* find the close dim freq */ for (i = ARRAY_SIZE(dim_time) - 1; i >= 0; i--) { if (dim_time[i] >= (ton + toff)) break; } if (i < 0) { dev_warn(led_cdev->dev, "no match, sum %lu\n", ton + toff); i = 0; } /* write pwm dim freq selection */ switch (led_index) { case MT6370_PMU_LED1: reg_addr = 0x8B; reg_mask = 0xE0; reg_shift = 5; break; case MT6370_PMU_LED2: reg_addr = 0x8B; reg_mask = 0x1C; reg_shift = 2; break; case MT6370_PMU_LED3: reg_addr = 0x8C; reg_mask = 0xE0; reg_shift = 5; break; case MT6370_PMU_LED4: reg_addr = 0x8C; reg_mask = 0x1C; reg_shift = 2; break; default: return -EINVAL; } ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, reg_mask, i << reg_shift); if (ret < 0) return ret; /* find the closest pwm duty */ j = 256 * ton / (ton + toff); if (j == 0) j = 1; j--; switch (led_index) { case MT6370_PMU_LED1: reg_addr = 0x87; break; case MT6370_PMU_LED2: reg_addr = 0x88; break; case MT6370_PMU_LED3: reg_addr = 0x89; break; case MT6370_PMU_LED4: reg_addr = 0x8A; break; default: return -EINVAL; } reg_mask = 0xFF; reg_shift = 0; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, reg_mask, j << reg_shift); if (ret < 0) return ret; return 0; } static int mt6370_pmu_led_change_mode2(struct led_classdev *led_cdev, int mode); static int mt6370_pmu_led_blink_set2(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { int mode_sel = MT6370_PMU_LED_PWMMODE; int ret = 0; if (!*delay_on && !*delay_off) *delay_on = *delay_off = 500; if (!*delay_off) mode_sel = MT6370_PMU_LED_REGMODE; if (mode_sel == MT6370_PMU_LED_PWMMODE) { ret = mt6370_pmu_led_config_pwm2(led_cdev, delay_on, delay_off); if (ret < 0) dev_err(led_cdev->dev, "%s: cfg pwm fail\n", __func__); } ret = mt6370_pmu_led_change_mode2(led_cdev, mode_sel); if (ret < 0) dev_err(led_cdev->dev, "%s: change mode fail\n", __func__); return 0; } static struct mt6370_led_classdev mt6370_led_classdev2[MT6370_PMU_MAXLED] = { { .led_dev = { .max_brightness = 12, .brightness_set = mt6370_pmu_led_bright_set2, .brightness_get = mt6370_pmu_led_bright_get2, .blink_set = mt6370_pmu_led_blink_set2, }, .led_index = MT6370_PMU_LED1, }, { .led_dev = { .max_brightness = 12, .brightness_set = mt6370_pmu_led_bright_set2, .brightness_get = mt6370_pmu_led_bright_get2, .blink_set = mt6370_pmu_led_blink_set2, }, .led_index = MT6370_PMU_LED2, }, { .led_dev = { .max_brightness = 12, .brightness_set = mt6370_pmu_led_bright_set2, .brightness_get = mt6370_pmu_led_bright_get2, .blink_set = mt6370_pmu_led_blink_set2, }, .led_index = MT6370_PMU_LED3, }, { .led_dev = { .max_brightness = 12, .brightness_set = mt6370_pmu_led_bright_set2, .brightness_get = mt6370_pmu_led_bright_get2, .blink_set = mt6370_pmu_led_blink_set2, }, .led_index = MT6370_PMU_LED4, }, }; static int mt6370_pmu_led_change_mode2(struct led_classdev *led_cdev, int mode) { int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0; int ret = 0; if (mode >= MT6370_PMU_LED_MAXMODE) return -EINVAL; switch (led_index) { case MT6370_PMU_LED1: reg_addr = 0x83; break; case MT6370_PMU_LED2: reg_addr = 0x84; break; case MT6370_PMU_LED3: reg_addr = 0x85; break; case MT6370_PMU_LED4: /* disable auto mode */ ret = mt6370_pmu_led_update_bits(led_cdev, 0x82, 0x08, 0xFF); if (ret < 0) return ret; reg_addr = 0x86; break; default: return -EINVAL; } return mt6370_pmu_led_update_bits(led_cdev, reg_addr, 0xC0, mode << 6); } static const struct device_attribute mt_led_cc_mode_attrs2[] = { }; static void mt6370_pmu_led_cc_activate2(struct led_classdev *led_cdev) { int i = 0, ret = 0; for (i = 0; i < ARRAY_SIZE(mt_led_cc_mode_attrs2); i++) { ret = device_create_file(led_cdev->dev, mt_led_cc_mode_attrs2 + i); if (ret < 0) { dev_err(led_cdev->dev, "%s: create file fail %d\n", __func__, i); goto out_create_file; } } ret = mt6370_pmu_led_change_mode2(led_cdev, MT6370_PMU_LED_REGMODE); if (ret < 0) { dev_err(led_cdev->dev, "%s: change mode fail\n", __func__); goto out_change_mode; } return; out_change_mode: i = ARRAY_SIZE(mt_led_cc_mode_attrs2); out_create_file: while (--i >= 0) device_remove_file(led_cdev->dev, mt_led_cc_mode_attrs2 + i); } static void mt6370_pmu_led_cc_deactivate2(struct led_classdev *led_cdev) { int i = 0; for (i = 0; i < ARRAY_SIZE(mt_led_cc_mode_attrs2); i++) device_remove_file(led_cdev->dev, mt_led_cc_mode_attrs2 + i); } static ssize_t mt_led_pwm_duty_attr_show2(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0; int ret = 0; switch (led_index) { case MT6370_PMU_LED1: reg_addr = 0x87; break; case MT6370_PMU_LED2: reg_addr = 0x88; break; case MT6370_PMU_LED3: reg_addr = 0x89; break; case MT6370_PMU_LED4: reg_addr = 0x8A; break; default: return -EINVAL; } ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_addr = ret & 0xFF; return snprintf(buf, PAGE_SIZE, "%d (max: %d)\n", reg_addr, 255); } static ssize_t mt_led_pwm_duty_attr_store2(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > 255) return -EINVAL; switch (led_index) { case MT6370_PMU_LED1: reg_addr = 0x87; break; case MT6370_PMU_LED2: reg_addr = 0x88; break; case MT6370_PMU_LED3: reg_addr = 0x89; break; case MT6370_PMU_LED4: reg_addr = 0x8A; break; default: return -EINVAL; } reg_data = store << 0; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, 0xFF, reg_data); if (ret < 0) return ret; return cnt; } static const char * const led_dim_freq2[] = { "0.125Hz", "0.25Hz", "0.5Hz", "1Hz", "2Hz", "4Hz", "128Hz", "256Hz", }; static ssize_t mt_led_pwm_dim_freq_attr_show2(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_mask = 0, reg_shift = 0, reg_data = 0; unsigned long cnt = PAGE_SIZE; int i = 0, ret = 0; switch (led_index) { case MT6370_PMU_LED1: reg_addr = 0x8B; reg_mask = 0xE0; reg_shift = 5; break; case MT6370_PMU_LED2: reg_addr = 0x8B; reg_mask = 0x1C; reg_shift = 2; break; case MT6370_PMU_LED3: reg_addr = 0x8C; reg_mask = 0xE0; reg_shift = 5; break; case MT6370_PMU_LED4: reg_addr = 0x8C; reg_mask = 0x1C; reg_shift = 2; break; default: return -EINVAL; } ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & reg_mask) >> reg_shift; ret = 0; for (i = 0; i < ARRAY_SIZE(led_dim_freq2); i++) { if (reg_data == i) ret += snprintf(buf + ret, cnt - ret, ">"); ret += snprintf(buf + ret, cnt - ret, "%s ", led_dim_freq2[i]); } ret += snprintf(buf + ret, cnt - ret, "\n"); return ret; } static ssize_t mt_led_pwm_dim_freq_attr_store2(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_mask = 0, reg_shift = 0, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store >= ARRAY_SIZE(led_dim_freq2)) return -EINVAL; switch (led_index) { case MT6370_PMU_LED1: reg_addr = 0x8B; reg_mask = 0xE0; reg_shift = 5; break; case MT6370_PMU_LED2: reg_addr = 0x8B; reg_mask = 0x1C; reg_shift = 2; break; case MT6370_PMU_LED3: reg_addr = 0x8C; reg_mask = 0xE0; reg_shift = 5; break; case MT6370_PMU_LED4: reg_addr = 0x8C; reg_mask = 0x1C; reg_shift = 2; break; default: return -EINVAL; } reg_data = store << reg_shift; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, reg_mask, reg_data); if (ret < 0) return ret; return cnt; } static const struct device_attribute mt_led_pwm_mode_attrs2[] = { MT_LED_ATTR2(pwm_duty), MT_LED_ATTR2(pwm_dim_freq), }; static void mt6370_pmu_led_pwm_activate2(struct led_classdev *led_cdev) { int i = 0, ret = 0; for (i = 0; i < ARRAY_SIZE(mt_led_pwm_mode_attrs2); i++) { ret = device_create_file(led_cdev->dev, mt_led_pwm_mode_attrs2 + i); if (ret < 0) { dev_err(led_cdev->dev, "%s: create file fail %d\n", __func__, i); goto out_create_file; } } ret = mt6370_pmu_led_change_mode2(led_cdev, MT6370_PMU_LED_PWMMODE); if (ret < 0) { dev_err(led_cdev->dev, "%s: change mode fail\n", __func__); goto out_change_mode; } return; out_change_mode: i = ARRAY_SIZE(mt_led_pwm_mode_attrs2); out_create_file: while (--i >= 0) device_remove_file(led_cdev->dev, mt_led_pwm_mode_attrs2 + i); } static void mt6370_pmu_led_pwm_deactivate2(struct led_classdev *led_cdev) { int i = 0; for (i = 0; i < ARRAY_SIZE(mt_led_pwm_mode_attrs2); i++) device_remove_file(led_cdev->dev, mt_led_pwm_mode_attrs2 + i); } static int mt6370_pmu_led_get_breath_regbase2(struct led_classdev *led_cdev) { int led_index = mt6370_pmu_led_get_index(led_cdev); int ret = 0; switch (led_index) { case MT6370_PMU_LED1: ret = 0x8D; break; case MT6370_PMU_LED2: ret = 0x90; break; case MT6370_PMU_LED3: ret = 0x93; break; case MT6370_PMU_LED4: ret = 0x96; break; default: return -EINVAL; } return ret; } static ssize_t mt_led_tr1_attr_show2(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 0, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & 0xF0) >> 4; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.25s)\n", reg_data); } static ssize_t mt_led_tr1_attr_store2(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 0, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > 15) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << 4; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, 0xF0, reg_data); if (ret < 0) return ret; return cnt; } static ssize_t mt_led_tr2_attr_show2(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 0, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & 0x0F) >> 0; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.25s)\n", reg_data); } static ssize_t mt_led_tr2_attr_store2(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 0, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > 15) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << 0; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, 0x0F, reg_data); if (ret < 0) return ret; return cnt; } static ssize_t mt_led_tf1_attr_show2(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 1, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & 0xF0) >> 4; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.25s)\n", reg_data); } static ssize_t mt_led_tf1_attr_store2(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 1, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > 15) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << 4; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, 0xF0, reg_data); if (ret < 0) return ret; return cnt; } static ssize_t mt_led_tf2_attr_show2(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 1, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & 0x0F) >> 0; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.25s)\n", reg_data); } static ssize_t mt_led_tf2_attr_store2(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 1, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > 15) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << 0; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, 0x0F, reg_data); if (ret < 0) return ret; return cnt; } static ssize_t mt_led_ton_attr_show2(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 2, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & 0xF0) >> 4; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.25s)\n", reg_data); } static ssize_t mt_led_ton_attr_store2(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 2, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > 15) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << 4; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, 0xF0, reg_data); if (ret < 0) return ret; return cnt; } static ssize_t mt_led_toff_attr_show2(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 2, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & 0x0F) >> 0; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.25s)\n", reg_data); } static ssize_t mt_led_toff_attr_store2(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 2, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > 15) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase2(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << 0; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, 0x0F, reg_data); if (ret < 0) return ret; return cnt; } static const struct device_attribute mt_led_breath_mode_attrs2[] = { MT_LED_ATTR2(tr1), MT_LED_ATTR2(tr2), MT_LED_ATTR2(tf1), MT_LED_ATTR2(tf2), MT_LED_ATTR2(ton), MT_LED_ATTR2(toff), }; static void mt6370_pmu_led_breath_activate2(struct led_classdev *led_cdev) { int i = 0, ret = 0; for (i = 0; i < ARRAY_SIZE(mt_led_breath_mode_attrs2); i++) { ret = device_create_file(led_cdev->dev, mt_led_breath_mode_attrs2 + i); if (ret < 0) { dev_err(led_cdev->dev, "%s: create file fail %d\n", __func__, i); goto out_create_file; } } ret = mt6370_pmu_led_change_mode2(led_cdev, MT6370_PMU_LED_BREATHMODE); if (ret < 0) { dev_err(led_cdev->dev, "%s: change mode fail\n", __func__); goto out_change_mode; } return; out_change_mode: i = ARRAY_SIZE(mt_led_breath_mode_attrs2); out_create_file: while (--i >= 0) device_remove_file(led_cdev->dev, mt_led_breath_mode_attrs2 + i); } static void mt6370_pmu_led_breath_deactivate2(struct led_classdev *led_cdev) { int i = 0; for (i = 0; i < ARRAY_SIZE(mt_led_breath_mode_attrs2); i++) device_remove_file(led_cdev->dev, mt_led_breath_mode_attrs2 + i); } static struct led_trigger mt6370_pmu_led_trigger2[] = { { .name = "cc_mode", .activate = mt6370_pmu_led_cc_activate2, .deactivate = mt6370_pmu_led_cc_deactivate2, }, { .name = "pwm_mode", .activate = mt6370_pmu_led_pwm_activate2, .deactivate = mt6370_pmu_led_pwm_deactivate2, }, { .name = "breath_mode", .activate = mt6370_pmu_led_breath_activate2, .deactivate = mt6370_pmu_led_breath_deactivate2, }, }; /* 6372 end */ static void mt6370_pmu_led_bright_set(struct led_classdev *led_cdev, enum led_brightness bright) { int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_mask = 0x7, reg_shift = 0, en_mask = 0; bool need_enable_timer = true; int ret = 0; switch (led_index) { case MT6370_PMU_LED1: reg_addr = MT6370_PMU_REG_RGB1ISINK; en_mask = 0x80; break; case MT6370_PMU_LED2: reg_addr = MT6370_PMU_REG_RGB2ISINK; en_mask = 0x40; break; case MT6370_PMU_LED3: reg_addr = MT6370_PMU_REG_RGB3ISINK; en_mask = 0x20; break; case MT6370_PMU_LED4: reg_addr = MT6370_PMU_REG_RGBCHRINDCTRL; reg_mask = 0x3; en_mask = 0x10; need_enable_timer = false; break; default: dev_err(led_cdev->dev, "invalid mt led index\n"); return; } ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, reg_mask, (bright & reg_mask) << reg_shift); if (ret < 0) { dev_err(led_cdev->dev, "update brightness fail\n"); return; } if (!bright) need_enable_timer = false; if (need_enable_timer) { mt6370_pmu_led_enable_dwork(led_cdev); return; } ret = mt6370_pmu_led_update_bits(led_cdev, MT6370_PMU_REG_RGBEN, en_mask, (bright > 0) ? en_mask : ~en_mask); if (ret < 0) dev_err(led_cdev->dev, "update enable bit fail\n"); } static enum led_brightness mt6370_pmu_led_bright_get( struct led_classdev *led_cdev) { int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_mask = 0x7, reg_shift = 0, en_mask = 0; bool need_enable_timer = true; int ret = 0; switch (led_index) { case MT6370_PMU_LED1: reg_addr = MT6370_PMU_REG_RGB1ISINK; en_mask = 0x80; break; case MT6370_PMU_LED2: reg_addr = MT6370_PMU_REG_RGB2ISINK; en_mask = 0x40; break; case MT6370_PMU_LED3: reg_addr = MT6370_PMU_REG_RGB3ISINK; en_mask = 0x20; break; case MT6370_PMU_LED4: reg_addr = MT6370_PMU_REG_RGBCHRINDCTRL; reg_mask = 0x3; en_mask = 0x10; need_enable_timer = false; break; default: dev_err(led_cdev->dev, "invalid mt led index\n"); return -EINVAL; } ret = mt6370_pmu_led_reg_read(led_cdev, MT6370_PMU_REG_RGBEN); if (ret < 0) return ret; if (!(ret & en_mask)) return LED_OFF; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; return (ret & reg_mask) >> reg_shift; } static inline int mt6370_pmu_led_config_pwm(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { const ulong dim_time[] = { 10000, 5000, 2000, 1000, 500, 200, 5, 1}; const unsigned long ton = *delay_on, toff = *delay_off; int led_index = mt6370_pmu_led_get_index(led_cdev); int reg_addr, reg_mask, reg_shift; int i, j, ret = 0; dev_dbg(led_cdev->dev, "%s, on %lu, off %lu\n", __func__, ton, toff); /* find the close dim freq */ for (i = ARRAY_SIZE(dim_time) - 1; i >= 0; i--) { if (dim_time[i] >= (ton + toff)) break; } if (i < 0) { dev_warn(led_cdev->dev, "no match, sum %lu\n", ton + toff); i = 0; } /* write pwm dim freq selection */ switch (led_index) { case MT6370_PMU_LED1: reg_addr = MT6370_PMU_REG_RGB1ISINK; reg_mask = 0x38; reg_shift = 3; break; case MT6370_PMU_LED2: reg_addr = MT6370_PMU_REG_RGB2ISINK; reg_mask = 0x38; reg_shift = 3; break; case MT6370_PMU_LED3: reg_addr = MT6370_PMU_REG_RGB3ISINK; reg_mask = 0x38; reg_shift = 3; break; case MT6370_PMU_LED4: reg_addr = MT6370_PMU_REG_RGBCHRINDCTRL; reg_mask = 0x1C; reg_shift = 2; break; default: return -EINVAL; } ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, reg_mask, i << reg_shift); if (ret < 0) return ret; /* find the closest pwm duty */ j = 32 * ton / (ton + toff); if (j == 0) j = 1; j--; switch (led_index) { case MT6370_PMU_LED1: reg_addr = MT6370_PMU_REG_RGB1DIM; break; case MT6370_PMU_LED2: reg_addr = MT6370_PMU_REG_RGB2DIM; break; case MT6370_PMU_LED3: reg_addr = MT6370_PMU_REG_RGB3DIM; break; case MT6370_PMU_LED4: reg_addr = MT6370_PMU_REG_RGBCHRINDDIM; break; default: return -EINVAL; } reg_mask = MT6370_LED_PWMDUTYMASK; reg_shift = MT6370_LED_PWMDUTYSHFT; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, reg_mask, j << reg_shift); if (ret < 0) return ret; return 0; } static int mt6370_pmu_led_change_mode(struct led_classdev *led_cdev, int mode); static int mt6370_pmu_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { int mode_sel = MT6370_PMU_LED_PWMMODE; int ret = 0; if (!*delay_on && !*delay_off) *delay_on = *delay_off = 500; if (!*delay_off) mode_sel = MT6370_PMU_LED_REGMODE; if (mode_sel == MT6370_PMU_LED_PWMMODE) { /* workaround, fix cc to pwm */ ret = mt6370_pmu_led_change_mode(led_cdev, MT6370_PMU_LED_BREATHMODE); if (ret < 0) dev_err(led_cdev->dev, "%s: mode fix fail\n", __func__); ret = mt6370_pmu_led_config_pwm(led_cdev, delay_on, delay_off); if (ret < 0) dev_err(led_cdev->dev, "%s: cfg pwm fail\n", __func__); } ret = mt6370_pmu_led_change_mode(led_cdev, mode_sel); if (ret < 0) dev_err(led_cdev->dev, "%s: change mode fail\n", __func__); return 0; } static struct mt6370_led_classdev mt6370_led_classdev[MT6370_PMU_MAXLED] = { { .led_dev = { .max_brightness = 6, .brightness_set = mt6370_pmu_led_bright_set, .brightness_get = mt6370_pmu_led_bright_get, .blink_set = mt6370_pmu_led_blink_set, }, .led_index = MT6370_PMU_LED1, }, { .led_dev = { .max_brightness = 6, .brightness_set = mt6370_pmu_led_bright_set, .brightness_get = mt6370_pmu_led_bright_get, .blink_set = mt6370_pmu_led_blink_set, }, .led_index = MT6370_PMU_LED2, }, { .led_dev = { .max_brightness = 6, .brightness_set = mt6370_pmu_led_bright_set, .brightness_get = mt6370_pmu_led_bright_get, .blink_set = mt6370_pmu_led_blink_set, }, .led_index = MT6370_PMU_LED3, }, { .led_dev = { .max_brightness = 3, .brightness_set = mt6370_pmu_led_bright_set, .brightness_get = mt6370_pmu_led_bright_get, .blink_set = mt6370_pmu_led_blink_set, }, .led_index = MT6370_PMU_LED4, }, }; static int mt6370_pmu_led_change_mode(struct led_classdev *led_cdev, int mode) { int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0; int ret = 0; if (mode >= MT6370_PMU_LED_MAXMODE) return -EINVAL; switch (led_index) { case MT6370_PMU_LED1: reg_addr = MT6370_PMU_REG_RGB1DIM; break; case MT6370_PMU_LED2: reg_addr = MT6370_PMU_REG_RGB2DIM; break; case MT6370_PMU_LED3: reg_addr = MT6370_PMU_REG_RGB3DIM; break; case MT6370_PMU_LED4: /* disable auto mode */ ret = mt6370_pmu_led_update_bits(led_cdev, MT6370_PMU_REG_RGBCHRINDDIM, 0x80, 0x80); if (ret < 0) return ret; reg_addr = MT6370_PMU_REG_RGBCHRINDDIM; break; default: return -EINVAL; } return mt6370_pmu_led_update_bits(led_cdev, reg_addr, MT6370_LED_MODEMASK, mode << MT6370_LED_MODESHFT); } static const char * const soft_start_str[] = { "0.5us", "1us", "1.5us", "2us", }; static ssize_t mt_led_soft_start_step_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_mask = 0xC0, reg_shift = 6, reg_data = 0; unsigned long cnt = PAGE_SIZE; int i = 0, ret = 0; switch (led_index) { case MT6370_PMU_LED1: reg_addr = MT6370_PMU_REG_RGB1ISINK; break; case MT6370_PMU_LED2: reg_addr = MT6370_PMU_REG_RGB2ISINK; break; case MT6370_PMU_LED3: reg_addr = MT6370_PMU_REG_RGB3ISINK; break; case MT6370_PMU_LED4: reg_addr = MT6370_PMU_REG_RGBCHRINDCTRL; reg_mask = 0x60; reg_shift = 5; break; default: return -EINVAL; } ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & reg_mask) >> reg_shift; ret = 0; for (i = 0; i < ARRAY_SIZE(soft_start_str); i++) { if (reg_data == i) ret += snprintf(buf + ret, cnt - ret, ">"); ret += snprintf(buf + ret, cnt - ret, "%s ", soft_start_str[i]); } ret += snprintf(buf + ret, cnt - ret, "\n"); return ret; } static ssize_t mt_led_soft_start_step_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_mask = 0xC0, reg_shift = 6, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store >= ARRAY_SIZE(soft_start_str)) return -EINVAL; switch (led_index) { case MT6370_PMU_LED1: reg_addr = MT6370_PMU_REG_RGB1ISINK; break; case MT6370_PMU_LED2: reg_addr = MT6370_PMU_REG_RGB2ISINK; break; case MT6370_PMU_LED3: reg_addr = MT6370_PMU_REG_RGB3ISINK; break; case MT6370_PMU_LED4: reg_addr = MT6370_PMU_REG_RGBCHRINDCTRL; reg_mask = 0x60; reg_shift = 5; break; default: return -EINVAL; } reg_data = store << reg_shift; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, reg_mask, reg_data); if (ret < 0) return ret; return cnt; } static const struct device_attribute mt_led_cc_mode_attrs[] = { MT_LED_ATTR(soft_start_step), }; static void mt6370_pmu_led_cc_activate(struct led_classdev *led_cdev) { int i = 0, ret = 0; for (i = 0; i < ARRAY_SIZE(mt_led_cc_mode_attrs); i++) { ret = device_create_file(led_cdev->dev, mt_led_cc_mode_attrs + i); if (ret < 0) { dev_err(led_cdev->dev, "%s: create file fail %d\n", __func__, i); goto out_create_file; } } ret = mt6370_pmu_led_change_mode(led_cdev, MT6370_PMU_LED_REGMODE); if (ret < 0) { dev_err(led_cdev->dev, "%s: change mode fail\n", __func__); goto out_change_mode; } return; out_change_mode: i = ARRAY_SIZE(mt_led_cc_mode_attrs); out_create_file: while (--i >= 0) device_remove_file(led_cdev->dev, mt_led_cc_mode_attrs + i); } static void mt6370_pmu_led_cc_deactivate(struct led_classdev *led_cdev) { int i = 0; for (i = 0; i < ARRAY_SIZE(mt_led_cc_mode_attrs); i++) device_remove_file(led_cdev->dev, mt_led_cc_mode_attrs + i); } static ssize_t mt_led_pwm_duty_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0; int ret = 0; switch (led_index) { case MT6370_PMU_LED1: reg_addr = MT6370_PMU_REG_RGB1DIM; break; case MT6370_PMU_LED2: reg_addr = MT6370_PMU_REG_RGB2DIM; break; case MT6370_PMU_LED3: reg_addr = MT6370_PMU_REG_RGB3DIM; break; case MT6370_PMU_LED4: reg_addr = MT6370_PMU_REG_RGBCHRINDDIM; break; default: return -EINVAL; } ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_addr = ret & MT6370_LED_PWMDUTYMASK; return snprintf(buf, PAGE_SIZE, "%d (max: %d)\n", reg_addr, MT6370_LED_PWMDUTYMAX); } static ssize_t mt_led_pwm_duty_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > MT6370_LED_PWMDUTYMAX) return -EINVAL; switch (led_index) { case MT6370_PMU_LED1: reg_addr = MT6370_PMU_REG_RGB1DIM; break; case MT6370_PMU_LED2: reg_addr = MT6370_PMU_REG_RGB2DIM; break; case MT6370_PMU_LED3: reg_addr = MT6370_PMU_REG_RGB3DIM; break; case MT6370_PMU_LED4: reg_addr = MT6370_PMU_REG_RGBCHRINDDIM; break; default: return -EINVAL; } reg_data = store << MT6370_LED_PWMDUTYSHFT; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, MT6370_LED_PWMDUTYMASK, reg_data); if (ret < 0) return ret; return cnt; } static const char * const led_dim_freq[] = { "0.1Hz", "0.2Hz", "0.5Hz", "1Hz", "2Hz", "5Hz", "200Hz", "1KHz", }; static ssize_t mt_led_pwm_dim_freq_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_mask = 0x38, reg_shift = 3, reg_data = 0; unsigned long cnt = PAGE_SIZE; int i = 0, ret = 0; switch (led_index) { case MT6370_PMU_LED1: reg_addr = MT6370_PMU_REG_RGB1ISINK; break; case MT6370_PMU_LED2: reg_addr = MT6370_PMU_REG_RGB2ISINK; break; case MT6370_PMU_LED3: reg_addr = MT6370_PMU_REG_RGB3ISINK; break; case MT6370_PMU_LED4: reg_addr = MT6370_PMU_REG_RGBCHRINDCTRL; reg_mask = 0x1C; reg_shift = 2; break; default: return -EINVAL; } ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & reg_mask) >> reg_shift; ret = 0; for (i = 0; i < ARRAY_SIZE(led_dim_freq); i++) { if (reg_data == i) ret += snprintf(buf + ret, cnt - ret, ">"); ret += snprintf(buf + ret, cnt - ret, "%s ", led_dim_freq[i]); } ret += snprintf(buf + ret, cnt - ret, "\n"); return ret; } static ssize_t mt_led_pwm_dim_freq_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); int led_index = mt6370_pmu_led_get_index(led_cdev); uint8_t reg_addr = 0, reg_mask = 0x38, reg_shift = 3, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store >= ARRAY_SIZE(led_dim_freq)) return -EINVAL; switch (led_index) { case MT6370_PMU_LED1: reg_addr = MT6370_PMU_REG_RGB1ISINK; break; case MT6370_PMU_LED2: reg_addr = MT6370_PMU_REG_RGB2ISINK; break; case MT6370_PMU_LED3: reg_addr = MT6370_PMU_REG_RGB3ISINK; break; case MT6370_PMU_LED4: reg_addr = MT6370_PMU_REG_RGBCHRINDCTRL; reg_mask = 0x1C; reg_shift = 2; break; default: return -EINVAL; } reg_data = store << reg_shift; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, reg_mask, reg_data); if (ret < 0) return ret; return cnt; } static const struct device_attribute mt_led_pwm_mode_attrs[] = { MT_LED_ATTR(pwm_duty), MT_LED_ATTR(pwm_dim_freq), }; static void mt6370_pmu_led_pwm_activate(struct led_classdev *led_cdev) { int i = 0, ret = 0; for (i = 0; i < ARRAY_SIZE(mt_led_pwm_mode_attrs); i++) { ret = device_create_file(led_cdev->dev, mt_led_pwm_mode_attrs + i); if (ret < 0) { dev_err(led_cdev->dev, "%s: create file fail %d\n", __func__, i); goto out_create_file; } } /* workaround, fix cc to pwm */ ret = mt6370_pmu_led_change_mode(led_cdev, MT6370_PMU_LED_BREATHMODE); if (ret < 0) { dev_err(led_cdev->dev, "%s: mode fix fail\n", __func__); goto out_change_mode; } ret = mt6370_pmu_led_change_mode(led_cdev, MT6370_PMU_LED_PWMMODE); if (ret < 0) { dev_err(led_cdev->dev, "%s: change mode fail\n", __func__); goto out_change_mode; } return; out_change_mode: i = ARRAY_SIZE(mt_led_pwm_mode_attrs); out_create_file: while (--i >= 0) device_remove_file(led_cdev->dev, mt_led_pwm_mode_attrs + i); } static void mt6370_pmu_led_pwm_deactivate(struct led_classdev *led_cdev) { int i = 0; for (i = 0; i < ARRAY_SIZE(mt_led_pwm_mode_attrs); i++) device_remove_file(led_cdev->dev, mt_led_pwm_mode_attrs + i); } static int mt6370_pmu_led_get_breath_regbase(struct led_classdev *led_cdev) { int led_index = mt6370_pmu_led_get_index(led_cdev); int ret = 0; switch (led_index) { case MT6370_PMU_LED1: ret = MT6370_PMU_REG_RGB1TR; break; case MT6370_PMU_LED2: ret = MT6370_PMU_REG_RGB2TR; break; case MT6370_PMU_LED3: ret = MT6370_PMU_REG_RGB3TR; break; case MT6370_PMU_LED4: ret = MT6370_PMU_REG_RGBCHRINDTR; break; default: return -EINVAL; } return ret; } static ssize_t mt_led_tr1_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 0, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & MT6370_LEDTR1_MASK) >> MT6370_LEDTR1_SHFT; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.2s)\n", reg_data); } static ssize_t mt_led_tr1_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 0, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > MT6370_LEDBREATH_MAX) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << MT6370_LEDTR1_SHFT; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, MT6370_LEDTR1_MASK, reg_data); if (ret < 0) return ret; return cnt; } static ssize_t mt_led_tr2_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 0, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & MT6370_LEDTR2_MASK) >> MT6370_LEDTR2_SHFT; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.2s)\n", reg_data); } static ssize_t mt_led_tr2_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 0, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > MT6370_LEDBREATH_MAX) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << MT6370_LEDTR2_SHFT; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, MT6370_LEDTR2_MASK, reg_data); if (ret < 0) return ret; return cnt; } static ssize_t mt_led_tf1_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 1, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & MT6370_LEDTF1_MASK) >> MT6370_LEDTF1_SHFT; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.2s)\n", reg_data); } static ssize_t mt_led_tf1_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 1, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > MT6370_LEDBREATH_MAX) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << MT6370_LEDTF1_SHFT; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, MT6370_LEDTF1_MASK, reg_data); if (ret < 0) return ret; return cnt; } static ssize_t mt_led_tf2_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 1, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & MT6370_LEDTF2_MASK) >> MT6370_LEDTF2_SHFT; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.2s)\n", reg_data); } static ssize_t mt_led_tf2_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 1, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > MT6370_LEDBREATH_MAX) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << MT6370_LEDTF2_SHFT; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, MT6370_LEDTF2_MASK, reg_data); if (ret < 0) return ret; return cnt; } static ssize_t mt_led_ton_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 2, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & MT6370_LEDTON_MASK) >> MT6370_LEDTON_SHFT; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.2s)\n", reg_data); } static ssize_t mt_led_ton_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 2, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > MT6370_LEDBREATH_MAX) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << MT6370_LEDTON_SHFT; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, MT6370_LEDTON_MASK, reg_data); if (ret < 0) return ret; return cnt; } static ssize_t mt_led_toff_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 2, reg_data = 0; unsigned long cnt = PAGE_SIZE; int ret = 0; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; ret = mt6370_pmu_led_reg_read(led_cdev, reg_addr); if (ret < 0) return ret; reg_data = (ret & MT6370_LEDTOFF_MASK) >> MT6370_LEDTOFF_SHFT; return snprintf(buf, cnt, "%d (max 15, 0.125s, step 0.2s)\n", reg_data); } static ssize_t mt_led_toff_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct led_classdev *led_cdev = dev_get_drvdata(dev); uint8_t reg_addr = 2, reg_data = 0; unsigned long store = 0; int ret = 0; ret = kstrtoul(buf, 10, &store); if (ret < 0) return ret; if (store > MT6370_LEDBREATH_MAX) return -EINVAL; ret = mt6370_pmu_led_get_breath_regbase(led_cdev); if (ret < 0) return ret; reg_addr += ret; reg_data = store << MT6370_LEDTOFF_SHFT; ret = mt6370_pmu_led_update_bits(led_cdev, reg_addr, MT6370_LEDTOFF_MASK, reg_data); if (ret < 0) return ret; return cnt; } static const struct device_attribute mt_led_breath_mode_attrs[] = { MT_LED_ATTR(tr1), MT_LED_ATTR(tr2), MT_LED_ATTR(tf1), MT_LED_ATTR(tf2), MT_LED_ATTR(ton), MT_LED_ATTR(toff), }; static void mt6370_pmu_led_breath_activate(struct led_classdev *led_cdev) { int i = 0, ret = 0; for (i = 0; i < ARRAY_SIZE(mt_led_breath_mode_attrs); i++) { ret = device_create_file(led_cdev->dev, mt_led_breath_mode_attrs + i); if (ret < 0) { dev_err(led_cdev->dev, "%s: create file fail %d\n", __func__, i); goto out_create_file; } } ret = mt6370_pmu_led_change_mode(led_cdev, MT6370_PMU_LED_BREATHMODE); if (ret < 0) { dev_err(led_cdev->dev, "%s: change mode fail\n", __func__); goto out_change_mode; } return; out_change_mode: i = ARRAY_SIZE(mt_led_breath_mode_attrs); out_create_file: while (--i >= 0) device_remove_file(led_cdev->dev, mt_led_breath_mode_attrs + i); } static void mt6370_pmu_led_breath_deactivate(struct led_classdev *led_cdev) { int i = 0; for (i = 0; i < ARRAY_SIZE(mt_led_breath_mode_attrs); i++) device_remove_file(led_cdev->dev, mt_led_breath_mode_attrs + i); } static struct led_trigger mt6370_pmu_led_trigger[] = { { .name = "cc_mode", .activate = mt6370_pmu_led_cc_activate, .deactivate = mt6370_pmu_led_cc_deactivate, }, { .name = "pwm_mode", .activate = mt6370_pmu_led_pwm_activate, .deactivate = mt6370_pmu_led_pwm_deactivate, }, { .name = "breath_mode", .activate = mt6370_pmu_led_breath_activate, .deactivate = mt6370_pmu_led_breath_deactivate, }, }; static irqreturn_t mt6370_pmu_isink4_short_irq_handler(int irq, void *data) { struct mt6370_pmu_rgbled_data *rgbled_data = data; dev_notice(rgbled_data->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6370_pmu_isink3_short_irq_handler(int irq, void *data) { struct mt6370_pmu_rgbled_data *rgbled_data = data; dev_notice(rgbled_data->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6370_pmu_isink2_short_irq_handler(int irq, void *data) { struct mt6370_pmu_rgbled_data *rgbled_data = data; dev_notice(rgbled_data->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6370_pmu_isink1_short_irq_handler(int irq, void *data) { struct mt6370_pmu_rgbled_data *rgbled_data = data; dev_notice(rgbled_data->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6370_pmu_isink4_open_irq_handler(int irq, void *data) { struct mt6370_pmu_rgbled_data *rgbled_data = data; dev_notice(rgbled_data->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6370_pmu_isink3_open_irq_handler(int irq, void *data) { struct mt6370_pmu_rgbled_data *rgbled_data = data; dev_notice(rgbled_data->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6370_pmu_isink2_open_irq_handler(int irq, void *data) { struct mt6370_pmu_rgbled_data *rgbled_data = data; dev_notice(rgbled_data->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6370_pmu_isink1_open_irq_handler(int irq, void *data) { struct mt6370_pmu_rgbled_data *rgbled_data = data; dev_notice(rgbled_data->dev, "%s\n", __func__); return IRQ_HANDLED; } static struct mt6370_pmu_irq_desc mt6370_rgbled_irq_desc[] = { MT6370_PMU_IRQDESC(isink4_short), MT6370_PMU_IRQDESC(isink3_short), MT6370_PMU_IRQDESC(isink2_short), MT6370_PMU_IRQDESC(isink1_short), MT6370_PMU_IRQDESC(isink4_open), MT6370_PMU_IRQDESC(isink3_open), MT6370_PMU_IRQDESC(isink2_open), MT6370_PMU_IRQDESC(isink1_open), }; static void mt6370_pmu_rgbled_irq_register(struct platform_device *pdev) { struct resource *res; int i, ret = 0; for (i = 0; i < ARRAY_SIZE(mt6370_rgbled_irq_desc); i++) { if (!mt6370_rgbled_irq_desc[i].name) continue; res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, mt6370_rgbled_irq_desc[i].name); if (!res) continue; ret = devm_request_threaded_irq(&pdev->dev, res->start, NULL, mt6370_rgbled_irq_desc[i].irq_handler, IRQF_TRIGGER_FALLING, mt6370_rgbled_irq_desc[i].name, platform_get_drvdata(pdev)); if (ret < 0) { dev_err(&pdev->dev, "request %s irq fail\n", res->name); continue; } mt6370_rgbled_irq_desc[i].irq = res->start; } } static void mt6370_led_enable_dwork_func(struct work_struct *work) { struct mt6370_pmu_rgbled_data *rgbled_data = container_of(work, struct mt6370_pmu_rgbled_data, dwork.work); u8 chip_vid = rgbled_data->chip->chip_vid; u8 reg_data = 0, reg_mask = 0xE0, reg_addr = MT6370_PMU_REG_RGBEN; int ret = 0; dev_dbg(rgbled_data->dev, "%s\n", __func__); /* red */ if (mt6370_led_classdev[0].led_dev.brightness != 0) reg_data |= 0x80; /* blue */ if (mt6370_led_classdev[1].led_dev.brightness != 0) reg_data |= 0x40; /* green */ if (mt6370_led_classdev[2].led_dev.brightness != 0) reg_data |= 0x20; /* if 6372 */ if (chip_vid == MT6372_VENDOR_ID || chip_vid == MT6372C_VENDOR_ID) reg_addr = 0x82; ret = mt6370_pmu_reg_update_bits(rgbled_data->chip, reg_addr, reg_mask, reg_data); if (ret < 0) dev_err(rgbled_data->dev, "timer update enable bit fail\n"); } static inline int mt6370_pmu_rgbled_init_register( struct mt6370_pmu_rgbled_data *rgbled_data) { const u8 *init_data = rgbled_init_data; int init_data_size = ARRAY_SIZE(rgbled_init_data); u8 chip_vid = rgbled_data->chip->chip_vid; if (chip_vid == MT6372_VENDOR_ID || chip_vid == MT6372C_VENDOR_ID) { init_data = rgbled_init_data2; init_data_size = ARRAY_SIZE(rgbled_init_data2); } return mt6370_pmu_reg_block_write(rgbled_data->chip, MT6370_PMU_REG_RGB1DIM, init_data_size, init_data); } static inline int mt6370_pmu_rgbled_parse_initdata( struct mt6370_pmu_rgbled_data *rgbled_data) { return 0; } static inline int mt_parse_dt(struct device *dev) { struct mt6370_pmu_rgbled_platdata *pdata = dev_get_platdata(dev); int name_cnt = 0, trigger_cnt = 0; struct device_node *np = dev->of_node; int ret = 0; while (true) { const char *name = NULL; ret = of_property_read_string_index(np, "mt,led_name", name_cnt, &name); if (ret < 0) break; pdata->led_name[name_cnt] = name; name_cnt++; } while (true) { const char *name = NULL; ret = of_property_read_string_index(np, "mt,led_default_trigger", trigger_cnt, &name); if (ret < 0) break; pdata->led_default_trigger[trigger_cnt] = name; trigger_cnt++; } if (name_cnt != MT6370_PMU_MAXLED || trigger_cnt != MT6370_PMU_MAXLED) return -EINVAL; return 0; } static int mt6370_pmu_rgbled_probe(struct platform_device *pdev) { struct mt6370_pmu_rgbled_platdata *pdata = dev_get_platdata(&pdev->dev); struct mt6370_pmu_rgbled_data *rgbled_data; bool use_dt = pdev->dev.of_node, new = false; int i = 0, ret = 0; pr_info("%s: (%s)\n", __func__, MT6370_PMU_RGBLED_DRV_VERSION); rgbled_data = devm_kzalloc(&pdev->dev, sizeof(*rgbled_data), GFP_KERNEL); if (!rgbled_data) return -ENOMEM; if (use_dt) { /* DTS used */ pdata = devm_kzalloc(&pdev->dev, sizeof(*rgbled_data), GFP_KERNEL); if (!pdata) { ret = -ENOMEM; goto out_pdata; } pdev->dev.platform_data = pdata; ret = mt_parse_dt(&pdev->dev); if (ret < 0) { devm_kfree(&pdev->dev, pdata); goto out_pdata; } } else { if (!pdata) { ret = -EINVAL; goto out_pdata; } } rgbled_data->chip = dev_get_drvdata(pdev->dev.parent); rgbled_data->dev = &pdev->dev; platform_set_drvdata(pdev, rgbled_data); INIT_DELAYED_WORK(&rgbled_data->dwork, mt6370_led_enable_dwork_func); if (rgbled_data->chip->chip_vid == MT6372_VENDOR_ID || rgbled_data->chip->chip_vid == MT6372C_VENDOR_ID) new = true; ret = mt6370_pmu_rgbled_parse_initdata(rgbled_data); if (ret < 0) goto out_init_data; ret = mt6370_pmu_rgbled_init_register(rgbled_data); if (ret < 0) goto out_init_reg; for (i = 0; i < ARRAY_SIZE(mt6370_pmu_led_trigger) && !new; i++) { ret = led_trigger_register(&mt6370_pmu_led_trigger[i]); if (ret < 0) { dev_err(&pdev->dev, "register %d trigger fail\n", i); goto out_led_trigger; } } for (i = 0; i < ARRAY_SIZE(mt6370_pmu_led_trigger2) && new; i++) { ret = led_trigger_register(&mt6370_pmu_led_trigger2[i]); if (ret < 0) { dev_err(&pdev->dev, "register %d trigger fail\n", i); goto out_led_trigger; } } for (i = 0; i < ARRAY_SIZE(mt6370_led_classdev) && !new; i++) { mt6370_led_classdev[i].led_dev.name = pdata->led_name[i]; mt6370_led_classdev[i].led_dev.default_trigger = pdata->led_default_trigger[i]; ret = led_classdev_register(&pdev->dev, &mt6370_led_classdev[i].led_dev); if (ret < 0) { dev_err(&pdev->dev, "register led %d fail\n", i); goto out_led_register; } } for (i = 0; i < ARRAY_SIZE(mt6370_led_classdev2) && new; i++) { mt6370_led_classdev2[i].led_dev.name = pdata->led_name[i]; mt6370_led_classdev2[i].led_dev.default_trigger = pdata->led_default_trigger[i]; ret = led_classdev_register(&pdev->dev, &mt6370_led_classdev2[i].led_dev); if (ret < 0) { dev_err(&pdev->dev, "register led %d fail\n", i); goto out_led_register; } } mt6370_pmu_rgbled_irq_register(pdev); dev_info(&pdev->dev, "%s successfully\n", __func__); return 0; out_led_register: while (!new && --i >= 0) led_classdev_unregister(&mt6370_led_classdev[i].led_dev); while (new && --i >= 0) led_classdev_unregister(&mt6370_led_classdev2[i].led_dev); if (!new) i = ARRAY_SIZE(mt6370_pmu_led_trigger); else i = ARRAY_SIZE(mt6370_pmu_led_trigger2); out_led_trigger: while (!new && --i >= 0) led_trigger_register(&mt6370_pmu_led_trigger[i]); while (new && --i >= 0) led_trigger_register(&mt6370_pmu_led_trigger2[i]); out_init_reg: out_init_data: out_pdata: devm_kfree(&pdev->dev, rgbled_data); return ret; } static int mt6370_pmu_rgbled_remove(struct platform_device *pdev) { struct mt6370_pmu_rgbled_data *rgbled_data = platform_get_drvdata(pdev); u8 chip_vid = rgbled_data->chip->chip_vid; bool new = false; int i = 0; if (chip_vid == MT6372_VENDOR_ID || chip_vid == MT6372C_VENDOR_ID) new = true; for (i = 0; i < ARRAY_SIZE(mt6370_led_classdev) && !new; i++) led_classdev_unregister(&mt6370_led_classdev[i].led_dev); for (i = 0; i < ARRAY_SIZE(mt6370_led_classdev2) && new; i++) led_classdev_unregister(&mt6370_led_classdev2[i].led_dev); for (i = 0; i < ARRAY_SIZE(mt6370_pmu_led_trigger) && !new; i++) led_trigger_register(&mt6370_pmu_led_trigger[i]); for (i = 0; i < ARRAY_SIZE(mt6370_pmu_led_trigger2) && new; i++) led_trigger_register(&mt6370_pmu_led_trigger2[i]); dev_info(rgbled_data->dev, "%s successfully\n", __func__); return 0; } static const struct of_device_id mt_ofid_table[] = { { .compatible = "mediatek,mt6370_pmu_rgbled", }, { }, }; MODULE_DEVICE_TABLE(of, mt_ofid_table); static const struct platform_device_id mt_id_table[] = { { "mt6370_pmu_rgbled", 0}, { }, }; MODULE_DEVICE_TABLE(platform, mt_id_table); static struct platform_driver mt6370_pmu_rgbled = { .driver = { .name = "mt6370_pmu_rgbled", .owner = THIS_MODULE, .of_match_table = of_match_ptr(mt_ofid_table), }, .probe = mt6370_pmu_rgbled_probe, .remove = mt6370_pmu_rgbled_remove, .id_table = mt_id_table, }; module_platform_driver(mt6370_pmu_rgbled); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MediaTek MT6370 PMU RGBled"); MODULE_VERSION(MT6370_PMU_RGBLED_DRV_VERSION); /* * Release Note * 1.0.1_MTK * (1) Add support for MT6372 * * 1.0.0_MTK * (1) Initial Release */