571 lines
15 KiB
C
571 lines
15 KiB
C
/*
|
|
* max16989-regulator.c -- max16989 regulator driver
|
|
*
|
|
* Copyright (c) 2013-2016, NVIDIA CORPORATION. All rights reserved.
|
|
* Author: Laxman Dewangan <ldewangan@nvidia.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/regulator/machine.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/regulator/of_regulator.h>
|
|
#include <linux/of_gpio.h>
|
|
|
|
/* Register definitions */
|
|
#define MAX16989_ID_REG 0x0
|
|
#define MAX16989_VIDMAX_REG 0x2
|
|
#define MAX16989_TCONFIG_REG 0x3
|
|
#define MAX16989_STATUS_REG 0x4
|
|
#define MAX16989_CONFIG_REG 0x5
|
|
#define MAX16989_SLEW_REG 0x6
|
|
#define MAX16989_VID_REG 0x7
|
|
#define MAX16989_TRACKVID_REG 0x2B
|
|
#define MAX16989_MAX_REG (MAX16989_TRACKVID_REG + 1)
|
|
|
|
#define MAX16989_VID_MASK 0x7F
|
|
#define MAX16989_VOLTAGE_NSTEP 0x4E
|
|
|
|
#define MAX16989_CONFIG_VSTEP BIT(7)
|
|
#define MAX16989_CONFIG_FPWM BIT(3)
|
|
#define MAX16989_CONFIG_SS BIT(2)
|
|
#define MAX16989_CONFIG_SYNC_OUTPUT 0x02
|
|
|
|
#define MAX16989_TCONFIG_ENTRK BIT(7)
|
|
|
|
#define MAX16989_STATUS_INTERR BIT(7)
|
|
#define MAX16989_STATUS_TRKERR BIT(6)
|
|
#define MAX16989_STATUS_VRHOT BIT(5)
|
|
#define MAX16989_STATUS_UV BIT(4)
|
|
#define MAX16989_STATUS_OV BIT(3)
|
|
#define MAX16989_STATUS_OC BIT(2)
|
|
#define MAX16989_STATUS_VMERR BIT(1)
|
|
|
|
struct max16989_regulator_platform_data {
|
|
struct regulator_init_data *reg_init_data;
|
|
int voltage_step_uv;
|
|
bool enable_clock_ss;
|
|
int enable_gpio;
|
|
int fpwm_sync_gpio;
|
|
int tracking_device_i2c_address;
|
|
int slew_rate;
|
|
bool enable_external_fpwm;
|
|
bool enable_sync_output;
|
|
};
|
|
|
|
struct max16989_chip {
|
|
struct device *dev;
|
|
struct regulator_desc desc;
|
|
struct regulator_dev *rdev;
|
|
struct regmap *rmap;
|
|
struct max16989_regulator_platform_data *pdata;
|
|
bool vsel_volatile;
|
|
int max_uv;
|
|
int min_uv;
|
|
int max_vout_vsel;
|
|
};
|
|
|
|
static int max16989_set_mode(struct regulator_dev *rdev, unsigned int mode)
|
|
{
|
|
struct max16989_chip *mchip = rdev_get_drvdata(rdev);
|
|
struct max16989_regulator_platform_data *pdata = mchip->pdata;
|
|
int val;
|
|
|
|
if (gpio_is_valid(pdata->fpwm_sync_gpio)) {
|
|
val = (mode == REGULATOR_MODE_FAST) ? 1 : 0;
|
|
gpio_set_value(pdata->fpwm_sync_gpio, val);
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int max16989_get_mode(struct regulator_dev *rdev)
|
|
{
|
|
struct max16989_chip *mchip = rdev_get_drvdata(rdev);
|
|
struct max16989_regulator_platform_data *pdata = mchip->pdata;
|
|
int val;
|
|
unsigned int mode;
|
|
|
|
if (gpio_is_valid(pdata->fpwm_sync_gpio)) {
|
|
val = gpio_get_value(pdata->fpwm_sync_gpio);
|
|
mode = (val == 1) ? REGULATOR_MODE_FAST :
|
|
REGULATOR_MODE_NORMAL;
|
|
return mode;
|
|
}
|
|
return REGULATOR_MODE_FAST;
|
|
}
|
|
|
|
static int max16989_set_voltage_time_sel(struct regulator_dev *rdev,
|
|
unsigned int old_sel, unsigned int new_sel)
|
|
{
|
|
int old_volt, new_volt;
|
|
|
|
if (old_sel < new_sel)
|
|
return regulator_set_voltage_time_sel(rdev, old_sel, new_sel);
|
|
|
|
old_volt = regulator_list_voltage_linear(rdev, old_sel);
|
|
new_volt = regulator_list_voltage_linear(rdev, new_sel);
|
|
if (old_volt < 0)
|
|
return old_volt;
|
|
if (new_volt < 0)
|
|
return new_volt;
|
|
return DIV_ROUND_UP(abs(old_volt - new_volt), 1375);
|
|
}
|
|
|
|
static struct regulator_ops max16989_ops = {
|
|
.get_voltage_sel = regulator_get_voltage_sel_regmap,
|
|
.set_voltage_sel = regulator_set_voltage_sel_regmap,
|
|
.list_voltage = regulator_list_voltage_linear,
|
|
.map_voltage = regulator_map_voltage_linear,
|
|
.set_mode = max16989_set_mode,
|
|
.get_mode = max16989_get_mode,
|
|
.set_voltage_time_sel = max16989_set_voltage_time_sel,
|
|
};
|
|
static int slew_table_startup[] = {
|
|
22000, 11000, 5500, 11000, 5500, 22000, 22000, 11000, 5500, 5500};
|
|
static int slew_table_rising[] = {
|
|
22000, 22000, 22000, 11000, 11000, 22000, 22000, 22000, 22000, 5500};
|
|
|
|
static int max16989_init(struct max16989_chip *mchip,
|
|
struct max16989_regulator_platform_data *pdata)
|
|
{
|
|
struct regulator_init_data *ridata = pdata->reg_init_data;
|
|
unsigned int status;
|
|
unsigned int tconfig, config;
|
|
int startup, rising;
|
|
unsigned int slew;
|
|
int ret;
|
|
int vsel;
|
|
|
|
config = 0;
|
|
if (pdata->voltage_step_uv == -EINVAL) {
|
|
ret = regmap_read(mchip->rmap, MAX16989_CONFIG_REG, &config);
|
|
if (ret < 0) {
|
|
dev_err(mchip->dev,
|
|
"CONFIG reg read failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
pdata->voltage_step_uv = (config & MAX16989_CONFIG_VSTEP) ?
|
|
12500 : 10000;
|
|
|
|
pdata->enable_clock_ss = !!(config & MAX16989_CONFIG_SS);
|
|
pdata->enable_sync_output = ((config & 0x3) ==
|
|
MAX16989_CONFIG_SYNC_OUTPUT);
|
|
config = 0;
|
|
}
|
|
|
|
if (pdata->voltage_step_uv == 12500)
|
|
config |= MAX16989_CONFIG_VSTEP;
|
|
if (pdata->enable_clock_ss)
|
|
config |= MAX16989_CONFIG_SS;
|
|
if (pdata->enable_sync_output)
|
|
config |= MAX16989_CONFIG_SYNC_OUTPUT;
|
|
else if (!pdata->enable_external_fpwm)
|
|
config |= MAX16989_CONFIG_FPWM;
|
|
|
|
if (gpio_is_valid(pdata->fpwm_sync_gpio)) {
|
|
ret = devm_gpio_request_one(mchip->dev, pdata->fpwm_sync_gpio,
|
|
GPIOF_OUT_INIT_HIGH, "max16989-fpwm");
|
|
if (ret < 0) {
|
|
dev_err(mchip->dev,
|
|
"gpio_request for gpio %d failed: %d\n",
|
|
pdata->fpwm_sync_gpio, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
ret = regmap_write(mchip->rmap, MAX16989_CONFIG_REG, config);
|
|
if (ret < 0) {
|
|
dev_err(mchip->dev, "CONFIG write failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
mchip->max_uv = (pdata->voltage_step_uv == 12500) ? 1587500 : 1270000;
|
|
mchip->min_uv = (pdata->voltage_step_uv == 12500) ? 625000 : 500000;
|
|
mchip->max_vout_vsel = MAX16989_VOLTAGE_NSTEP;
|
|
if (ridata) {
|
|
if (!ridata->constraints.max_uV ||
|
|
(ridata->constraints.max_uV > mchip->max_uv))
|
|
ridata->constraints.max_uV = mchip->max_uv;
|
|
if (ridata->constraints.max_uV <= mchip->min_uv)
|
|
ridata->constraints.max_uV = mchip->max_uv;
|
|
|
|
if (!ridata->constraints.min_uV ||
|
|
(ridata->constraints.min_uV < mchip->min_uv))
|
|
ridata->constraints.min_uV = mchip->min_uv;
|
|
if (ridata->constraints.min_uV > mchip->max_uv)
|
|
ridata->constraints.min_uV = mchip->min_uv;
|
|
|
|
vsel = DIV_ROUND_UP(ridata->constraints.max_uV - mchip->min_uv,
|
|
pdata->voltage_step_uv) + 0x1;
|
|
mchip->max_vout_vsel = vsel;
|
|
}
|
|
ret = regmap_write(mchip->rmap, MAX16989_VIDMAX_REG,
|
|
mchip->max_vout_vsel);
|
|
if (ret < 0) {
|
|
dev_err(mchip->dev, "VMAX write failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
tconfig = 0;
|
|
if (pdata->tracking_device_i2c_address &&
|
|
pdata->voltage_step_uv == 12500) {
|
|
switch (pdata->tracking_device_i2c_address) {
|
|
case 0x38:
|
|
tconfig = 0;
|
|
break;
|
|
case 0x3C:
|
|
tconfig = 1;
|
|
break;
|
|
case 0x78:
|
|
tconfig = 2;
|
|
break;
|
|
case 0x7C:
|
|
tconfig = 3;
|
|
break;
|
|
default:
|
|
dev_err(mchip->dev,
|
|
"Invalid tracking device address: %d\n",
|
|
pdata->tracking_device_i2c_address);
|
|
return -EINVAL;
|
|
}
|
|
tconfig = MAX16989_TCONFIG_ENTRK;
|
|
}
|
|
ret = regmap_write(mchip->rmap, MAX16989_TCONFIG_REG, tconfig);
|
|
if (ret < 0) {
|
|
dev_err(mchip->dev, "TCONFIG write failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (pdata->slew_rate == -EINVAL) {
|
|
slew = 0;
|
|
ret = regmap_read(mchip->rmap, MAX16989_SLEW_REG, &slew);
|
|
if (ret < 0) {
|
|
dev_err(mchip->dev, "SLEW reg read failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
slew = slew & 0xF;
|
|
if (slew > 0x9)
|
|
slew = 0x9;
|
|
startup = slew_table_startup[slew];
|
|
rising = slew_table_rising[slew];
|
|
goto slew_done;
|
|
}
|
|
startup = 22000;
|
|
rising = 22000;
|
|
slew = 0;
|
|
switch (pdata->slew_rate) {
|
|
case 0:
|
|
startup = 22000;
|
|
rising = 22000;
|
|
slew = 0;
|
|
break;
|
|
case 1:
|
|
startup = 11000;
|
|
rising = 22000;
|
|
slew = 1;
|
|
break;
|
|
case 2:
|
|
startup = 5500;
|
|
rising = 22000;
|
|
slew = 2;
|
|
break;
|
|
case 3:
|
|
startup = 11000;
|
|
rising = 11000;
|
|
slew = 3;
|
|
break;
|
|
case 4:
|
|
startup = 5500;
|
|
rising = 11000;
|
|
slew = 4;
|
|
break;
|
|
case 5:
|
|
startup = 5500;
|
|
rising = 5500;
|
|
slew = 9;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
ret = regmap_write(mchip->rmap, MAX16989_SLEW_REG, slew);
|
|
if (ret < 0) {
|
|
dev_err(mchip->dev, "SLEW write failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
slew_done:
|
|
if (pdata->voltage_step_uv == 12500) {
|
|
rising = DIV_ROUND_UP(rising * 5, 4);
|
|
startup = DIV_ROUND_UP(startup * 5, 4);
|
|
}
|
|
|
|
mchip->desc.ramp_delay = rising;
|
|
mchip->desc.enable_time = startup;
|
|
|
|
ret = regmap_read(mchip->rmap, MAX16989_STATUS_REG, &status);
|
|
|
|
if (ret < 0) {
|
|
dev_err(mchip->dev, "STATUS reg read failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (status & MAX16989_STATUS_INTERR)
|
|
dev_err(mchip->dev, "Device Internal error\n");
|
|
|
|
if (status & MAX16989_STATUS_TRKERR)
|
|
dev_err(mchip->dev, "Device Tracking error\n");
|
|
|
|
if (status & MAX16989_STATUS_VRHOT)
|
|
dev_err(mchip->dev, "Device thermal shutdown indication\n");
|
|
|
|
if (status & MAX16989_STATUS_UV)
|
|
dev_err(mchip->dev, "Device VOUT undervoltage\n");
|
|
|
|
if (status & MAX16989_STATUS_OV)
|
|
dev_err(mchip->dev, "Device VOUT overvoltage\n");
|
|
|
|
if (status & MAX16989_STATUS_OC)
|
|
dev_err(mchip->dev, "Device VOUT overcurrent\n");
|
|
|
|
if (status & MAX16989_STATUS_VMERR)
|
|
dev_err(mchip->dev, "Device VOUTMAX error\n");
|
|
return 0;
|
|
}
|
|
|
|
static const struct regmap_range max16989_rd_ranges[] = {
|
|
regmap_reg_range(MAX16989_ID_REG, MAX16989_ID_REG),
|
|
regmap_reg_range(MAX16989_VIDMAX_REG, MAX16989_VID_REG),
|
|
regmap_reg_range(MAX16989_TRACKVID_REG, MAX16989_TRACKVID_REG),
|
|
};
|
|
|
|
static const struct regmap_access_table max16989_rd_table = {
|
|
.yes_ranges = max16989_rd_ranges,
|
|
.n_yes_ranges = ARRAY_SIZE(max16989_rd_ranges),
|
|
};
|
|
|
|
static const struct regmap_range max16989_wr_ranges[] = {
|
|
regmap_reg_range(MAX16989_VIDMAX_REG, MAX16989_TCONFIG_REG),
|
|
regmap_reg_range(MAX16989_CONFIG_REG, MAX16989_VID_REG),
|
|
regmap_reg_range(MAX16989_TRACKVID_REG, MAX16989_TRACKVID_REG),
|
|
};
|
|
|
|
static const struct regmap_access_table max16989_wr_table = {
|
|
.yes_ranges = max16989_wr_ranges,
|
|
.n_yes_ranges = ARRAY_SIZE(max16989_wr_ranges),
|
|
};
|
|
|
|
static bool max16989_is_volatile_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
struct max16989_chip *mchip = dev_get_drvdata(dev);
|
|
|
|
switch (reg) {
|
|
case MAX16989_STATUS_REG:
|
|
case MAX16989_TRACKVID_REG:
|
|
return true;
|
|
case MAX16989_VID_REG:
|
|
return mchip->vsel_volatile;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const struct regmap_config max16989_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
.rd_table = &max16989_rd_table,
|
|
.wr_table = &max16989_wr_table,
|
|
.volatile_reg = max16989_is_volatile_reg,
|
|
.max_register = MAX16989_MAX_REG - 1,
|
|
.cache_type = REGCACHE_RBTREE,
|
|
};
|
|
|
|
static const struct of_device_id max16989_of_match[] = {
|
|
{ .compatible = "maxim,max16989", },
|
|
{ .compatible = "maxim,max16989-new", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, max16989_of_match);
|
|
|
|
static struct max16989_regulator_platform_data *
|
|
of_get_max16989_platform_data(struct device *dev,
|
|
struct max16989_chip *mchip)
|
|
{
|
|
struct max16989_regulator_platform_data *pdata;
|
|
struct device_node *np = dev->of_node;
|
|
u32 pval;
|
|
int ret;
|
|
|
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return NULL;
|
|
|
|
pdata->reg_init_data = of_get_regulator_init_data(dev, dev->of_node,
|
|
&mchip->desc);
|
|
if (!pdata->reg_init_data) {
|
|
dev_err(dev, "Not able to get OF regulator init data\n");
|
|
return NULL;
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "regulator-voltage-steps", &pval);
|
|
pdata->voltage_step_uv = (ret) ? -EINVAL : pval;
|
|
|
|
pdata->enable_sync_output = of_property_read_bool(np,
|
|
"maxim,enable-sync-output");
|
|
|
|
pdata->enable_clock_ss = of_property_read_bool(np,
|
|
"maxim,enable-clk-spread-spectrum");
|
|
|
|
pdata->enable_gpio = of_get_named_gpio(np, "maxim,enable-gpio", 0);
|
|
|
|
pdata->fpwm_sync_gpio = of_get_named_gpio(np,
|
|
"maxim,fpwm-sync-gpio", 0);
|
|
if (pdata->fpwm_sync_gpio == -EINVAL)
|
|
pdata->enable_external_fpwm = of_property_read_bool(np,
|
|
"maxim,enable-external-fpwm");
|
|
if (gpio_is_valid(pdata->fpwm_sync_gpio))
|
|
pdata->enable_external_fpwm = true;
|
|
|
|
ret = of_property_read_u32(np, "maxim,tracking-device-i2c-address",
|
|
&pval);
|
|
pdata->tracking_device_i2c_address = (ret) ? 0 : pval;
|
|
|
|
ret = of_property_read_u32(np, "maxim,slew-rate", &pval);
|
|
pdata->slew_rate = (ret) ? -EINVAL : pval;
|
|
return pdata;
|
|
}
|
|
|
|
static int max16989_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct max16989_chip *mchip;
|
|
struct max16989_regulator_platform_data *pdata;
|
|
struct regulator_init_data *ridata;
|
|
struct regulator_dev *rdev;
|
|
struct regulator_config rconfig = { };
|
|
int ret;
|
|
|
|
if (!client->dev.of_node) {
|
|
dev_err(&client->dev, "Supported only from DT\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
mchip = devm_kzalloc(&client->dev, sizeof(*mchip), GFP_KERNEL);
|
|
if (!mchip)
|
|
return -ENOMEM;
|
|
|
|
mchip->dev = &client->dev;
|
|
mchip->desc.name = id->name;
|
|
mchip->desc.id = 0;
|
|
mchip->desc.ops = &max16989_ops;
|
|
mchip->desc.type = REGULATOR_VOLTAGE;
|
|
mchip->desc.owner = THIS_MODULE;
|
|
mchip->desc.vsel_reg = MAX16989_VID_REG;
|
|
mchip->desc.vsel_mask = MAX16989_VID_MASK;
|
|
|
|
pdata = of_get_max16989_platform_data(&client->dev, mchip);
|
|
if (!pdata) {
|
|
dev_err(&client->dev, "No Platform data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (pdata->enable_gpio == -EPROBE_DEFER)
|
|
return -EPROBE_DEFER;
|
|
|
|
mchip->pdata = pdata;
|
|
i2c_set_clientdata(client, mchip);
|
|
|
|
mchip->rmap = devm_regmap_init_i2c(client, &max16989_regmap_config);
|
|
if (IS_ERR(mchip->rmap)) {
|
|
ret = PTR_ERR(mchip->rmap);
|
|
dev_err(&client->dev, "regmap init failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = max16989_init(mchip, pdata);
|
|
if (ret < 0) {
|
|
dev_err(mchip->dev, "Init failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
mchip->desc.uV_step = pdata->voltage_step_uv;
|
|
mchip->desc.min_uV = mchip->min_uv;
|
|
mchip->desc.n_voltages = mchip->max_vout_vsel;
|
|
mchip->desc.linear_min_sel = 1;
|
|
|
|
/* Register the regulators */
|
|
rconfig.dev = &client->dev;
|
|
rconfig.of_node = client->dev.of_node;
|
|
rconfig.init_data = pdata->reg_init_data;
|
|
rconfig.driver_data = mchip;
|
|
rconfig.regmap = mchip->rmap;
|
|
if (gpio_is_valid(pdata->enable_gpio)) {
|
|
ridata = pdata->reg_init_data;
|
|
rconfig.ena_gpio = pdata->enable_gpio;
|
|
rconfig.ena_gpio_flags = GPIOF_OUT_INIT_LOW;
|
|
if (ridata && (ridata->constraints.always_on ||
|
|
ridata->constraints.boot_on))
|
|
rconfig.ena_gpio_flags = GPIOF_OUT_INIT_HIGH;
|
|
}
|
|
|
|
rdev = devm_regulator_register(&client->dev, &mchip->desc, &rconfig);
|
|
if (IS_ERR(rdev)) {
|
|
ret = PTR_ERR(rdev);
|
|
dev_err(mchip->dev, "regulator register failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
mchip->rdev = rdev;
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id max16989_id[] = {
|
|
{.name = "max16989",},
|
|
{.name = "max16989-new",},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, max16989_id);
|
|
|
|
static struct i2c_driver max16989_i2c_driver = {
|
|
.driver = {
|
|
.name = "max16989",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = max16989_of_match,
|
|
},
|
|
.probe = max16989_probe,
|
|
.id_table = max16989_id,
|
|
};
|
|
|
|
static int __init max16989_drv_init(void)
|
|
{
|
|
return i2c_add_driver(&max16989_i2c_driver);
|
|
}
|
|
subsys_initcall(max16989_drv_init);
|
|
|
|
static void __exit max16989_drv_cleanup(void)
|
|
{
|
|
i2c_del_driver(&max16989_i2c_driver);
|
|
}
|
|
module_exit(max16989_drv_cleanup);
|
|
|
|
MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
|
|
MODULE_DESCRIPTION("MAX16989 voltage regulator driver");
|
|
MODULE_LICENSE("GPL v2");
|