blob: a14046ecfabb327cf7358f700ae5640895184b83 [file] [edit]
// SPDX-License-Identifier: GPL-2.0+
/*
* s2mpg10-powermeter.c
*
* Copyright (c) 2015 Samsung Electronics Co., Ltd
* http://www.samsung.com
*/
#include <linux/bug.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <../drivers/pinctrl/samsung/pinctrl-samsung.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#include <linux/mfd/samsung/s2mpg10.h>
#include <linux/mfd/samsung/s2mpg10-register.h>
#include <linux/mfd/samsung/s2mpg1x-meter.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/debugfs.h>
#include <linux/interrupt.h>
#include <linux/regulator/pmic_class.h>
#include <linux/mfd/core.h>
#if IS_ENABLED(CONFIG_ODPM)
static struct mfd_cell s2mpg10_meter_devs[] = {
{
.name = "s2mpg10-odpm",
},
};
#endif
u32 s2mpg10_muxsel_to_current_resolution(s2mpg1x_meter_muxsel m)
{
switch (m) {
case MUXSEL_NONE:
case VSEN_C1: /* Requires shunt value */
case VSEN_C2: /* Requires shunt value */
case VSEN_C3: /* Requires shunt value */
return INVALID_RESOLUTION;
case BUCK1:
case BUCK4:
case BUCK6:
case BUCK7:
case BUCK8:
case BUCK9:
return CMS_BUCK_CURRENT;
case BUCK3:
return CMD_BUCK_CURRENT;
case BUCK2:
case BUCK5:
case BUCK10:
return CMT_BUCK_CURRENT;
case LDO1:
case LDO11:
return DVS_NLDO_CURRENT_150mA;
case LDO5:
case LDO6:
case LDO24:
return NLDO_CURRENT_150mA;
case LDO4:
case LDO9:
case LDO10:
case LDO14:
case LDO18:
case LDO19:
case LDO20:
case LDO22:
case LDO25:
case LDO26:
case LDO27:
case LDO29:
case LDO30:
return PLDO_CURRENT_150mA;
case LDO8:
case LDO28:
return NLDO_CURRENT_300mA;
case LDO23:
case LDO31:
case LDO21:
return PLDO_CURRENT_300mA;
case LDO7:
case LDO13:
return DVS_NLDO_CURRENT_450mA;
case LDO12:
case LDO15:
return DVS_NLDO_CURRENT_800mA;
case LDO3:
case LDO16:
case LDO17:
return NLDO_CURRENT_800mA;
case LDO2:
return PLDO_CURRENT_800mA;
default:
pr_err("%s: wrong muxsel\n", __func__);
return INVALID_RESOLUTION;
}
}
EXPORT_SYMBOL_GPL(s2mpg10_muxsel_to_current_resolution);
u32 s2mpg10_muxsel_to_power_resolution(s2mpg1x_meter_muxsel m)
{
switch (m) {
case MUXSEL_NONE:
case VSEN_C1: /* Requires shunt value */
case VSEN_C2: /* Requires shunt value */
case VSEN_C3: /* Requires shunt value */
return INVALID_RESOLUTION;
case BUCK1:
case BUCK4:
case BUCK6:
case BUCK7:
case BUCK8:
case BUCK9:
return CMS_BUCK_POWER;
case BUCK3:
return CMD_BUCK_POWER;
case BUCK2:
case BUCK5:
case BUCK10:
return CMT_BUCK_POWER;
case LDO1:
case LDO11:
return DVS_NLDO_POWER_150mA;
case LDO5:
case LDO6:
case LDO24:
return NLDO_POWER_150mA;
case LDO4:
case LDO9:
case LDO10:
case LDO14:
case LDO18:
case LDO19:
case LDO20:
case LDO22:
case LDO25:
case LDO26:
case LDO27:
case LDO29:
case LDO30:
return PLDO_POWER_150mA;
case LDO8:
case LDO28:
return NLDO_POWER_300mA;
case LDO23:
case LDO31:
case LDO21:
return PLDO_POWER_300mA;
case LDO7:
case LDO13:
return DVS_NLDO_POWER_450mA;
case LDO12:
case LDO15:
return DVS_NLDO_POWER_800mA;
case LDO3:
case LDO16:
case LDO17:
return NLDO_POWER_800mA;
case LDO2:
return PLDO_POWER_800mA;
default:
pr_err("%s: wrong muxsel\n", __func__);
return INVALID_RESOLUTION;
}
}
EXPORT_SYMBOL_GPL(s2mpg10_muxsel_to_power_resolution);
static const char *muxsel_to_str(s2mpg1x_meter_muxsel m)
{
char *ret;
switch (m) {
ENUM_STR(MUXSEL_NONE, "M", ret);
ENUM_STR(BUCK1, "M", ret);
ENUM_STR(BUCK2, "M", ret);
ENUM_STR(BUCK3, "M", ret);
ENUM_STR(BUCK4, "M", ret);
ENUM_STR(BUCK5, "M", ret);
ENUM_STR(BUCK6, "M", ret);
ENUM_STR(BUCK7, "M", ret);
ENUM_STR(BUCK8, "M", ret);
ENUM_STR(BUCK9, "M", ret);
ENUM_STR(BUCK10, "M", ret);
ENUM_STR(VSEN_V1, "", ret);
ENUM_STR(VSEN_V2, "", ret);
ENUM_STR(VSEN_V3, "", ret);
ENUM_STR(LDO1, "M", ret);
ENUM_STR(LDO2, "M", ret);
ENUM_STR(LDO3, "M", ret);
ENUM_STR(LDO4, "M", ret);
ENUM_STR(LDO5, "M", ret);
ENUM_STR(LDO6, "M", ret);
ENUM_STR(LDO7, "M", ret);
ENUM_STR(LDO8, "M", ret);
ENUM_STR(LDO9, "M", ret);
ENUM_STR(LDO10, "M", ret);
ENUM_STR(LDO11, "M", ret);
ENUM_STR(LDO12, "M", ret);
ENUM_STR(LDO13, "M", ret);
ENUM_STR(LDO14, "M", ret);
ENUM_STR(LDO15, "M", ret);
ENUM_STR(LDO16, "M", ret);
ENUM_STR(LDO17, "M", ret);
ENUM_STR(LDO18, "M", ret);
ENUM_STR(LDO19, "M", ret);
ENUM_STR(LDO20, "M", ret);
ENUM_STR(LDO21, "M", ret);
ENUM_STR(LDO22, "M", ret);
ENUM_STR(LDO23, "M", ret);
ENUM_STR(LDO24, "M", ret);
ENUM_STR(LDO25, "M", ret);
ENUM_STR(LDO26, "M", ret);
ENUM_STR(LDO27, "M", ret);
ENUM_STR(LDO28, "M", ret);
ENUM_STR(LDO29, "M", ret);
ENUM_STR(LDO30, "M", ret);
ENUM_STR(LDO31, "M", ret);
ENUM_STR(VSEN_C1, "", ret);
ENUM_STR(VSEN_C2, "", ret);
ENUM_STR(VSEN_C3, "", ret);
default:
return "invalid";
}
return ret;
}
int s2mpg10_meter_onoff(struct s2mpg10_meter *s2mpg10, bool onoff)
{
int ret;
if (onoff) {
pr_info("%s: s2mpg10 meter on\n", __func__);
ret = s2mpg10_update_reg(s2mpg10->i2c, S2MPG10_METER_CTRL1,
METER_EN_MASK, METER_EN_MASK);
} else {
pr_info("%s: s2mpg10 meter off\n", __func__);
ret = s2mpg10_update_reg(s2mpg10->i2c, S2MPG10_METER_CTRL1, 0,
METER_EN_MASK);
}
return ret;
}
EXPORT_SYMBOL_GPL(s2mpg10_meter_onoff);
int s2mpg10_ext_meter_onoff(struct s2mpg10_meter *s2mpg10, bool onoff)
{
int ret;
if (onoff) {
pr_info("%s: s2mpg10 external meter on\n", __func__);
ret = s2mpg10_update_reg(s2mpg10->i2c, S2MPG10_METER_CTRL1,
EXT_METER_EN_MASK, EXT_METER_EN_MASK);
} else {
pr_info("%s: s2mpg10 external meter off\n", __func__);
ret = s2mpg10_update_reg(s2mpg10->i2c, S2MPG10_METER_CTRL1, 0,
EXT_METER_EN_MASK);
}
return ret;
}
EXPORT_SYMBOL_GPL(s2mpg10_ext_meter_onoff);
int s2mpg10_meter_set_muxsel(struct s2mpg10_meter *s2mpg10, int channel,
s2mpg1x_meter_muxsel m)
{
int reg = S2MPG10_METER_MUXSEL0;
int ret = -EPERM;
if (channel < 0 || channel >= S2MPG1X_METER_CHANNEL_MAX) {
pr_err("%s: invalid channel number\n", __func__);
return ret;
}
pr_debug("%s: CH%d, %s\n", __func__, channel, muxsel_to_str(m));
reg += channel;
mutex_lock(&s2mpg10->meter_lock);
ret = s2mpg10_update_reg(s2mpg10->i2c, reg, m, MUXSEL_MASK);
s2mpg10->chg_mux_sel[channel] = m;
mutex_unlock(&s2mpg10->meter_lock);
return ret;
}
EXPORT_SYMBOL_GPL(s2mpg10_meter_set_muxsel);
#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC)
static ssize_t s2mpg10_muxsel_table_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int muxsel_cnt = 0;
int muxsel = BUCK1;
size_t count = 0;
while (muxsel <= VSEN_C3) {
count += scnprintf(buf + count, PAGE_SIZE - count,
"%s : 0x%x , ", muxsel_to_str(muxsel), muxsel);
if (muxsel == BUCK10)
muxsel = VSEN_V1;
else if (muxsel == VSEN_V3)
muxsel = LDO1;
else if (muxsel == LDO31)
muxsel = VSEN_C1;
else if (muxsel == VSEN_C3)
break;
else
muxsel++;
muxsel_cnt++;
if (!(muxsel_cnt % 8))
count += scnprintf(buf + count,
PAGE_SIZE - count, "\n");
}
return count;
}
static ssize_t s2mpg10_channel_muxsel_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct s2mpg10_meter *s2mpg10 = dev_get_drvdata(dev);
int channel, muxsel;
int ret;
if (!buf) {
pr_err("%s: empty buffer\n", __func__);
return -EINVAL;
}
ret = sscanf(buf, "%x %x", &channel, &muxsel);
if (ret != 2) {
pr_err("%s: input error\n", __func__);
return -EINVAL;
}
if (channel < 0 || channel > 7) {
pr_err("%s: wrong channel %d\n", __func__, channel);
return -EINVAL;
}
if ((muxsel >= BUCK1 && muxsel <= BUCK10) ||
(muxsel >= VSEN_V1 && muxsel <= VSEN_V3) ||
(muxsel >= LDO1 && muxsel <= LDO31) ||
(muxsel >= VSEN_C1 && muxsel <= VSEN_C3)) {
s2mpg10_meter_set_muxsel(s2mpg10, channel, muxsel);
} else {
pr_err("%s: wrong muxsel 0x%x\n", __func__, muxsel);
return -EINVAL;
}
return size;
}
static ssize_t s2mpg10_channel_muxsel_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct s2mpg10_meter *s2mpg10 = dev_get_drvdata(dev);
int i;
size_t count = 0;
mutex_lock(&s2mpg10->meter_lock);
for (i = 0; i < S2MPG1X_METER_CHANNEL_MAX; i++) {
count += scnprintf(buf + count, PAGE_SIZE - count, "CH%d[%s], ",
i, muxsel_to_str(s2mpg10->chg_mux_sel[i]));
}
mutex_unlock(&s2mpg10->meter_lock);
return count;
}
static ssize_t s2mpg10_lpf_current_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct s2mpg10_meter *s2mpg10 = dev_get_drvdata(dev);
int i;
ssize_t count = 0;
mutex_lock(&s2mpg10->meter_lock);
s2mpg1x_meter_set_lpf_mode(ID_S2MPG10, s2mpg10->i2c,
S2MPG1X_METER_CURRENT);
s2mpg1x_meter_read_lpf_data_reg(ID_S2MPG10, s2mpg10->i2c,
s2mpg10->lpf_data);
for (i = 0; i < S2MPG1X_METER_CHANNEL_MAX; i++) {
s2mpg1x_meter_muxsel muxsel = s2mpg10->chg_mux_sel[i];
count += s2mpg1x_meter_format_channel(buf, count, i,
muxsel_to_str(muxsel), "(mA)",
s2mpg10->lpf_data[i],
s2mpg10_muxsel_to_current_resolution(muxsel), 1);
}
mutex_unlock(&s2mpg10->meter_lock);
return count;
}
static ssize_t s2mpg10_lpf_power_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mpg10_meter *s2mpg10 = dev_get_drvdata(dev);
int i;
ssize_t count = 0;
mutex_lock(&s2mpg10->meter_lock);
s2mpg1x_meter_set_lpf_mode(ID_S2MPG10, s2mpg10->i2c,
S2MPG1X_METER_POWER);
s2mpg1x_meter_read_lpf_data_reg(ID_S2MPG10, s2mpg10->i2c,
s2mpg10->lpf_data);
for (i = 0; i < S2MPG1X_METER_CHANNEL_MAX; i++) {
s2mpg1x_meter_muxsel muxsel = s2mpg10->chg_mux_sel[i];
count += s2mpg1x_meter_format_channel(buf, count, i,
muxsel_to_str(muxsel), "(mW)",
s2mpg10->lpf_data[i],
s2mpg10_muxsel_to_power_resolution(muxsel), 1);
}
mutex_unlock(&s2mpg10->meter_lock);
return count;
}
static ssize_t s2mpg10_acc_current_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct s2mpg10_meter *s2mpg10 = dev_get_drvdata(dev);
int i;
ssize_t count = 0;
u64 acc_data[S2MPG1X_METER_CHANNEL_MAX];
u32 acc_count;
s2mpg1x_meter_measure_acc(ID_S2MPG10, s2mpg10->i2c,
&s2mpg10->meter_lock,
S2MPG1X_METER_CURRENT, acc_data,
&acc_count, NULL, INT_125HZ);
for (i = 0; i < S2MPG1X_METER_CHANNEL_MAX; i++) {
s2mpg1x_meter_muxsel muxsel = s2mpg10->chg_mux_sel[i];
count += s2mpg1x_meter_format_channel(buf, count, i,
muxsel_to_str(muxsel), "(mA)",
acc_data[i],
s2mpg10_muxsel_to_current_resolution(muxsel),
acc_count);
}
return count;
}
static ssize_t s2mpg10_acc_power_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mpg10_meter *s2mpg10 = dev_get_drvdata(dev);
int i;
ssize_t count = 0;
u64 acc_data[S2MPG1X_METER_CHANNEL_MAX];
u32 acc_count;
s2mpg1x_meter_measure_acc(ID_S2MPG10, s2mpg10->i2c,
&s2mpg10->meter_lock,
S2MPG1X_METER_POWER, acc_data,
&acc_count, NULL, INT_125HZ);
for (i = 0; i < S2MPG1X_METER_CHANNEL_MAX; i++) {
s2mpg1x_meter_muxsel muxsel = s2mpg10->chg_mux_sel[i];
count += s2mpg1x_meter_format_channel(buf, count, i,
muxsel_to_str(muxsel), "(mW)",
acc_data[i], s2mpg10_muxsel_to_power_resolution(muxsel),
acc_count);
}
return count;
}
static DEVICE_ATTR_RO(s2mpg10_muxsel_table);
static DEVICE_ATTR_RW(s2mpg10_channel_muxsel);
static DEVICE_ATTR_RO(s2mpg10_lpf_current);
static DEVICE_ATTR_RO(s2mpg10_lpf_power);
static DEVICE_ATTR_RO(s2mpg10_acc_current);
static DEVICE_ATTR_RO(s2mpg10_acc_power);
int create_s2mpg10_meter_sysfs(struct s2mpg10_meter *s2mpg10)
{
struct device *s2mpg10_meter_dev = s2mpg10->dev;
int err = -ENODEV;
pr_info("%s: s2mpg10 meter sysfs start\n", __func__);
s2mpg10_meter_dev = pmic_device_create(s2mpg10, "s2mpg10-meter");
err = device_create_file(s2mpg10_meter_dev,
&dev_attr_s2mpg10_lpf_current);
if (err) {
pr_err("s2mpg10_sysfs: failed to create device file, %s\n",
dev_attr_s2mpg10_lpf_current.attr.name);
}
err = device_create_file(s2mpg10_meter_dev,
&dev_attr_s2mpg10_lpf_power);
if (err) {
pr_err("s2mpg10_sysfs: failed to create device file, %s\n",
dev_attr_s2mpg10_lpf_power.attr.name);
}
err = device_create_file(s2mpg10_meter_dev,
&dev_attr_s2mpg10_acc_current);
if (err) {
pr_err("s2mpg10_sysfs: failed to create device file, %s\n",
dev_attr_s2mpg10_acc_current.attr.name);
}
err = device_create_file(s2mpg10_meter_dev,
&dev_attr_s2mpg10_acc_power);
if (err) {
pr_err("s2mpg10_sysfs: failed to create device file, %s\n",
dev_attr_s2mpg10_acc_power.attr.name);
}
err = device_create_file(s2mpg10_meter_dev,
&dev_attr_s2mpg10_channel_muxsel);
if (err) {
pr_err("s2mpg10_sysfs: failed to create device file, %s\n",
dev_attr_s2mpg10_channel_muxsel.attr.name);
}
err = device_create_file(s2mpg10_meter_dev,
&dev_attr_s2mpg10_muxsel_table);
if (err) {
pr_err("s2mpg10_sysfs: failed to create device file, %s\n",
dev_attr_s2mpg10_muxsel_table.attr.name);
}
return 0;
}
#endif
static int s2mpg10_meter_probe(struct platform_device *pdev)
{
struct s2mpg10_dev *iodev = dev_get_drvdata(pdev->dev.parent);
struct s2mpg10_platform_data *pdata = iodev->pdata;
struct s2mpg10_meter *s2mpg10;
u8 dsm_trim_high = 0, dsm_trim_low = 0;
char dsm_trim, dsm_result;
int ret;
if (!pdata) {
dev_err(pdev->dev.parent, "Platform data not supplied\n");
return -ENODEV;
}
s2mpg10 = devm_kzalloc(&pdev->dev, sizeof(struct s2mpg10_meter),
GFP_KERNEL);
if (!s2mpg10)
return -ENOMEM;
s2mpg10->i2c = iodev->meter;
s2mpg10->dev = &pdev->dev;
mutex_init(&s2mpg10->meter_lock);
platform_set_drvdata(pdev, s2mpg10);
/* w/a powermeter offset rising */
ret = s2mpg10_read_reg(s2mpg10->i2c, S2MPG10_METER_DSM_TRIM_OFFSET, &dsm_trim_low);
ret = s2mpg10_read_reg(s2mpg10->i2c, S2MPG10_METER_BUCK_METER_TRIM3, &dsm_trim_high);
pr_info("%s: DSM_TRIM_OFFSET: 0x%x\n", __func__, dsm_trim_low);
pr_info("%s: BUCK_METER_TRIM3: 0x%x\n", __func__, dsm_trim_high);
dsm_trim = (dsm_trim_high & S2MPG10_METER_DSM_TRIM_HIGH_MASK) |
(dsm_trim_low & S2MPG10_METER_DSM_TRIM_LOW_MASK);
if (dsm_trim & BIT(7)) {
dsm_result = dsm_trim + S2MPG10_DSM_TRIM_OFFSET_ADD_VALUE;
} else {
if (dsm_trim >= S2MPG10_DSM_TRIM_OFFSET_THRESHOLD)
dsm_result = S2MPG10_DSM_TRIM_OFFSET_MAX_VALUE;
else
dsm_result = dsm_trim + S2MPG10_DSM_TRIM_OFFSET_ADD_VALUE;
}
pr_info("%s: dsm_trim: %d, dsm_result: %d\n", __func__, dsm_trim, dsm_result);
ret = s2mpg10_update_reg(s2mpg10->i2c, S2MPG10_METER_DSM_TRIM_OFFSET,
dsm_result & S2MPG10_METER_DSM_TRIM_LOW_MASK,
S2MPG10_METER_DSM_TRIM_LOW_MASK);
ret = s2mpg10_update_reg(s2mpg10->i2c, S2MPG10_METER_BUCK_METER_TRIM3,
dsm_result & S2MPG10_METER_DSM_TRIM_HIGH_MASK,
S2MPG10_METER_DSM_TRIM_HIGH_MASK);
#if !IS_ENABLED(CONFIG_ODPM)
/* initial setting */
/* set BUCK1M ~ BUCK8m muxsel from CH0 to CH7 */
/* any necessary settings can be added */
s2mpg1x_meter_set_int_samp_rate(ID_S2MPG10, s2mpg10->i2c, INT_125HZ);
s2mpg10_meter_set_muxsel(s2mpg10, 0, BUCK1);
s2mpg10_meter_set_muxsel(s2mpg10, 1, BUCK2);
s2mpg10_meter_set_muxsel(s2mpg10, 2, BUCK3);
s2mpg10_meter_set_muxsel(s2mpg10, 3, BUCK4);
s2mpg10_meter_set_muxsel(s2mpg10, 4, BUCK5);
s2mpg10_meter_set_muxsel(s2mpg10, 5, BUCK6);
s2mpg10_meter_set_muxsel(s2mpg10, 6, BUCK7);
s2mpg10_meter_set_muxsel(s2mpg10, 7, BUCK8);
s2mpg10_meter_onoff(s2mpg10, true);
s2mpg10_ext_meter_onoff(s2mpg10, false);
#else
ret = mfd_add_devices(s2mpg10->dev, -1, s2mpg10_meter_devs,
ARRAY_SIZE(s2mpg10_meter_devs), NULL, 0, NULL);
if (ret < 0) {
mfd_remove_devices(s2mpg10->dev);
return ret;
}
#endif
#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC)
/* create sysfs */
ret = create_s2mpg10_meter_sysfs(s2mpg10);
#endif
return ret;
}
static int s2mpg10_meter_remove(struct platform_device *pdev)
{
struct s2mpg10_meter *s2mpg10 = platform_get_drvdata(pdev);
s2mpg10_meter_onoff(s2mpg10, false);
s2mpg10_ext_meter_onoff(s2mpg10, false);
#if IS_ENABLED(CONFIG_ODPM)
mfd_remove_devices(s2mpg10->dev);
#endif
#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC)
pmic_device_destroy(s2mpg10->dev->devt);
#endif
return 0;
}
static void s2mpg10_meter_shutdown(struct platform_device *pdev)
{
struct s2mpg10_meter *s2mpg10 = platform_get_drvdata(pdev);
s2mpg10_meter_onoff(s2mpg10, false);
s2mpg10_ext_meter_onoff(s2mpg10, false);
}
static const struct platform_device_id s2mpg10_meter_id[] = {
{ "s2mpg10-meter", 0 },
{},
};
MODULE_DEVICE_TABLE(platform, s2mpg10_meter_id);
static struct platform_driver s2mpg10_meter_driver = {
.driver = {
.name = "s2mpg10-meter",
.owner = THIS_MODULE,
.suppress_bind_attrs = true,
},
.probe = s2mpg10_meter_probe,
.remove = s2mpg10_meter_remove,
.shutdown = s2mpg10_meter_shutdown,
.id_table = s2mpg10_meter_id,
};
static int __init s2mpg10_meter_init(void)
{
return platform_driver_register(&s2mpg10_meter_driver);
}
subsys_initcall(s2mpg10_meter_init);
static void __exit s2mpg10_meter_exit(void)
{
platform_driver_unregister(&s2mpg10_meter_driver);
}
module_exit(s2mpg10_meter_exit);
/* Module information */
MODULE_DESCRIPTION("SAMSUNG S2MPG10 Meter Driver");
MODULE_LICENSE("GPL");