/* * drivers/power/axp/axp-regulator.c * (C) Copyright 2010-2016 * Allwinner Technology Co., Ltd. * Pannan * * axp regulator APIs * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_SUNXI_ARISC) #include #endif #include "axp-core.h" #include "axp-regulator.h" /* pwr_dm_bitmap -> pwr_dm_name */ struct bitmap_name_mapping pwr_dm_bitmap_name_mapping[] = { {VDD_CPUA_BIT, "vdd-cpua" }, {VDD_CPUB_BIT, "vdd-cpub" }, {VCC_DRAM_BIT, "vcc-dram" }, {VDD_GPU_BIT, "vdd-gpu" }, {VDD_SYS_BIT, "vdd-sys" }, {VDD_VPU_BIT, "vdd-vpu" }, {VDD_CPUS_BIT, "vdd-cpus" }, {VDD_DRAMPLL_BIT, "vdd-drampll" }, {VCC_ADC_BIT, "vcc-adc" }, {VCC_PL_BIT, "vcc-pl" }, {VCC_PM_BIT, "vcc-pm" }, {VCC_IO_BIT, "vcc-io" }, {VCC_CPVDD_BIT, "vcc-cpvdd" }, {VCC_LDOIN_BIT, "vcc-ldoin" }, {VCC_PLL_BIT, "vcc-pll" }, {VCC_LPDDR_BIT, "vcc-lpddr" }, {VDD_TEST_BIT, "vdd-test" }, {VDD_RES1_BIT, "vdd-res1-bit"}, {VDD_RES2_BIT, "vdd-res2-bit"}, #if (defined(CONFIG_ARCH_SUN8IW10) || defined(CONFIG_ARCH_SUN8IW11)) {VCC_PC_BIT, "vcc-pc" }, #else {VDD_RES3_BIT, "vdd-res3-bit"}, #endif }; int pwr_dm_bitmap_name_mapping_cnt = sizeof(pwr_dm_bitmap_name_mapping)\ /sizeof(pwr_dm_bitmap_name_mapping[0]); RAW_NOTIFIER_HEAD(axp_regu_notifier); #if defined(CONFIG_AW_AXPDUMMY) && defined(CONFIG_SUNXI_ARISC) struct ldo_type_name_mapping { enum power_voltage_type ldo_type; char *ldo_name; }; static struct ldo_type_name_mapping mapping_list[] = { {DUMMY_REGULATOR1, "axpdummy_ldo1"}, {DUMMY_REGULATOR2, "axpdummy_ldo2"}, {DUMMY_REGULATOR3, "axpdummy_ldo3"}, {DUMMY_REGULATOR4, "axpdummy_ldo4"}, {DUMMY_REGULATOR5, "axpdummy_ldo5"}, {DUMMY_REGULATOR6, "axpdummy_ldo6"}, }; static enum power_voltage_type get_ldo_type_by_name(const char *name) { int i; for (i = 0; i < ARRAY_SIZE(mapping_list); i++) { if (!strcmp(mapping_list[i].ldo_name, name)) break; } return mapping_list[i].ldo_type; }; #endif static struct axp_consumer_supply *consumer_supply_count; static inline struct axp_dev *to_axp_dev(struct regulator_dev *rdev) { struct device *dev; struct axp_dev *adev; dev = rdev_get_dev(rdev)->parent->parent; adev = dev_get_drvdata(dev); return adev; } static inline s32 check_range(struct axp_regulator_info *info, s32 min_uv, s32 max_uv) { if (min_uv < info->min_uv || min_uv > info->max_uv) return -EINVAL; return 0; } /* AXP common operations */ static s32 axp_set_voltage(struct regulator_dev *rdev, s32 min_uv, s32 max_uv, unsigned *selector) { struct axp_regulator_info *info = rdev_get_drvdata(rdev); struct axp_regmap *regmap = info->regmap; u8 val, mask; s32 ret = -1; int i, flag = 0; struct axp_dev *cur_axp_dev; for (i = 0; i < AXP_ONLINE_SUM; i++) { cur_axp_dev = get_pmu_cur_dev(i); if (cur_axp_dev == NULL) continue; flag++; if (cur_axp_dev->is_dummy) { #if defined(CONFIG_AW_AXPDUMMY) && defined(CONFIG_SUNXI_ARISC) return arisc_pmu_set_voltage( get_ldo_type_by_name(rdev->constraints->name), min_uv / 1000); #endif return 0; } else { continue; } } BUG_ON(flag == 0); if (check_range(info, min_uv, max_uv)) { pr_err("invalid voltage range (%d, %d) uV\n", min_uv, max_uv); return -EINVAL; } if ((info->switch_uv != 0) && (info->step2_uv != 0) && (info->new_level_uv != 0) && (min_uv > info->switch_uv)) { val = (info->switch_uv - info->min_uv + info->step1_uv - 1) / info->step1_uv; if (min_uv <= info->new_level_uv) { val += 1; } else { val += (min_uv - info->new_level_uv) / info->step2_uv; val += 1; } mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; } else if ((info->switch_uv != 0) && (info->step2_uv != 0) && (min_uv > info->switch_uv) && (info->new_level_uv == 0)) { val = (info->switch_uv - info->min_uv + info->step1_uv - 1) / info->step1_uv; val += (min_uv - info->switch_uv) / info->step2_uv; mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; } else { val = (min_uv - info->min_uv + info->step1_uv - 1) / info->step1_uv; val <<= info->vol_shift; mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; } ret = axp_regmap_update(regmap, info->vol_reg, val, mask); if (ret) return ret; if (0 != info->dvm_enable_reg) { ret = axp_regmap_read(regmap, info->dvm_enable_reg, &val); if (ret) { pr_err("read dvm enable reg failed!\n"); return ret; } if (val & (0x1<dvm_enable_bit)) { if (!info->dvm_finish_flag) { udelay(1000); } else { /* wait dvm status update */ udelay(100); do { ret = axp_regmap_read(regmap, info->vol_reg, &val); if (ret) { pr_err("read dvm failed!\n"); break; } } while (!(val & (0x1<<7))); } } } AXP_DEBUG(AXP_REGU, info->pmu_num, "set %s voltage: %duV\n", rdev->constraints->name, min_uv); return ret; } static s32 axp_get_voltage(struct regulator_dev *rdev) { struct axp_regulator_info *info = rdev_get_drvdata(rdev); struct axp_regmap *regmap = info->regmap; u8 val, mask; s32 ret, switch_val, vol; int i, flag = 0; struct axp_dev *cur_axp_dev; for (i = 0; i < AXP_ONLINE_SUM; i++) { cur_axp_dev = get_pmu_cur_dev(i); if (cur_axp_dev == NULL) continue; flag++; if (cur_axp_dev->is_dummy) { #if defined(CONFIG_AW_AXPDUMMY) && defined(CONFIG_SUNXI_ARISC) return arisc_pmu_get_voltage( get_ldo_type_by_name(rdev->constraints->name)) * 1000; #endif return INT_MAX; } else { continue; } } BUG_ON(flag == 0); ret = axp_regmap_read(regmap, info->vol_reg, &val); if (ret) return ret; mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; if (info->step1_uv != 0) switch_val = ((info->switch_uv - info->min_uv + info->step1_uv - 1) / info->step1_uv); else switch_val = 0; val = (val & mask) >> info->vol_shift; if ((info->switch_uv != 0) && (info->step2_uv != 0) && (val > switch_val) && (info->new_level_uv != 0)) { val -= switch_val; vol = info->new_level_uv + info->step2_uv * val; } else if ((info->switch_uv != 0) && (info->step2_uv != 0) && (val > switch_val) && (info->new_level_uv == 0)) { val -= switch_val; vol = info->switch_uv + info->step2_uv * val; } else { vol = info->min_uv + info->step1_uv * val; } if (vol > info->max_uv) vol = info->max_uv; return vol; } static s32 axp_enable(struct regulator_dev *rdev) { struct axp_regulator_info *info = rdev_get_drvdata(rdev); struct axp_regmap *regmap = info->regmap; int i, flag = 0; struct axp_dev *cur_axp_dev; AXP_DEBUG(AXP_REGU, info->pmu_num, "enable %s\n", rdev->constraints->name); for (i = 0; i < AXP_ONLINE_SUM; i++) { cur_axp_dev = get_pmu_cur_dev(i); if (cur_axp_dev == NULL) continue; flag++; if (cur_axp_dev->is_dummy) { #if defined(CONFIG_AW_AXPDUMMY) && defined(CONFIG_SUNXI_ARISC) return arisc_pmu_set_voltage_state( get_ldo_type_by_name(rdev->constraints->name), 1); #endif return 0; } else { continue; } } BUG_ON(flag == 0); return axp_regmap_update(regmap, rdev->desc->enable_reg, info->enable_val, rdev->desc->enable_mask); } static s32 axp_disable(struct regulator_dev *rdev) { struct axp_regulator_info *info = rdev_get_drvdata(rdev); struct axp_regmap *regmap = info->regmap; int i, flag = 0; struct axp_dev *cur_axp_dev; AXP_DEBUG(AXP_REGU, info->pmu_num, "disable %s\n", rdev->constraints->name); for (i = 0; i < AXP_ONLINE_SUM; i++) { cur_axp_dev = get_pmu_cur_dev(i); if (cur_axp_dev == NULL) continue; flag++; if (cur_axp_dev->is_dummy) { #if defined(CONFIG_AW_AXPDUMMY) && defined(CONFIG_SUNXI_ARISC) return arisc_pmu_set_voltage_state( get_ldo_type_by_name(rdev->constraints->name), 0); #endif return 0; } else { continue; } } BUG_ON(flag == 0); return axp_regmap_update(regmap, rdev->desc->enable_reg, info->disable_val, rdev->desc->enable_mask); } static s32 axp_is_enabled(struct regulator_dev *rdev) { struct axp_regulator_info *info = rdev_get_drvdata(rdev); struct axp_regmap *regmap = info->regmap; u8 reg_val; s32 ret; int i, flag = 0; struct axp_dev *cur_axp_dev; for (i = 0; i < AXP_ONLINE_SUM; i++) { cur_axp_dev = get_pmu_cur_dev(i); if (cur_axp_dev == NULL) continue; flag++; if (cur_axp_dev->is_dummy) { #if defined(CONFIG_AW_AXPDUMMY) && defined(CONFIG_SUNXI_ARISC) return arisc_pmu_get_voltage_state( get_ldo_type_by_name(rdev->constraints->name)); #endif return INT_MAX; } else { continue; } } BUG_ON(flag == 0); /* rtc is always enabled, can't disable by software */ if (strstr(rdev->constraints->name, "rtc")) return 1; ret = axp_regmap_read(regmap, rdev->desc->enable_reg, ®_val); if (ret) return -1; if ((reg_val & rdev->desc->enable_mask) == info->enable_val) return 1; else return 0; } static s32 axp_list_voltage(struct regulator_dev *rdev, unsigned selector) { struct axp_regulator_info *info = rdev_get_drvdata(rdev); s32 ret; int i, flag = 0; struct axp_dev *cur_axp_dev; for (i = 0; i < AXP_ONLINE_SUM; i++) { cur_axp_dev = get_pmu_cur_dev(i); if (cur_axp_dev == NULL) continue; flag++; if (cur_axp_dev->is_dummy) return 0; else continue; } BUG_ON(flag == 0); ret = info->min_uv + info->step1_uv * selector; if ((info->switch_uv != 0) && (info->step2_uv != 0) && (ret > info->switch_uv) && (info->new_level_uv != 0)) { selector -= ((info->switch_uv-info->min_uv)/info->step1_uv); ret = info->new_level_uv + info->step2_uv * selector; } else if ((info->switch_uv != 0) && (info->step2_uv != 0) && (ret > info->switch_uv) && (info->new_level_uv == 0)) { selector -= ((info->switch_uv-info->min_uv)/info->step1_uv); ret = info->switch_uv + info->step2_uv * selector; } if (ret > info->max_uv) return -EINVAL; return ret; } static s32 axp_enable_time_regulator(struct regulator_dev *rdev) { struct axp_regulator_info *info = rdev_get_drvdata(rdev); int i, flag = 0; struct axp_dev *cur_axp_dev; for (i = 0; i < AXP_ONLINE_SUM; i++) { cur_axp_dev = get_pmu_cur_dev(i); if (cur_axp_dev == NULL) continue; flag++; if (cur_axp_dev->is_dummy) return 0; else continue; } BUG_ON(flag == 0); /* Per-regulator power on delay from spec */ if (40 > info->desc.id) return 400; else return 1200; } static int axp_set_voltage_sel(struct regulator_dev *rdev, unsigned selector) { struct axp_regulator_info *info = rdev_get_drvdata(rdev); struct axp_regmap *regmap = info->regmap; u8 mask; AXP_DEBUG(AXP_REGU, info->pmu_num, "set %s voltage selector: %u\n", rdev->constraints->name, selector); selector <<= info->vol_shift; mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; return axp_regmap_update(regmap, info->vol_reg, selector, mask); } static int axp_get_voltage_sel(struct regulator_dev *rdev) { struct axp_regulator_info *info = rdev_get_drvdata(rdev); struct axp_regmap *regmap = info->regmap; int ret; u8 val, mask; mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; ret = axp_regmap_read(regmap, info->vol_reg, &val); if (ret != 0) return ret; val &= mask; val >>= info->vol_shift; return val; } static int axp_list_voltage_sel(struct regulator_dev *rdev, unsigned index) { struct axp_regulator_info *info = rdev_get_drvdata(rdev); int ret = -EINVAL; if (info->vtable && (index < rdev->desc->n_voltages)) ret = info->vtable[index] * 1000; return ret; } static struct regulator_ops axp_ops = { .set_voltage = axp_set_voltage, .get_voltage = axp_get_voltage, .list_voltage = axp_list_voltage, .enable = axp_enable, .disable = axp_disable, .is_enabled = axp_is_enabled, .enable_time = axp_enable_time_regulator, .set_suspend_enable = axp_enable, .set_suspend_disable = axp_disable, }; static struct regulator_ops axp_sel_ops = { .set_voltage_sel = axp_set_voltage_sel, .get_voltage_sel = axp_get_voltage_sel, .list_voltage = axp_list_voltage_sel, .enable = axp_enable, .disable = axp_disable, .is_enabled = axp_is_enabled, .enable_time = axp_enable_time_regulator, .set_suspend_enable = axp_enable, .set_suspend_disable = axp_disable, }; struct regulator_dev *axp_regulator_register(struct device *dev, struct axp_regmap *regmap, struct regulator_init_data *init_data, struct axp_regulator_info *info) { struct regulator_config config = { }; struct regulator_dev *rdev; config.dev = dev; config.init_data = init_data; info->regmap = regmap; info->desc.ops = &axp_ops; config.driver_data = info; rdev = regulator_register(&info->desc, &config); return rdev; } EXPORT_SYMBOL(axp_regulator_register); struct regulator_dev *axp_regulator_sel_register(struct device *dev, struct axp_regmap *regmap, struct regulator_init_data *init_data, struct axp_regulator_info *info) { struct regulator_config config = { }; struct regulator_dev *rdev; config.dev = dev; config.init_data = init_data; info->regmap = regmap; info->desc.ops = &axp_sel_ops; config.driver_data = info; rdev = regulator_register(&info->desc, &config); return rdev; } EXPORT_SYMBOL(axp_regulator_sel_register); void axp_regulator_unregister(struct regulator_dev *rdev) { regulator_unregister(rdev); } EXPORT_SYMBOL(axp_regulator_unregister); static s32 regu_device_tree_do_parse(struct device_node *node, struct regulator_init_data *axp_init_data, s32 (*get_dep_cb)(const char *)) { int ret; u32 ldo_count = 0, ldo_index = 0; char name[32] = {0}; s32 num = 0, supply_num = 0, i = 0, j = 0, var = 0; struct axp_consumer_supply consumer_supply[20]; const char *regulator_string = NULL; struct regulator_consumer_supply *regu_consumer_supply = NULL; if (of_property_read_u32(node, "regulator_count", &ldo_count)) { pr_err("%s: axp regu get regulator_count failed", __func__); return -ENOENT; } for (ldo_index = 1; ldo_index <= ldo_count; ldo_index++) { sprintf(name, "regulator%d", ldo_index); if (of_property_read_string(node, (const char *)&name, ®ulator_string)) { pr_err("node %s get failed!\n", name); continue; } num = sscanf(regulator_string, "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s", consumer_supply[0].supply, consumer_supply[1].supply, consumer_supply[2].supply, consumer_supply[3].supply, consumer_supply[4].supply, consumer_supply[5].supply, consumer_supply[6].supply, consumer_supply[7].supply, consumer_supply[8].supply, consumer_supply[9].supply, consumer_supply[10].supply, consumer_supply[11].supply, consumer_supply[12].supply, consumer_supply[13].supply, consumer_supply[14].supply, consumer_supply[15].supply, consumer_supply[16].supply, consumer_supply[17].supply, consumer_supply[18].supply, consumer_supply[19].supply); if (num <= -1) { pr_err("parse ldo%d from sysconfig failed\n", ldo_index); return -1; } else { if (strcmp(consumer_supply[1].supply, "none")) { var = simple_strtoul( consumer_supply[1].supply, NULL, 10); if (var > (ldo_index-1)) pr_err("supply rely set err\n"); else (*(axp_init_data+(ldo_index-1))).supply_regulator = ((*(axp_init_data+(var-1))).consumer_supplies)->supply; } supply_num = num-1; (*(axp_init_data+(ldo_index-1))).num_consumer_supplies = supply_num; consumer_supply_count = kzalloc( sizeof(struct axp_consumer_supply)*supply_num, GFP_KERNEL); if (!consumer_supply_count) { pr_err("%s: request \"\ \"consumer_supply_count failed\n", __func__); return -1; } regu_consumer_supply = kzalloc( sizeof(struct regulator_consumer_supply)*supply_num, GFP_KERNEL); if (!regu_consumer_supply) { pr_err("%s: request \"\ \"regu_consumer_supply failed\n", __func__); kfree(consumer_supply_count); return -1; } for (i = 0; i < supply_num; i++) { if (0 != i) j = i + 1; else j = i; strcpy((char *)(consumer_supply_count+i), consumer_supply[j].supply); (regu_consumer_supply+i)->supply = (const char *)( (struct axp_consumer_supply *) (consumer_supply_count+i)->supply); { int ret = 0, sys_id_index = 0; sys_id_index = axp_check_sys_id( (consumer_supply_count+i)->supply); if (0 <= sys_id_index) { ret = axp_get_ldo_dependence( (const char *) &(consumer_supply[0].supply), sys_id_index, get_dep_cb); if (ret < 0) pr_err("sys_id %s set dependence failed.\n", (consumer_supply_count +i)->supply); } } } (*(axp_init_data+(ldo_index-1))).consumer_supplies = regu_consumer_supply; } } ret = __raw_notifier_call_chain(&axp_regu_notifier, AXP_READY, NULL, -1, NULL); return notifier_to_errno(ret); } s32 axp_regulator_dt_parse(struct device_node *node, struct regulator_init_data *axp_init_data, s32 (*get_dep_cb)(const char *)) { s32 ret; if (!of_device_is_available(node)) { pr_err("%s: failed\n", __func__); return -1; } ret = regu_device_tree_do_parse(node, axp_init_data, get_dep_cb); return ret; } EXPORT_SYMBOL(axp_regulator_dt_parse); int axp_get_ldo_count(struct device_node *node, u32 *ldo_count) { if (!of_device_is_available(node)) { pr_err("%s: axp regu is disable", __func__); return -EPERM; } if (of_property_read_u32(node, "regulator_count", ldo_count)) { pr_err("%s: axp regu get regulator_count failed", __func__); return -ENOENT; } return 0; } int axp_mem_regu_init(struct device_node *node, axp_mem_data_t *r_list, u32 ldo_count) { u32 ldo_index = 0; char name[32] = {0}; s32 num = 0; struct axp_consumer_supply consumer_supply[20]; const char *regulator_string = NULL; for (ldo_index = 1; ldo_index <= ldo_count; ldo_index++) { sprintf(name, "regulator%d", ldo_index); if (of_property_read_string(node, (const char *)&name, ®ulator_string)) { pr_err("node %s get failed!\n", name); continue; } num = sscanf(regulator_string, "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s", consumer_supply[0].supply, consumer_supply[1].supply, consumer_supply[2].supply, consumer_supply[3].supply, consumer_supply[4].supply, consumer_supply[5].supply, consumer_supply[6].supply, consumer_supply[7].supply, consumer_supply[8].supply, consumer_supply[9].supply, consumer_supply[10].supply, consumer_supply[11].supply, consumer_supply[12].supply, consumer_supply[13].supply, consumer_supply[14].supply, consumer_supply[15].supply, consumer_supply[16].supply, consumer_supply[17].supply, consumer_supply[18].supply, consumer_supply[19].supply); if (num <= -1) { pr_err("get ldo%d from sysconfig failed\n", ldo_index); return -1; } else { strcpy((char *)((r_list + ldo_index - 1)->id_name), consumer_supply[0].supply); } } return 0; } static ssize_t workmode_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; uint8_t val; struct regulator_dev *rdev; struct axp_regulator_info *info; struct axp_regmap *regmap; rdev = container_of(dev, struct regulator_dev, dev); info = rdev_get_drvdata(rdev); regmap = info->regmap; ret = axp_regmap_read(regmap, info->mode_reg, &val); if (ret) return sprintf(buf, "IO ERROR\n"); if ((val & info->mode_mask) == info->mode_mask) return sprintf(buf, "PWM\n"); else return sprintf(buf, "AUTO\n"); } static ssize_t workmode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { uint8_t val; struct regulator_dev *rdev; struct axp_regulator_info *info; struct axp_regmap *regmap; unsigned int mode; int ret; rdev = container_of(dev, struct regulator_dev, dev); info = rdev_get_drvdata(rdev); regmap = info->regmap; ret = kstrtouint(buf, 10, &mode); if (ret) return -EINVAL; val = !!mode; if (val) axp_regmap_set_bits(regmap, info->mode_reg, info->mode_mask); else axp_regmap_clr_bits(regmap, info->mode_reg, info->mode_mask); return count; } static ssize_t frequency_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; uint8_t val; struct regulator_dev *rdev; struct axp_regulator_info *info; struct axp_regmap *regmap; rdev = container_of(dev, struct regulator_dev, dev); info = rdev_get_drvdata(rdev); regmap = info->regmap; ret = axp_regmap_read(regmap, info->freq_reg, &val); if (ret) return ret; ret = val & 0x0F; return sprintf(buf, "%d\n", (ret * 5 + 50)); } static ssize_t frequency_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { uint8_t val, tmp; int var, err; struct regulator_dev *rdev; struct axp_regulator_info *info; struct axp_regmap *regmap; rdev = container_of(dev, struct regulator_dev, dev); info = rdev_get_drvdata(rdev); regmap = info->regmap; err = kstrtoint(buf, 10, &var); if (err) return err; if (var < 50) var = 50; if (var > 100) var = 100; val = (var - 50) / 5; val &= 0x0F; axp_regmap_read(regmap, info->freq_reg, &tmp); tmp &= 0xF0; val |= tmp; axp_regmap_write(regmap, info->freq_reg, val); return count; } static struct device_attribute axp_regulator_attrs[] = { AXP_REGULATOR_ATTR(workmode), AXP_REGULATOR_ATTR(frequency), }; int axp_regulator_create_attrs(struct device *dev) { int j, ret; for (j = 0; j < ARRAY_SIZE(axp_regulator_attrs); j++) { ret = device_create_file(dev, &axp_regulator_attrs[j]); if (ret) goto sysfs_failed; } return 0; sysfs_failed: while (j--) device_remove_file(dev, &axp_regulator_attrs[j]); return ret; } MODULE_DESCRIPTION("ALLWINNERTECH axp regulator"); MODULE_AUTHOR("pannan"); MODULE_LICENSE("GPL");