xref: /openbmc/linux/drivers/hwmon/smsc47b397.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
174ba9207SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
28d5d45fbSJean Delvare /*
32a52dd66SGuenter Roeck  * smsc47b397.c - Part of lm_sensors, Linux kernel modules
42a52dd66SGuenter Roeck  * for hardware monitoring
52a52dd66SGuenter Roeck  *
62a52dd66SGuenter Roeck  * Supports the SMSC LPC47B397-NC Super-I/O chip.
72a52dd66SGuenter Roeck  *
82a52dd66SGuenter Roeck  * Author/Maintainer: Mark M. Hoffman <mhoffman@lightlink.com>
92a52dd66SGuenter Roeck  * Copyright (C) 2004 Utilitek Systems, Inc.
102a52dd66SGuenter Roeck  *
112a52dd66SGuenter Roeck  * derived in part from smsc47m1.c:
122a52dd66SGuenter Roeck  * Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
137c81c60fSJean Delvare  * Copyright (C) 2004 Jean Delvare <jdelvare@suse.de>
148d5d45fbSJean Delvare  */
158d5d45fbSJean Delvare 
16bf1a85efSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
17bf1a85efSJoe Perches 
188d5d45fbSJean Delvare #include <linux/module.h>
198d5d45fbSJean Delvare #include <linux/slab.h>
208d5d45fbSJean Delvare #include <linux/ioport.h>
218d5d45fbSJean Delvare #include <linux/jiffies.h>
22292fc1a5SJean Delvare #include <linux/platform_device.h>
23943b0830SMark M. Hoffman #include <linux/hwmon.h>
243659a017SJean Delvare #include <linux/hwmon-sysfs.h>
25943b0830SMark M. Hoffman #include <linux/err.h>
268d5d45fbSJean Delvare #include <linux/init.h>
279a61bf63SIngo Molnar #include <linux/mutex.h>
28b9acb64aSJean Delvare #include <linux/acpi.h>
296055fae8SH Hartley Sweeten #include <linux/io.h>
308d5d45fbSJean Delvare 
3167b671bcSJean Delvare static unsigned short force_id;
3267b671bcSJean Delvare module_param(force_id, ushort, 0);
3367b671bcSJean Delvare MODULE_PARM_DESC(force_id, "Override the detected device ID");
3467b671bcSJean Delvare 
35292fc1a5SJean Delvare static struct platform_device *pdev;
36292fc1a5SJean Delvare 
37292fc1a5SJean Delvare #define DRVNAME "smsc47b397"
388d5d45fbSJean Delvare 
398d5d45fbSJean Delvare /* Super-I/0 registers and commands */
408d5d45fbSJean Delvare 
418d5d45fbSJean Delvare #define	REG	0x2e	/* The register to read/write */
428d5d45fbSJean Delvare #define	VAL	0x2f	/* The value to read/write */
438d5d45fbSJean Delvare 
superio_outb(int reg,int val)448d5d45fbSJean Delvare static inline void superio_outb(int reg, int val)
458d5d45fbSJean Delvare {
468d5d45fbSJean Delvare 	outb(reg, REG);
478d5d45fbSJean Delvare 	outb(val, VAL);
488d5d45fbSJean Delvare }
498d5d45fbSJean Delvare 
superio_inb(int reg)508d5d45fbSJean Delvare static inline int superio_inb(int reg)
518d5d45fbSJean Delvare {
528d5d45fbSJean Delvare 	outb(reg, REG);
538d5d45fbSJean Delvare 	return inb(VAL);
548d5d45fbSJean Delvare }
558d5d45fbSJean Delvare 
568d5d45fbSJean Delvare /* select superio logical device */
superio_select(int ld)578d5d45fbSJean Delvare static inline void superio_select(int ld)
588d5d45fbSJean Delvare {
598d5d45fbSJean Delvare 	superio_outb(0x07, ld);
608d5d45fbSJean Delvare }
618d5d45fbSJean Delvare 
superio_enter(void)628c082675SGuenter Roeck static inline int superio_enter(void)
638d5d45fbSJean Delvare {
648c082675SGuenter Roeck 	if (!request_muxed_region(REG, 2, DRVNAME))
658c082675SGuenter Roeck 		return -EBUSY;
668c082675SGuenter Roeck 
678d5d45fbSJean Delvare 	outb(0x55, REG);
688c082675SGuenter Roeck 	return 0;
698d5d45fbSJean Delvare }
708d5d45fbSJean Delvare 
superio_exit(void)718d5d45fbSJean Delvare static inline void superio_exit(void)
728d5d45fbSJean Delvare {
738d5d45fbSJean Delvare 	outb(0xAA, REG);
748c082675SGuenter Roeck 	release_region(REG, 2);
758d5d45fbSJean Delvare }
768d5d45fbSJean Delvare 
778d5d45fbSJean Delvare #define SUPERIO_REG_DEVID	0x20
788d5d45fbSJean Delvare #define SUPERIO_REG_DEVREV	0x21
798d5d45fbSJean Delvare #define SUPERIO_REG_BASE_MSB	0x60
808d5d45fbSJean Delvare #define SUPERIO_REG_BASE_LSB	0x61
818d5d45fbSJean Delvare #define SUPERIO_REG_LD8		0x08
828d5d45fbSJean Delvare 
838d5d45fbSJean Delvare #define SMSC_EXTENT		0x02
848d5d45fbSJean Delvare 
858d5d45fbSJean Delvare /* 0 <= nr <= 3 */
868d5d45fbSJean Delvare static u8 smsc47b397_reg_temp[] = {0x25, 0x26, 0x27, 0x80};
878d5d45fbSJean Delvare #define SMSC47B397_REG_TEMP(nr)	(smsc47b397_reg_temp[(nr)])
888d5d45fbSJean Delvare 
898d5d45fbSJean Delvare /* 0 <= nr <= 3 */
908d5d45fbSJean Delvare #define SMSC47B397_REG_FAN_LSB(nr) (0x28 + 2 * (nr))
918d5d45fbSJean Delvare #define SMSC47B397_REG_FAN_MSB(nr) (0x29 + 2 * (nr))
928d5d45fbSJean Delvare 
938d5d45fbSJean Delvare struct smsc47b397_data {
94292fc1a5SJean Delvare 	unsigned short addr;
959a61bf63SIngo Molnar 	struct mutex lock;
968d5d45fbSJean Delvare 
979a61bf63SIngo Molnar 	struct mutex update_lock;
988d5d45fbSJean Delvare 	unsigned long last_updated; /* in jiffies */
99*952a11caSPaul Fertser 	bool valid;
1008d5d45fbSJean Delvare 
1018d5d45fbSJean Delvare 	/* register values */
1028d5d45fbSJean Delvare 	u16 fan[4];
1038d5d45fbSJean Delvare 	u8 temp[4];
1048d5d45fbSJean Delvare };
1058d5d45fbSJean Delvare 
smsc47b397_read_value(struct smsc47b397_data * data,u8 reg)106292fc1a5SJean Delvare static int smsc47b397_read_value(struct smsc47b397_data *data, u8 reg)
1078d5d45fbSJean Delvare {
1088d5d45fbSJean Delvare 	int res;
1098d5d45fbSJean Delvare 
1109a61bf63SIngo Molnar 	mutex_lock(&data->lock);
111292fc1a5SJean Delvare 	outb(reg, data->addr);
112292fc1a5SJean Delvare 	res = inb_p(data->addr + 1);
1139a61bf63SIngo Molnar 	mutex_unlock(&data->lock);
1148d5d45fbSJean Delvare 	return res;
1158d5d45fbSJean Delvare }
1168d5d45fbSJean Delvare 
smsc47b397_update_device(struct device * dev)1178d5d45fbSJean Delvare static struct smsc47b397_data *smsc47b397_update_device(struct device *dev)
1188d5d45fbSJean Delvare {
119292fc1a5SJean Delvare 	struct smsc47b397_data *data = dev_get_drvdata(dev);
1208d5d45fbSJean Delvare 	int i;
1218d5d45fbSJean Delvare 
1229a61bf63SIngo Molnar 	mutex_lock(&data->update_lock);
1238d5d45fbSJean Delvare 
1248d5d45fbSJean Delvare 	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
125292fc1a5SJean Delvare 		dev_dbg(dev, "starting device update...\n");
1268d5d45fbSJean Delvare 
1278d5d45fbSJean Delvare 		/* 4 temperature inputs, 4 fan inputs */
1288d5d45fbSJean Delvare 		for (i = 0; i < 4; i++) {
129292fc1a5SJean Delvare 			data->temp[i] = smsc47b397_read_value(data,
1308d5d45fbSJean Delvare 					SMSC47B397_REG_TEMP(i));
1318d5d45fbSJean Delvare 
1328d5d45fbSJean Delvare 			/* must read LSB first */
133292fc1a5SJean Delvare 			data->fan[i]  = smsc47b397_read_value(data,
1348d5d45fbSJean Delvare 					SMSC47B397_REG_FAN_LSB(i));
135292fc1a5SJean Delvare 			data->fan[i] |= smsc47b397_read_value(data,
1368d5d45fbSJean Delvare 					SMSC47B397_REG_FAN_MSB(i)) << 8;
1378d5d45fbSJean Delvare 		}
1388d5d45fbSJean Delvare 
1398d5d45fbSJean Delvare 		data->last_updated = jiffies;
140*952a11caSPaul Fertser 		data->valid = true;
1418d5d45fbSJean Delvare 
142292fc1a5SJean Delvare 		dev_dbg(dev, "... device update complete\n");
1438d5d45fbSJean Delvare 	}
1448d5d45fbSJean Delvare 
1459a61bf63SIngo Molnar 	mutex_unlock(&data->update_lock);
1468d5d45fbSJean Delvare 
1478d5d45fbSJean Delvare 	return data;
1488d5d45fbSJean Delvare }
1498d5d45fbSJean Delvare 
1502a52dd66SGuenter Roeck /*
1512a52dd66SGuenter Roeck  * TEMP: 0.001C/bit (-128C to +127C)
1522a52dd66SGuenter Roeck  * REG: 1C/bit, two's complement
1532a52dd66SGuenter Roeck  */
temp_from_reg(u8 reg)1548d5d45fbSJean Delvare static int temp_from_reg(u8 reg)
1558d5d45fbSJean Delvare {
1568d5d45fbSJean Delvare 	return (s8)reg * 1000;
1578d5d45fbSJean Delvare }
1588d5d45fbSJean Delvare 
temp_show(struct device * dev,struct device_attribute * devattr,char * buf)1598721bdecSGuenter Roeck static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
1608721bdecSGuenter Roeck 			 char *buf)
1618d5d45fbSJean Delvare {
1623659a017SJean Delvare 	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
1638d5d45fbSJean Delvare 	struct smsc47b397_data *data = smsc47b397_update_device(dev);
1643659a017SJean Delvare 	return sprintf(buf, "%d\n", temp_from_reg(data->temp[attr->index]));
1658d5d45fbSJean Delvare }
1668d5d45fbSJean Delvare 
1678721bdecSGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0);
1688721bdecSGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1);
1698721bdecSGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2);
1708721bdecSGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3);
1718d5d45fbSJean Delvare 
1722a52dd66SGuenter Roeck /*
1732a52dd66SGuenter Roeck  * FAN: 1 RPM/bit
1742a52dd66SGuenter Roeck  * REG: count of 90kHz pulses / revolution
1752a52dd66SGuenter Roeck  */
fan_from_reg(u16 reg)1768d5d45fbSJean Delvare static int fan_from_reg(u16 reg)
1778d5d45fbSJean Delvare {
17890205c6cSJean Delvare 	if (reg == 0 || reg == 0xffff)
17990205c6cSJean Delvare 		return 0;
1808d5d45fbSJean Delvare 	return 90000 * 60 / reg;
1818d5d45fbSJean Delvare }
1828d5d45fbSJean Delvare 
fan_show(struct device * dev,struct device_attribute * devattr,char * buf)1838721bdecSGuenter Roeck static ssize_t fan_show(struct device *dev, struct device_attribute *devattr,
1848721bdecSGuenter Roeck 			char *buf)
1858d5d45fbSJean Delvare {
1863659a017SJean Delvare 	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
1878d5d45fbSJean Delvare 	struct smsc47b397_data *data = smsc47b397_update_device(dev);
1883659a017SJean Delvare 	return sprintf(buf, "%d\n", fan_from_reg(data->fan[attr->index]));
1898d5d45fbSJean Delvare }
1908721bdecSGuenter Roeck static SENSOR_DEVICE_ATTR_RO(fan1_input, fan, 0);
1918721bdecSGuenter Roeck static SENSOR_DEVICE_ATTR_RO(fan2_input, fan, 1);
1928721bdecSGuenter Roeck static SENSOR_DEVICE_ATTR_RO(fan3_input, fan, 2);
1938721bdecSGuenter Roeck static SENSOR_DEVICE_ATTR_RO(fan4_input, fan, 3);
1948d5d45fbSJean Delvare 
1959b993e36SAxel Lin static struct attribute *smsc47b397_attrs[] = {
1963659a017SJean Delvare 	&sensor_dev_attr_temp1_input.dev_attr.attr,
1973659a017SJean Delvare 	&sensor_dev_attr_temp2_input.dev_attr.attr,
1983659a017SJean Delvare 	&sensor_dev_attr_temp3_input.dev_attr.attr,
1993659a017SJean Delvare 	&sensor_dev_attr_temp4_input.dev_attr.attr,
2003659a017SJean Delvare 	&sensor_dev_attr_fan1_input.dev_attr.attr,
2013659a017SJean Delvare 	&sensor_dev_attr_fan2_input.dev_attr.attr,
2023659a017SJean Delvare 	&sensor_dev_attr_fan3_input.dev_attr.attr,
2033659a017SJean Delvare 	&sensor_dev_attr_fan4_input.dev_attr.attr,
204c1685f61SMark M. Hoffman 
205c1685f61SMark M. Hoffman 	NULL
206c1685f61SMark M. Hoffman };
207c1685f61SMark M. Hoffman 
2089b993e36SAxel Lin ATTRIBUTE_GROUPS(smsc47b397);
2098d5d45fbSJean Delvare 
210292fc1a5SJean Delvare static int smsc47b397_probe(struct platform_device *pdev);
2112d8672c5SJean Delvare 
212292fc1a5SJean Delvare static struct platform_driver smsc47b397_driver = {
213cdaf7934SLaurent Riffard 	.driver = {
214292fc1a5SJean Delvare 		.name	= DRVNAME,
215cdaf7934SLaurent Riffard 	},
216292fc1a5SJean Delvare 	.probe		= smsc47b397_probe,
2178d5d45fbSJean Delvare };
2188d5d45fbSJean Delvare 
smsc47b397_probe(struct platform_device * pdev)2196c931ae1SBill Pemberton static int smsc47b397_probe(struct platform_device *pdev)
2208d5d45fbSJean Delvare {
221292fc1a5SJean Delvare 	struct device *dev = &pdev->dev;
2228d5d45fbSJean Delvare 	struct smsc47b397_data *data;
2239b993e36SAxel Lin 	struct device *hwmon_dev;
224292fc1a5SJean Delvare 	struct resource *res;
2258d5d45fbSJean Delvare 
226292fc1a5SJean Delvare 	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
227e43d5fefSGuenter Roeck 	if (!devm_request_region(dev, res->start, SMSC_EXTENT,
228cdaf7934SLaurent Riffard 				 smsc47b397_driver.driver.name)) {
229292fc1a5SJean Delvare 		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
230292fc1a5SJean Delvare 			(unsigned long)res->start,
231292fc1a5SJean Delvare 			(unsigned long)res->start + SMSC_EXTENT - 1);
2328d5d45fbSJean Delvare 		return -EBUSY;
2338d5d45fbSJean Delvare 	}
2348d5d45fbSJean Delvare 
235e43d5fefSGuenter Roeck 	data = devm_kzalloc(dev, sizeof(struct smsc47b397_data), GFP_KERNEL);
236e43d5fefSGuenter Roeck 	if (!data)
237e43d5fefSGuenter Roeck 		return -ENOMEM;
2388d5d45fbSJean Delvare 
239292fc1a5SJean Delvare 	data->addr = res->start;
2409a61bf63SIngo Molnar 	mutex_init(&data->lock);
2419a61bf63SIngo Molnar 	mutex_init(&data->update_lock);
2428d5d45fbSJean Delvare 
2439b993e36SAxel Lin 	hwmon_dev = devm_hwmon_device_register_with_groups(dev, "smsc47b397",
2449b993e36SAxel Lin 							   data,
2459b993e36SAxel Lin 							   smsc47b397_groups);
2469b993e36SAxel Lin 	return PTR_ERR_OR_ZERO(hwmon_dev);
247292fc1a5SJean Delvare }
248292fc1a5SJean Delvare 
smsc47b397_device_add(unsigned short address)249292fc1a5SJean Delvare static int __init smsc47b397_device_add(unsigned short address)
250292fc1a5SJean Delvare {
251292fc1a5SJean Delvare 	struct resource res = {
252292fc1a5SJean Delvare 		.start	= address,
253292fc1a5SJean Delvare 		.end	= address + SMSC_EXTENT - 1,
254292fc1a5SJean Delvare 		.name	= DRVNAME,
255292fc1a5SJean Delvare 		.flags	= IORESOURCE_IO,
256292fc1a5SJean Delvare 	};
257292fc1a5SJean Delvare 	int err;
258292fc1a5SJean Delvare 
259b9acb64aSJean Delvare 	err = acpi_check_resource_conflict(&res);
260b9acb64aSJean Delvare 	if (err)
261b9acb64aSJean Delvare 		goto exit;
262b9acb64aSJean Delvare 
263292fc1a5SJean Delvare 	pdev = platform_device_alloc(DRVNAME, address);
264292fc1a5SJean Delvare 	if (!pdev) {
265292fc1a5SJean Delvare 		err = -ENOMEM;
266bf1a85efSJoe Perches 		pr_err("Device allocation failed\n");
267292fc1a5SJean Delvare 		goto exit;
268292fc1a5SJean Delvare 	}
269292fc1a5SJean Delvare 
270292fc1a5SJean Delvare 	err = platform_device_add_resources(pdev, &res, 1);
271292fc1a5SJean Delvare 	if (err) {
272bf1a85efSJoe Perches 		pr_err("Device resource addition failed (%d)\n", err);
273292fc1a5SJean Delvare 		goto exit_device_put;
274292fc1a5SJean Delvare 	}
275292fc1a5SJean Delvare 
276292fc1a5SJean Delvare 	err = platform_device_add(pdev);
277292fc1a5SJean Delvare 	if (err) {
278bf1a85efSJoe Perches 		pr_err("Device addition failed (%d)\n", err);
279292fc1a5SJean Delvare 		goto exit_device_put;
280292fc1a5SJean Delvare 	}
281292fc1a5SJean Delvare 
282292fc1a5SJean Delvare 	return 0;
283292fc1a5SJean Delvare 
284292fc1a5SJean Delvare exit_device_put:
285292fc1a5SJean Delvare 	platform_device_put(pdev);
286292fc1a5SJean Delvare exit:
2878d5d45fbSJean Delvare 	return err;
2888d5d45fbSJean Delvare }
2898d5d45fbSJean Delvare 
smsc47b397_find(void)2908528e07eSGuenter Roeck static int __init smsc47b397_find(void)
2918d5d45fbSJean Delvare {
2928d5d45fbSJean Delvare 	u8 id, rev;
29380930776SCraig Kelley 	char *name;
2948528e07eSGuenter Roeck 	unsigned short addr;
2958c082675SGuenter Roeck 	int err;
2968d5d45fbSJean Delvare 
2978c082675SGuenter Roeck 	err = superio_enter();
2988c082675SGuenter Roeck 	if (err)
2998c082675SGuenter Roeck 		return err;
3008c082675SGuenter Roeck 
30167b671bcSJean Delvare 	id = force_id ? force_id : superio_inb(SUPERIO_REG_DEVID);
3028d5d45fbSJean Delvare 
30380930776SCraig Kelley 	switch (id) {
30480930776SCraig Kelley 	case 0x81:
30580930776SCraig Kelley 		name = "SCH5307-NS";
30680930776SCraig Kelley 		break;
30780930776SCraig Kelley 	case 0x6f:
30880930776SCraig Kelley 		name = "LPC47B397-NC";
30980930776SCraig Kelley 		break;
31080930776SCraig Kelley 	case 0x85:
31180930776SCraig Kelley 	case 0x8c:
31280930776SCraig Kelley 		name = "SCH5317";
31380930776SCraig Kelley 		break;
31480930776SCraig Kelley 	default:
3158d5d45fbSJean Delvare 		superio_exit();
3168d5d45fbSJean Delvare 		return -ENODEV;
3178d5d45fbSJean Delvare 	}
3188d5d45fbSJean Delvare 
3198d5d45fbSJean Delvare 	rev = superio_inb(SUPERIO_REG_DEVREV);
3208d5d45fbSJean Delvare 
3218d5d45fbSJean Delvare 	superio_select(SUPERIO_REG_LD8);
3228528e07eSGuenter Roeck 	addr = (superio_inb(SUPERIO_REG_BASE_MSB) << 8)
3238d5d45fbSJean Delvare 		 |  superio_inb(SUPERIO_REG_BASE_LSB);
3248d5d45fbSJean Delvare 
325bf1a85efSJoe Perches 	pr_info("found SMSC %s (base address 0x%04x, revision %u)\n",
3268528e07eSGuenter Roeck 		name, addr, rev);
3278d5d45fbSJean Delvare 
3288d5d45fbSJean Delvare 	superio_exit();
3298528e07eSGuenter Roeck 	return addr;
3308d5d45fbSJean Delvare }
3318d5d45fbSJean Delvare 
smsc47b397_init(void)3328d5d45fbSJean Delvare static int __init smsc47b397_init(void)
3338d5d45fbSJean Delvare {
334292fc1a5SJean Delvare 	unsigned short address;
3358d5d45fbSJean Delvare 	int ret;
3368d5d45fbSJean Delvare 
3378528e07eSGuenter Roeck 	ret = smsc47b397_find();
3388528e07eSGuenter Roeck 	if (ret < 0)
3398d5d45fbSJean Delvare 		return ret;
3408528e07eSGuenter Roeck 	address = ret;
3418d5d45fbSJean Delvare 
342292fc1a5SJean Delvare 	ret = platform_driver_register(&smsc47b397_driver);
343292fc1a5SJean Delvare 	if (ret)
344292fc1a5SJean Delvare 		goto exit;
345292fc1a5SJean Delvare 
346292fc1a5SJean Delvare 	/* Sets global pdev as a side effect */
347292fc1a5SJean Delvare 	ret = smsc47b397_device_add(address);
348292fc1a5SJean Delvare 	if (ret)
349292fc1a5SJean Delvare 		goto exit_driver;
350292fc1a5SJean Delvare 
351292fc1a5SJean Delvare 	return 0;
352292fc1a5SJean Delvare 
353292fc1a5SJean Delvare exit_driver:
354292fc1a5SJean Delvare 	platform_driver_unregister(&smsc47b397_driver);
355292fc1a5SJean Delvare exit:
356292fc1a5SJean Delvare 	return ret;
3578d5d45fbSJean Delvare }
3588d5d45fbSJean Delvare 
smsc47b397_exit(void)3598d5d45fbSJean Delvare static void __exit smsc47b397_exit(void)
3608d5d45fbSJean Delvare {
361292fc1a5SJean Delvare 	platform_device_unregister(pdev);
362292fc1a5SJean Delvare 	platform_driver_unregister(&smsc47b397_driver);
3638d5d45fbSJean Delvare }
3648d5d45fbSJean Delvare 
3658d5d45fbSJean Delvare MODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>");
3668d5d45fbSJean Delvare MODULE_DESCRIPTION("SMSC LPC47B397 driver");
3678d5d45fbSJean Delvare MODULE_LICENSE("GPL");
3688d5d45fbSJean Delvare 
3698d5d45fbSJean Delvare module_init(smsc47b397_init);
3708d5d45fbSJean Delvare module_exit(smsc47b397_exit);
371