162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * twl-regulator.c -- support regulators in twl4030/twl6030 family chips 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2008 David Brownell 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/string.h> 1062306a36Sopenharmony_ci#include <linux/slab.h> 1162306a36Sopenharmony_ci#include <linux/init.h> 1262306a36Sopenharmony_ci#include <linux/err.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci#include <linux/of.h> 1562306a36Sopenharmony_ci#include <linux/regulator/driver.h> 1662306a36Sopenharmony_ci#include <linux/regulator/machine.h> 1762306a36Sopenharmony_ci#include <linux/regulator/of_regulator.h> 1862306a36Sopenharmony_ci#include <linux/mfd/twl.h> 1962306a36Sopenharmony_ci#include <linux/delay.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* 2262306a36Sopenharmony_ci * The TWL4030/TW5030/TPS659x0 family chips include power management, a 2362306a36Sopenharmony_ci * USB OTG transceiver, an RTC, ADC, PWM, and lots more. Some versions 2462306a36Sopenharmony_ci * include an audio codec, battery charger, and more voltage regulators. 2562306a36Sopenharmony_ci * These chips are often used in OMAP-based systems. 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * This driver implements software-based resource control for various 2862306a36Sopenharmony_ci * voltage regulators. This is usually augmented with state machine 2962306a36Sopenharmony_ci * based control. 3062306a36Sopenharmony_ci */ 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistruct twlreg_info { 3362306a36Sopenharmony_ci /* start of regulator's PM_RECEIVER control register bank */ 3462306a36Sopenharmony_ci u8 base; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci /* twl resource ID, for resource control state machine */ 3762306a36Sopenharmony_ci u8 id; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci /* voltage in mV = table[VSEL]; table_len must be a power-of-two */ 4062306a36Sopenharmony_ci u8 table_len; 4162306a36Sopenharmony_ci const u16 *table; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci /* State REMAP default configuration */ 4462306a36Sopenharmony_ci u8 remap; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci /* used by regulator core */ 4762306a36Sopenharmony_ci struct regulator_desc desc; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci /* chip specific features */ 5062306a36Sopenharmony_ci unsigned long features; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci /* data passed from board for external get/set voltage */ 5362306a36Sopenharmony_ci void *data; 5462306a36Sopenharmony_ci}; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci/* LDO control registers ... offset is from the base of its register bank. 5862306a36Sopenharmony_ci * The first three registers of all power resource banks help hardware to 5962306a36Sopenharmony_ci * manage the various resource groups. 6062306a36Sopenharmony_ci */ 6162306a36Sopenharmony_ci/* Common offset in TWL4030/6030 */ 6262306a36Sopenharmony_ci#define VREG_GRP 0 6362306a36Sopenharmony_ci/* TWL4030 register offsets */ 6462306a36Sopenharmony_ci#define VREG_TYPE 1 6562306a36Sopenharmony_ci#define VREG_REMAP 2 6662306a36Sopenharmony_ci#define VREG_DEDICATED 3 /* LDO control */ 6762306a36Sopenharmony_ci#define VREG_VOLTAGE_SMPS_4030 9 6862306a36Sopenharmony_ci/* TWL6030 register offsets */ 6962306a36Sopenharmony_ci#define VREG_TRANS 1 7062306a36Sopenharmony_ci#define VREG_STATE 2 7162306a36Sopenharmony_ci#define VREG_VOLTAGE 3 7262306a36Sopenharmony_ci#define VREG_VOLTAGE_SMPS 4 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic inline int 7562306a36Sopenharmony_citwlreg_read(struct twlreg_info *info, unsigned slave_subgp, unsigned offset) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci u8 value; 7862306a36Sopenharmony_ci int status; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci status = twl_i2c_read_u8(slave_subgp, 8162306a36Sopenharmony_ci &value, info->base + offset); 8262306a36Sopenharmony_ci return (status < 0) ? status : value; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic inline int 8662306a36Sopenharmony_citwlreg_write(struct twlreg_info *info, unsigned slave_subgp, unsigned offset, 8762306a36Sopenharmony_ci u8 value) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci return twl_i2c_write_u8(slave_subgp, 9062306a36Sopenharmony_ci value, info->base + offset); 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci/*----------------------------------------------------------------------*/ 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci/* generic power resource operations, which work on all regulators */ 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic int twlreg_grp(struct regulator_dev *rdev) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci return twlreg_read(rdev_get_drvdata(rdev), TWL_MODULE_PM_RECEIVER, 10062306a36Sopenharmony_ci VREG_GRP); 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci/* 10462306a36Sopenharmony_ci * Enable/disable regulators by joining/leaving the P1 (processor) group. 10562306a36Sopenharmony_ci * We assume nobody else is updating the DEV_GRP registers. 10662306a36Sopenharmony_ci */ 10762306a36Sopenharmony_ci/* definition for 4030 family */ 10862306a36Sopenharmony_ci#define P3_GRP_4030 BIT(7) /* "peripherals" */ 10962306a36Sopenharmony_ci#define P2_GRP_4030 BIT(6) /* secondary processor, modem, etc */ 11062306a36Sopenharmony_ci#define P1_GRP_4030 BIT(5) /* CPU/Linux */ 11162306a36Sopenharmony_ci/* definition for 6030 family */ 11262306a36Sopenharmony_ci#define P3_GRP_6030 BIT(2) /* secondary processor, modem, etc */ 11362306a36Sopenharmony_ci#define P2_GRP_6030 BIT(1) /* "peripherals" */ 11462306a36Sopenharmony_ci#define P1_GRP_6030 BIT(0) /* CPU/Linux */ 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistatic int twl4030reg_is_enabled(struct regulator_dev *rdev) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci int state = twlreg_grp(rdev); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (state < 0) 12162306a36Sopenharmony_ci return state; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci return state & P1_GRP_4030; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci#define PB_I2C_BUSY BIT(0) 12762306a36Sopenharmony_ci#define PB_I2C_BWEN BIT(1) 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci/* Wait until buffer empty/ready to send a word on power bus. */ 13062306a36Sopenharmony_cistatic int twl4030_wait_pb_ready(void) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci int ret; 13462306a36Sopenharmony_ci int timeout = 10; 13562306a36Sopenharmony_ci u8 val; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci do { 13862306a36Sopenharmony_ci ret = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &val, 13962306a36Sopenharmony_ci TWL4030_PM_MASTER_PB_CFG); 14062306a36Sopenharmony_ci if (ret < 0) 14162306a36Sopenharmony_ci return ret; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci if (!(val & PB_I2C_BUSY)) 14462306a36Sopenharmony_ci return 0; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci mdelay(1); 14762306a36Sopenharmony_ci timeout--; 14862306a36Sopenharmony_ci } while (timeout); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci return -ETIMEDOUT; 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci/* Send a word over the powerbus */ 15462306a36Sopenharmony_cistatic int twl4030_send_pb_msg(unsigned msg) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci u8 val; 15762306a36Sopenharmony_ci int ret; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci /* save powerbus configuration */ 16062306a36Sopenharmony_ci ret = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &val, 16162306a36Sopenharmony_ci TWL4030_PM_MASTER_PB_CFG); 16262306a36Sopenharmony_ci if (ret < 0) 16362306a36Sopenharmony_ci return ret; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci /* Enable i2c access to powerbus */ 16662306a36Sopenharmony_ci ret = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, val | PB_I2C_BWEN, 16762306a36Sopenharmony_ci TWL4030_PM_MASTER_PB_CFG); 16862306a36Sopenharmony_ci if (ret < 0) 16962306a36Sopenharmony_ci return ret; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci ret = twl4030_wait_pb_ready(); 17262306a36Sopenharmony_ci if (ret < 0) 17362306a36Sopenharmony_ci return ret; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci ret = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, msg >> 8, 17662306a36Sopenharmony_ci TWL4030_PM_MASTER_PB_WORD_MSB); 17762306a36Sopenharmony_ci if (ret < 0) 17862306a36Sopenharmony_ci return ret; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci ret = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, msg & 0xff, 18162306a36Sopenharmony_ci TWL4030_PM_MASTER_PB_WORD_LSB); 18262306a36Sopenharmony_ci if (ret < 0) 18362306a36Sopenharmony_ci return ret; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci ret = twl4030_wait_pb_ready(); 18662306a36Sopenharmony_ci if (ret < 0) 18762306a36Sopenharmony_ci return ret; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci /* Restore powerbus configuration */ 19062306a36Sopenharmony_ci return twl_i2c_write_u8(TWL_MODULE_PM_MASTER, val, 19162306a36Sopenharmony_ci TWL4030_PM_MASTER_PB_CFG); 19262306a36Sopenharmony_ci} 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_cistatic int twl4030reg_enable(struct regulator_dev *rdev) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci struct twlreg_info *info = rdev_get_drvdata(rdev); 19762306a36Sopenharmony_ci int grp; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci grp = twlreg_grp(rdev); 20062306a36Sopenharmony_ci if (grp < 0) 20162306a36Sopenharmony_ci return grp; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci grp |= P1_GRP_4030; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci return twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_GRP, grp); 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic int twl4030reg_disable(struct regulator_dev *rdev) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci struct twlreg_info *info = rdev_get_drvdata(rdev); 21162306a36Sopenharmony_ci int grp; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci grp = twlreg_grp(rdev); 21462306a36Sopenharmony_ci if (grp < 0) 21562306a36Sopenharmony_ci return grp; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci grp &= ~(P1_GRP_4030 | P2_GRP_4030 | P3_GRP_4030); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci return twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_GRP, grp); 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic int twl4030reg_get_status(struct regulator_dev *rdev) 22362306a36Sopenharmony_ci{ 22462306a36Sopenharmony_ci int state = twlreg_grp(rdev); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci if (state < 0) 22762306a36Sopenharmony_ci return state; 22862306a36Sopenharmony_ci state &= 0x0f; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci /* assume state != WARM_RESET; we'd not be running... */ 23162306a36Sopenharmony_ci if (!state) 23262306a36Sopenharmony_ci return REGULATOR_STATUS_OFF; 23362306a36Sopenharmony_ci return (state & BIT(3)) 23462306a36Sopenharmony_ci ? REGULATOR_STATUS_NORMAL 23562306a36Sopenharmony_ci : REGULATOR_STATUS_STANDBY; 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic int twl4030reg_set_mode(struct regulator_dev *rdev, unsigned mode) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci struct twlreg_info *info = rdev_get_drvdata(rdev); 24162306a36Sopenharmony_ci unsigned message; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci /* We can only set the mode through state machine commands... */ 24462306a36Sopenharmony_ci switch (mode) { 24562306a36Sopenharmony_ci case REGULATOR_MODE_NORMAL: 24662306a36Sopenharmony_ci message = MSG_SINGULAR(DEV_GRP_P1, info->id, RES_STATE_ACTIVE); 24762306a36Sopenharmony_ci break; 24862306a36Sopenharmony_ci case REGULATOR_MODE_STANDBY: 24962306a36Sopenharmony_ci message = MSG_SINGULAR(DEV_GRP_P1, info->id, RES_STATE_SLEEP); 25062306a36Sopenharmony_ci break; 25162306a36Sopenharmony_ci default: 25262306a36Sopenharmony_ci return -EINVAL; 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci return twl4030_send_pb_msg(message); 25662306a36Sopenharmony_ci} 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_cistatic inline unsigned int twl4030reg_map_mode(unsigned int mode) 25962306a36Sopenharmony_ci{ 26062306a36Sopenharmony_ci switch (mode) { 26162306a36Sopenharmony_ci case RES_STATE_ACTIVE: 26262306a36Sopenharmony_ci return REGULATOR_MODE_NORMAL; 26362306a36Sopenharmony_ci case RES_STATE_SLEEP: 26462306a36Sopenharmony_ci return REGULATOR_MODE_STANDBY; 26562306a36Sopenharmony_ci default: 26662306a36Sopenharmony_ci return REGULATOR_MODE_INVALID; 26762306a36Sopenharmony_ci } 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci/*----------------------------------------------------------------------*/ 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci/* 27362306a36Sopenharmony_ci * Support for adjustable-voltage LDOs uses a four bit (or less) voltage 27462306a36Sopenharmony_ci * select field in its control register. We use tables indexed by VSEL 27562306a36Sopenharmony_ci * to record voltages in milliVolts. (Accuracy is about three percent.) 27662306a36Sopenharmony_ci * 27762306a36Sopenharmony_ci * Note that VSEL values for VAUX2 changed in twl5030 and newer silicon; 27862306a36Sopenharmony_ci * currently handled by listing two slightly different VAUX2 regulators, 27962306a36Sopenharmony_ci * only one of which will be configured. 28062306a36Sopenharmony_ci * 28162306a36Sopenharmony_ci * VSEL values documented as "TI cannot support these values" are flagged 28262306a36Sopenharmony_ci * in these tables as UNSUP() values; we normally won't assign them. 28362306a36Sopenharmony_ci * 28462306a36Sopenharmony_ci * VAUX3 at 3V is incorrectly listed in some TI manuals as unsupported. 28562306a36Sopenharmony_ci * TI are revising the twl5030/tps659x0 specs to support that 3.0V setting. 28662306a36Sopenharmony_ci */ 28762306a36Sopenharmony_ci#define UNSUP_MASK 0x8000 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci#define UNSUP(x) (UNSUP_MASK | (x)) 29062306a36Sopenharmony_ci#define IS_UNSUP(info, x) \ 29162306a36Sopenharmony_ci ((UNSUP_MASK & (x)) && \ 29262306a36Sopenharmony_ci !((info)->features & TWL4030_ALLOW_UNSUPPORTED)) 29362306a36Sopenharmony_ci#define LDO_MV(x) (~UNSUP_MASK & (x)) 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic const u16 VAUX1_VSEL_table[] = { 29762306a36Sopenharmony_ci UNSUP(1500), UNSUP(1800), 2500, 2800, 29862306a36Sopenharmony_ci 3000, 3000, 3000, 3000, 29962306a36Sopenharmony_ci}; 30062306a36Sopenharmony_cistatic const u16 VAUX2_4030_VSEL_table[] = { 30162306a36Sopenharmony_ci UNSUP(1000), UNSUP(1000), UNSUP(1200), 1300, 30262306a36Sopenharmony_ci 1500, 1800, UNSUP(1850), 2500, 30362306a36Sopenharmony_ci UNSUP(2600), 2800, UNSUP(2850), UNSUP(3000), 30462306a36Sopenharmony_ci UNSUP(3150), UNSUP(3150), UNSUP(3150), UNSUP(3150), 30562306a36Sopenharmony_ci}; 30662306a36Sopenharmony_cistatic const u16 VAUX2_VSEL_table[] = { 30762306a36Sopenharmony_ci 1700, 1700, 1900, 1300, 30862306a36Sopenharmony_ci 1500, 1800, 2000, 2500, 30962306a36Sopenharmony_ci 2100, 2800, 2200, 2300, 31062306a36Sopenharmony_ci 2400, 2400, 2400, 2400, 31162306a36Sopenharmony_ci}; 31262306a36Sopenharmony_cistatic const u16 VAUX3_VSEL_table[] = { 31362306a36Sopenharmony_ci 1500, 1800, 2500, 2800, 31462306a36Sopenharmony_ci 3000, 3000, 3000, 3000, 31562306a36Sopenharmony_ci}; 31662306a36Sopenharmony_cistatic const u16 VAUX4_VSEL_table[] = { 31762306a36Sopenharmony_ci 700, 1000, 1200, UNSUP(1300), 31862306a36Sopenharmony_ci 1500, 1800, UNSUP(1850), 2500, 31962306a36Sopenharmony_ci UNSUP(2600), 2800, UNSUP(2850), UNSUP(3000), 32062306a36Sopenharmony_ci UNSUP(3150), UNSUP(3150), UNSUP(3150), UNSUP(3150), 32162306a36Sopenharmony_ci}; 32262306a36Sopenharmony_cistatic const u16 VMMC1_VSEL_table[] = { 32362306a36Sopenharmony_ci 1850, 2850, 3000, 3150, 32462306a36Sopenharmony_ci}; 32562306a36Sopenharmony_cistatic const u16 VMMC2_VSEL_table[] = { 32662306a36Sopenharmony_ci UNSUP(1000), UNSUP(1000), UNSUP(1200), UNSUP(1300), 32762306a36Sopenharmony_ci UNSUP(1500), UNSUP(1800), 1850, UNSUP(2500), 32862306a36Sopenharmony_ci 2600, 2800, 2850, 3000, 32962306a36Sopenharmony_ci 3150, 3150, 3150, 3150, 33062306a36Sopenharmony_ci}; 33162306a36Sopenharmony_cistatic const u16 VPLL1_VSEL_table[] = { 33262306a36Sopenharmony_ci 1000, 1200, 1300, 1800, 33362306a36Sopenharmony_ci UNSUP(2800), UNSUP(3000), UNSUP(3000), UNSUP(3000), 33462306a36Sopenharmony_ci}; 33562306a36Sopenharmony_cistatic const u16 VPLL2_VSEL_table[] = { 33662306a36Sopenharmony_ci 700, 1000, 1200, 1300, 33762306a36Sopenharmony_ci UNSUP(1500), 1800, UNSUP(1850), UNSUP(2500), 33862306a36Sopenharmony_ci UNSUP(2600), UNSUP(2800), UNSUP(2850), UNSUP(3000), 33962306a36Sopenharmony_ci UNSUP(3150), UNSUP(3150), UNSUP(3150), UNSUP(3150), 34062306a36Sopenharmony_ci}; 34162306a36Sopenharmony_cistatic const u16 VSIM_VSEL_table[] = { 34262306a36Sopenharmony_ci UNSUP(1000), UNSUP(1200), UNSUP(1300), 1800, 34362306a36Sopenharmony_ci 2800, 3000, 3000, 3000, 34462306a36Sopenharmony_ci}; 34562306a36Sopenharmony_cistatic const u16 VDAC_VSEL_table[] = { 34662306a36Sopenharmony_ci 1200, 1300, 1800, 1800, 34762306a36Sopenharmony_ci}; 34862306a36Sopenharmony_cistatic const u16 VIO_VSEL_table[] = { 34962306a36Sopenharmony_ci 1800, 1850, 35062306a36Sopenharmony_ci}; 35162306a36Sopenharmony_cistatic const u16 VINTANA2_VSEL_table[] = { 35262306a36Sopenharmony_ci 2500, 2750, 35362306a36Sopenharmony_ci}; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci/* 600mV to 1450mV in 12.5 mV steps */ 35662306a36Sopenharmony_cistatic const struct linear_range VDD1_ranges[] = { 35762306a36Sopenharmony_ci REGULATOR_LINEAR_RANGE(600000, 0, 68, 12500) 35862306a36Sopenharmony_ci}; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci/* 600mV to 1450mV in 12.5 mV steps, everything above = 1500mV */ 36162306a36Sopenharmony_cistatic const struct linear_range VDD2_ranges[] = { 36262306a36Sopenharmony_ci REGULATOR_LINEAR_RANGE(600000, 0, 68, 12500), 36362306a36Sopenharmony_ci REGULATOR_LINEAR_RANGE(1500000, 69, 69, 12500) 36462306a36Sopenharmony_ci}; 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_cistatic int twl4030ldo_list_voltage(struct regulator_dev *rdev, unsigned index) 36762306a36Sopenharmony_ci{ 36862306a36Sopenharmony_ci struct twlreg_info *info = rdev_get_drvdata(rdev); 36962306a36Sopenharmony_ci int mV = info->table[index]; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci return IS_UNSUP(info, mV) ? 0 : (LDO_MV(mV) * 1000); 37262306a36Sopenharmony_ci} 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_cistatic int 37562306a36Sopenharmony_citwl4030ldo_set_voltage_sel(struct regulator_dev *rdev, unsigned selector) 37662306a36Sopenharmony_ci{ 37762306a36Sopenharmony_ci struct twlreg_info *info = rdev_get_drvdata(rdev); 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci return twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE, 38062306a36Sopenharmony_ci selector); 38162306a36Sopenharmony_ci} 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_cistatic int twl4030ldo_get_voltage_sel(struct regulator_dev *rdev) 38462306a36Sopenharmony_ci{ 38562306a36Sopenharmony_ci struct twlreg_info *info = rdev_get_drvdata(rdev); 38662306a36Sopenharmony_ci int vsel = twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE); 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci if (vsel < 0) 38962306a36Sopenharmony_ci return vsel; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci vsel &= info->table_len - 1; 39262306a36Sopenharmony_ci return vsel; 39362306a36Sopenharmony_ci} 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_cistatic const struct regulator_ops twl4030ldo_ops = { 39662306a36Sopenharmony_ci .list_voltage = twl4030ldo_list_voltage, 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci .set_voltage_sel = twl4030ldo_set_voltage_sel, 39962306a36Sopenharmony_ci .get_voltage_sel = twl4030ldo_get_voltage_sel, 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci .enable = twl4030reg_enable, 40262306a36Sopenharmony_ci .disable = twl4030reg_disable, 40362306a36Sopenharmony_ci .is_enabled = twl4030reg_is_enabled, 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci .set_mode = twl4030reg_set_mode, 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci .get_status = twl4030reg_get_status, 40862306a36Sopenharmony_ci}; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_cistatic int 41162306a36Sopenharmony_citwl4030smps_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV, 41262306a36Sopenharmony_ci unsigned *selector) 41362306a36Sopenharmony_ci{ 41462306a36Sopenharmony_ci struct twlreg_info *info = rdev_get_drvdata(rdev); 41562306a36Sopenharmony_ci int vsel = DIV_ROUND_UP(min_uV - 600000, 12500); 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE_SMPS_4030, vsel); 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci return 0; 42062306a36Sopenharmony_ci} 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_cistatic int twl4030smps_get_voltage(struct regulator_dev *rdev) 42362306a36Sopenharmony_ci{ 42462306a36Sopenharmony_ci struct twlreg_info *info = rdev_get_drvdata(rdev); 42562306a36Sopenharmony_ci int vsel; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci vsel = twlreg_read(info, TWL_MODULE_PM_RECEIVER, 42862306a36Sopenharmony_ci VREG_VOLTAGE_SMPS_4030); 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci return vsel * 12500 + 600000; 43162306a36Sopenharmony_ci} 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_cistatic const struct regulator_ops twl4030smps_ops = { 43462306a36Sopenharmony_ci .list_voltage = regulator_list_voltage_linear_range, 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci .set_voltage = twl4030smps_set_voltage, 43762306a36Sopenharmony_ci .get_voltage = twl4030smps_get_voltage, 43862306a36Sopenharmony_ci}; 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci/*----------------------------------------------------------------------*/ 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_cistatic const struct regulator_ops twl4030fixed_ops = { 44362306a36Sopenharmony_ci .list_voltage = regulator_list_voltage_linear, 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci .enable = twl4030reg_enable, 44662306a36Sopenharmony_ci .disable = twl4030reg_disable, 44762306a36Sopenharmony_ci .is_enabled = twl4030reg_is_enabled, 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci .set_mode = twl4030reg_set_mode, 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci .get_status = twl4030reg_get_status, 45262306a36Sopenharmony_ci}; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci/*----------------------------------------------------------------------*/ 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci#define TWL4030_ADJUSTABLE_LDO(label, offset, num, turnon_delay, remap_conf) \ 45762306a36Sopenharmony_cistatic const struct twlreg_info TWL4030_INFO_##label = { \ 45862306a36Sopenharmony_ci .base = offset, \ 45962306a36Sopenharmony_ci .id = num, \ 46062306a36Sopenharmony_ci .table_len = ARRAY_SIZE(label##_VSEL_table), \ 46162306a36Sopenharmony_ci .table = label##_VSEL_table, \ 46262306a36Sopenharmony_ci .remap = remap_conf, \ 46362306a36Sopenharmony_ci .desc = { \ 46462306a36Sopenharmony_ci .name = #label, \ 46562306a36Sopenharmony_ci .id = TWL4030_REG_##label, \ 46662306a36Sopenharmony_ci .n_voltages = ARRAY_SIZE(label##_VSEL_table), \ 46762306a36Sopenharmony_ci .ops = &twl4030ldo_ops, \ 46862306a36Sopenharmony_ci .type = REGULATOR_VOLTAGE, \ 46962306a36Sopenharmony_ci .owner = THIS_MODULE, \ 47062306a36Sopenharmony_ci .enable_time = turnon_delay, \ 47162306a36Sopenharmony_ci .of_map_mode = twl4030reg_map_mode, \ 47262306a36Sopenharmony_ci }, \ 47362306a36Sopenharmony_ci } 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci#define TWL4030_ADJUSTABLE_SMPS(label, offset, num, turnon_delay, remap_conf, \ 47662306a36Sopenharmony_ci n_volt) \ 47762306a36Sopenharmony_cistatic const struct twlreg_info TWL4030_INFO_##label = { \ 47862306a36Sopenharmony_ci .base = offset, \ 47962306a36Sopenharmony_ci .id = num, \ 48062306a36Sopenharmony_ci .remap = remap_conf, \ 48162306a36Sopenharmony_ci .desc = { \ 48262306a36Sopenharmony_ci .name = #label, \ 48362306a36Sopenharmony_ci .id = TWL4030_REG_##label, \ 48462306a36Sopenharmony_ci .ops = &twl4030smps_ops, \ 48562306a36Sopenharmony_ci .type = REGULATOR_VOLTAGE, \ 48662306a36Sopenharmony_ci .owner = THIS_MODULE, \ 48762306a36Sopenharmony_ci .enable_time = turnon_delay, \ 48862306a36Sopenharmony_ci .of_map_mode = twl4030reg_map_mode, \ 48962306a36Sopenharmony_ci .n_voltages = n_volt, \ 49062306a36Sopenharmony_ci .n_linear_ranges = ARRAY_SIZE(label ## _ranges), \ 49162306a36Sopenharmony_ci .linear_ranges = label ## _ranges, \ 49262306a36Sopenharmony_ci }, \ 49362306a36Sopenharmony_ci } 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci#define TWL4030_FIXED_LDO(label, offset, mVolts, num, turnon_delay, \ 49662306a36Sopenharmony_ci remap_conf) \ 49762306a36Sopenharmony_cistatic const struct twlreg_info TWLFIXED_INFO_##label = { \ 49862306a36Sopenharmony_ci .base = offset, \ 49962306a36Sopenharmony_ci .id = num, \ 50062306a36Sopenharmony_ci .remap = remap_conf, \ 50162306a36Sopenharmony_ci .desc = { \ 50262306a36Sopenharmony_ci .name = #label, \ 50362306a36Sopenharmony_ci .id = TWL4030##_REG_##label, \ 50462306a36Sopenharmony_ci .n_voltages = 1, \ 50562306a36Sopenharmony_ci .ops = &twl4030fixed_ops, \ 50662306a36Sopenharmony_ci .type = REGULATOR_VOLTAGE, \ 50762306a36Sopenharmony_ci .owner = THIS_MODULE, \ 50862306a36Sopenharmony_ci .min_uV = mVolts * 1000, \ 50962306a36Sopenharmony_ci .enable_time = turnon_delay, \ 51062306a36Sopenharmony_ci .of_map_mode = twl4030reg_map_mode, \ 51162306a36Sopenharmony_ci }, \ 51262306a36Sopenharmony_ci } 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci/* 51562306a36Sopenharmony_ci * We list regulators here if systems need some level of 51662306a36Sopenharmony_ci * software control over them after boot. 51762306a36Sopenharmony_ci */ 51862306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VAUX1, 0x17, 1, 100, 0x08); 51962306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VAUX2_4030, 0x1b, 2, 100, 0x08); 52062306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VAUX2, 0x1b, 2, 100, 0x08); 52162306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VAUX3, 0x1f, 3, 100, 0x08); 52262306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VAUX4, 0x23, 4, 100, 0x08); 52362306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VMMC1, 0x27, 5, 100, 0x08); 52462306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VMMC2, 0x2b, 6, 100, 0x08); 52562306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VPLL1, 0x2f, 7, 100, 0x00); 52662306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VPLL2, 0x33, 8, 100, 0x08); 52762306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VSIM, 0x37, 9, 100, 0x00); 52862306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VDAC, 0x3b, 10, 100, 0x08); 52962306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VINTANA2, 0x43, 12, 100, 0x08); 53062306a36Sopenharmony_ciTWL4030_ADJUSTABLE_LDO(VIO, 0x4b, 14, 1000, 0x08); 53162306a36Sopenharmony_ciTWL4030_ADJUSTABLE_SMPS(VDD1, 0x55, 15, 1000, 0x08, 68); 53262306a36Sopenharmony_ciTWL4030_ADJUSTABLE_SMPS(VDD2, 0x63, 16, 1000, 0x08, 69); 53362306a36Sopenharmony_ci/* VUSBCP is managed *only* by the USB subchip */ 53462306a36Sopenharmony_ciTWL4030_FIXED_LDO(VINTANA1, 0x3f, 1500, 11, 100, 0x08); 53562306a36Sopenharmony_ciTWL4030_FIXED_LDO(VINTDIG, 0x47, 1500, 13, 100, 0x08); 53662306a36Sopenharmony_ciTWL4030_FIXED_LDO(VUSB1V5, 0x71, 1500, 17, 100, 0x08); 53762306a36Sopenharmony_ciTWL4030_FIXED_LDO(VUSB1V8, 0x74, 1800, 18, 100, 0x08); 53862306a36Sopenharmony_ciTWL4030_FIXED_LDO(VUSB3V1, 0x77, 3100, 19, 150, 0x08); 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci#define TWL_OF_MATCH(comp, family, label) \ 54162306a36Sopenharmony_ci { \ 54262306a36Sopenharmony_ci .compatible = comp, \ 54362306a36Sopenharmony_ci .data = &family##_INFO_##label, \ 54462306a36Sopenharmony_ci } 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci#define TWL4030_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWL4030, label) 54762306a36Sopenharmony_ci#define TWL6030_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWL6030, label) 54862306a36Sopenharmony_ci#define TWL6032_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWL6032, label) 54962306a36Sopenharmony_ci#define TWLFIXED_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWLFIXED, label) 55062306a36Sopenharmony_ci#define TWLSMPS_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWLSMPS, label) 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_cistatic const struct of_device_id twl_of_match[] = { 55362306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vaux1", VAUX1), 55462306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vaux2", VAUX2_4030), 55562306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl5030-vaux2", VAUX2), 55662306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vaux3", VAUX3), 55762306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vaux4", VAUX4), 55862306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vmmc1", VMMC1), 55962306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vmmc2", VMMC2), 56062306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vpll1", VPLL1), 56162306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vpll2", VPLL2), 56262306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vsim", VSIM), 56362306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vdac", VDAC), 56462306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vintana2", VINTANA2), 56562306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vio", VIO), 56662306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vdd1", VDD1), 56762306a36Sopenharmony_ci TWL4030_OF_MATCH("ti,twl4030-vdd2", VDD2), 56862306a36Sopenharmony_ci TWLFIXED_OF_MATCH("ti,twl4030-vintana1", VINTANA1), 56962306a36Sopenharmony_ci TWLFIXED_OF_MATCH("ti,twl4030-vintdig", VINTDIG), 57062306a36Sopenharmony_ci TWLFIXED_OF_MATCH("ti,twl4030-vusb1v5", VUSB1V5), 57162306a36Sopenharmony_ci TWLFIXED_OF_MATCH("ti,twl4030-vusb1v8", VUSB1V8), 57262306a36Sopenharmony_ci TWLFIXED_OF_MATCH("ti,twl4030-vusb3v1", VUSB3V1), 57362306a36Sopenharmony_ci {}, 57462306a36Sopenharmony_ci}; 57562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, twl_of_match); 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_cistatic int twlreg_probe(struct platform_device *pdev) 57862306a36Sopenharmony_ci{ 57962306a36Sopenharmony_ci int id; 58062306a36Sopenharmony_ci struct twlreg_info *info; 58162306a36Sopenharmony_ci const struct twlreg_info *template; 58262306a36Sopenharmony_ci struct regulator_init_data *initdata; 58362306a36Sopenharmony_ci struct regulation_constraints *c; 58462306a36Sopenharmony_ci struct regulator_dev *rdev; 58562306a36Sopenharmony_ci struct regulator_config config = { }; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci template = of_device_get_match_data(&pdev->dev); 58862306a36Sopenharmony_ci if (!template) 58962306a36Sopenharmony_ci return -ENODEV; 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci id = template->desc.id; 59262306a36Sopenharmony_ci initdata = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node, 59362306a36Sopenharmony_ci &template->desc); 59462306a36Sopenharmony_ci if (!initdata) 59562306a36Sopenharmony_ci return -EINVAL; 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci info = devm_kmemdup(&pdev->dev, template, sizeof(*info), GFP_KERNEL); 59862306a36Sopenharmony_ci if (!info) 59962306a36Sopenharmony_ci return -ENOMEM; 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci /* Constrain board-specific capabilities according to what 60262306a36Sopenharmony_ci * this driver and the chip itself can actually do. 60362306a36Sopenharmony_ci */ 60462306a36Sopenharmony_ci c = &initdata->constraints; 60562306a36Sopenharmony_ci c->valid_modes_mask &= REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; 60662306a36Sopenharmony_ci c->valid_ops_mask &= REGULATOR_CHANGE_VOLTAGE 60762306a36Sopenharmony_ci | REGULATOR_CHANGE_MODE 60862306a36Sopenharmony_ci | REGULATOR_CHANGE_STATUS; 60962306a36Sopenharmony_ci switch (id) { 61062306a36Sopenharmony_ci case TWL4030_REG_VIO: 61162306a36Sopenharmony_ci case TWL4030_REG_VDD1: 61262306a36Sopenharmony_ci case TWL4030_REG_VDD2: 61362306a36Sopenharmony_ci case TWL4030_REG_VPLL1: 61462306a36Sopenharmony_ci case TWL4030_REG_VINTANA1: 61562306a36Sopenharmony_ci case TWL4030_REG_VINTANA2: 61662306a36Sopenharmony_ci case TWL4030_REG_VINTDIG: 61762306a36Sopenharmony_ci c->always_on = true; 61862306a36Sopenharmony_ci break; 61962306a36Sopenharmony_ci default: 62062306a36Sopenharmony_ci break; 62162306a36Sopenharmony_ci } 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci config.dev = &pdev->dev; 62462306a36Sopenharmony_ci config.init_data = initdata; 62562306a36Sopenharmony_ci config.driver_data = info; 62662306a36Sopenharmony_ci config.of_node = pdev->dev.of_node; 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci rdev = devm_regulator_register(&pdev->dev, &info->desc, &config); 62962306a36Sopenharmony_ci if (IS_ERR(rdev)) { 63062306a36Sopenharmony_ci dev_err(&pdev->dev, "can't register %s, %ld\n", 63162306a36Sopenharmony_ci info->desc.name, PTR_ERR(rdev)); 63262306a36Sopenharmony_ci return PTR_ERR(rdev); 63362306a36Sopenharmony_ci } 63462306a36Sopenharmony_ci platform_set_drvdata(pdev, rdev); 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_REMAP, info->remap); 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci /* NOTE: many regulators support short-circuit IRQs (presentable 63962306a36Sopenharmony_ci * as REGULATOR_OVER_CURRENT notifications?) configured via: 64062306a36Sopenharmony_ci * - SC_CONFIG 64162306a36Sopenharmony_ci * - SC_DETECT1 (vintana2, vmmc1/2, vaux1/2/3/4) 64262306a36Sopenharmony_ci * - SC_DETECT2 (vusb, vdac, vio, vdd1/2, vpll2) 64362306a36Sopenharmony_ci * - IT_CONFIG 64462306a36Sopenharmony_ci */ 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_ci return 0; 64762306a36Sopenharmony_ci} 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ciMODULE_ALIAS("platform:twl4030_reg"); 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_cistatic struct platform_driver twlreg_driver = { 65262306a36Sopenharmony_ci .probe = twlreg_probe, 65362306a36Sopenharmony_ci /* NOTE: short name, to work around driver model truncation of 65462306a36Sopenharmony_ci * "twl_regulator.12" (and friends) to "twl_regulator.1". 65562306a36Sopenharmony_ci */ 65662306a36Sopenharmony_ci .driver = { 65762306a36Sopenharmony_ci .name = "twl4030_reg", 65862306a36Sopenharmony_ci .probe_type = PROBE_PREFER_ASYNCHRONOUS, 65962306a36Sopenharmony_ci .of_match_table = of_match_ptr(twl_of_match), 66062306a36Sopenharmony_ci }, 66162306a36Sopenharmony_ci}; 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_cistatic int __init twlreg_init(void) 66462306a36Sopenharmony_ci{ 66562306a36Sopenharmony_ci return platform_driver_register(&twlreg_driver); 66662306a36Sopenharmony_ci} 66762306a36Sopenharmony_cisubsys_initcall(twlreg_init); 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_cistatic void __exit twlreg_exit(void) 67062306a36Sopenharmony_ci{ 67162306a36Sopenharmony_ci platform_driver_unregister(&twlreg_driver); 67262306a36Sopenharmony_ci} 67362306a36Sopenharmony_cimodule_exit(twlreg_exit) 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ciMODULE_DESCRIPTION("TWL4030 regulator driver"); 67662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 677