18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * smsc47b397.c - Part of lm_sensors, Linux kernel modules
48c2ecf20Sopenharmony_ci * for hardware monitoring
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Supports the SMSC LPC47B397-NC Super-I/O chip.
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Author/Maintainer: Mark M. Hoffman <mhoffman@lightlink.com>
98c2ecf20Sopenharmony_ci * Copyright (C) 2004 Utilitek Systems, Inc.
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * derived in part from smsc47m1.c:
128c2ecf20Sopenharmony_ci * Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
138c2ecf20Sopenharmony_ci * Copyright (C) 2004 Jean Delvare <jdelvare@suse.de>
148c2ecf20Sopenharmony_ci */
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#include <linux/module.h>
198c2ecf20Sopenharmony_ci#include <linux/slab.h>
208c2ecf20Sopenharmony_ci#include <linux/ioport.h>
218c2ecf20Sopenharmony_ci#include <linux/jiffies.h>
228c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
238c2ecf20Sopenharmony_ci#include <linux/hwmon.h>
248c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h>
258c2ecf20Sopenharmony_ci#include <linux/err.h>
268c2ecf20Sopenharmony_ci#include <linux/init.h>
278c2ecf20Sopenharmony_ci#include <linux/mutex.h>
288c2ecf20Sopenharmony_ci#include <linux/acpi.h>
298c2ecf20Sopenharmony_ci#include <linux/io.h>
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic unsigned short force_id;
328c2ecf20Sopenharmony_cimodule_param(force_id, ushort, 0);
338c2ecf20Sopenharmony_ciMODULE_PARM_DESC(force_id, "Override the detected device ID");
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic struct platform_device *pdev;
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci#define DRVNAME "smsc47b397"
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci/* Super-I/0 registers and commands */
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci#define	REG	0x2e	/* The register to read/write */
428c2ecf20Sopenharmony_ci#define	VAL	0x2f	/* The value to read/write */
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic inline void superio_outb(int reg, int val)
458c2ecf20Sopenharmony_ci{
468c2ecf20Sopenharmony_ci	outb(reg, REG);
478c2ecf20Sopenharmony_ci	outb(val, VAL);
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic inline int superio_inb(int reg)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	outb(reg, REG);
538c2ecf20Sopenharmony_ci	return inb(VAL);
548c2ecf20Sopenharmony_ci}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci/* select superio logical device */
578c2ecf20Sopenharmony_cistatic inline void superio_select(int ld)
588c2ecf20Sopenharmony_ci{
598c2ecf20Sopenharmony_ci	superio_outb(0x07, ld);
608c2ecf20Sopenharmony_ci}
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistatic inline int superio_enter(void)
638c2ecf20Sopenharmony_ci{
648c2ecf20Sopenharmony_ci	if (!request_muxed_region(REG, 2, DRVNAME))
658c2ecf20Sopenharmony_ci		return -EBUSY;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	outb(0x55, REG);
688c2ecf20Sopenharmony_ci	return 0;
698c2ecf20Sopenharmony_ci}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_cistatic inline void superio_exit(void)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	outb(0xAA, REG);
748c2ecf20Sopenharmony_ci	release_region(REG, 2);
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci#define SUPERIO_REG_DEVID	0x20
788c2ecf20Sopenharmony_ci#define SUPERIO_REG_DEVREV	0x21
798c2ecf20Sopenharmony_ci#define SUPERIO_REG_BASE_MSB	0x60
808c2ecf20Sopenharmony_ci#define SUPERIO_REG_BASE_LSB	0x61
818c2ecf20Sopenharmony_ci#define SUPERIO_REG_LD8		0x08
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci#define SMSC_EXTENT		0x02
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci/* 0 <= nr <= 3 */
868c2ecf20Sopenharmony_cistatic u8 smsc47b397_reg_temp[] = {0x25, 0x26, 0x27, 0x80};
878c2ecf20Sopenharmony_ci#define SMSC47B397_REG_TEMP(nr)	(smsc47b397_reg_temp[(nr)])
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci/* 0 <= nr <= 3 */
908c2ecf20Sopenharmony_ci#define SMSC47B397_REG_FAN_LSB(nr) (0x28 + 2 * (nr))
918c2ecf20Sopenharmony_ci#define SMSC47B397_REG_FAN_MSB(nr) (0x29 + 2 * (nr))
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistruct smsc47b397_data {
948c2ecf20Sopenharmony_ci	unsigned short addr;
958c2ecf20Sopenharmony_ci	struct mutex lock;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	struct mutex update_lock;
988c2ecf20Sopenharmony_ci	unsigned long last_updated; /* in jiffies */
998c2ecf20Sopenharmony_ci	int valid;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	/* register values */
1028c2ecf20Sopenharmony_ci	u16 fan[4];
1038c2ecf20Sopenharmony_ci	u8 temp[4];
1048c2ecf20Sopenharmony_ci};
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_cistatic int smsc47b397_read_value(struct smsc47b397_data *data, u8 reg)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	int res;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	mutex_lock(&data->lock);
1118c2ecf20Sopenharmony_ci	outb(reg, data->addr);
1128c2ecf20Sopenharmony_ci	res = inb_p(data->addr + 1);
1138c2ecf20Sopenharmony_ci	mutex_unlock(&data->lock);
1148c2ecf20Sopenharmony_ci	return res;
1158c2ecf20Sopenharmony_ci}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_cistatic struct smsc47b397_data *smsc47b397_update_device(struct device *dev)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	struct smsc47b397_data *data = dev_get_drvdata(dev);
1208c2ecf20Sopenharmony_ci	int i;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	mutex_lock(&data->update_lock);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
1258c2ecf20Sopenharmony_ci		dev_dbg(dev, "starting device update...\n");
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci		/* 4 temperature inputs, 4 fan inputs */
1288c2ecf20Sopenharmony_ci		for (i = 0; i < 4; i++) {
1298c2ecf20Sopenharmony_ci			data->temp[i] = smsc47b397_read_value(data,
1308c2ecf20Sopenharmony_ci					SMSC47B397_REG_TEMP(i));
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci			/* must read LSB first */
1338c2ecf20Sopenharmony_ci			data->fan[i]  = smsc47b397_read_value(data,
1348c2ecf20Sopenharmony_ci					SMSC47B397_REG_FAN_LSB(i));
1358c2ecf20Sopenharmony_ci			data->fan[i] |= smsc47b397_read_value(data,
1368c2ecf20Sopenharmony_ci					SMSC47B397_REG_FAN_MSB(i)) << 8;
1378c2ecf20Sopenharmony_ci		}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci		data->last_updated = jiffies;
1408c2ecf20Sopenharmony_ci		data->valid = 1;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci		dev_dbg(dev, "... device update complete\n");
1438c2ecf20Sopenharmony_ci	}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	mutex_unlock(&data->update_lock);
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	return data;
1488c2ecf20Sopenharmony_ci}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci/*
1518c2ecf20Sopenharmony_ci * TEMP: 0.001C/bit (-128C to +127C)
1528c2ecf20Sopenharmony_ci * REG: 1C/bit, two's complement
1538c2ecf20Sopenharmony_ci */
1548c2ecf20Sopenharmony_cistatic int temp_from_reg(u8 reg)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	return (s8)reg * 1000;
1578c2ecf20Sopenharmony_ci}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_cistatic ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
1608c2ecf20Sopenharmony_ci			 char *buf)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
1638c2ecf20Sopenharmony_ci	struct smsc47b397_data *data = smsc47b397_update_device(dev);
1648c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", temp_from_reg(data->temp[attr->index]));
1658c2ecf20Sopenharmony_ci}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0);
1688c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1);
1698c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2);
1708c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3);
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci/*
1738c2ecf20Sopenharmony_ci * FAN: 1 RPM/bit
1748c2ecf20Sopenharmony_ci * REG: count of 90kHz pulses / revolution
1758c2ecf20Sopenharmony_ci */
1768c2ecf20Sopenharmony_cistatic int fan_from_reg(u16 reg)
1778c2ecf20Sopenharmony_ci{
1788c2ecf20Sopenharmony_ci	if (reg == 0 || reg == 0xffff)
1798c2ecf20Sopenharmony_ci		return 0;
1808c2ecf20Sopenharmony_ci	return 90000 * 60 / reg;
1818c2ecf20Sopenharmony_ci}
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_cistatic ssize_t fan_show(struct device *dev, struct device_attribute *devattr,
1848c2ecf20Sopenharmony_ci			char *buf)
1858c2ecf20Sopenharmony_ci{
1868c2ecf20Sopenharmony_ci	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
1878c2ecf20Sopenharmony_ci	struct smsc47b397_data *data = smsc47b397_update_device(dev);
1888c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", fan_from_reg(data->fan[attr->index]));
1898c2ecf20Sopenharmony_ci}
1908c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(fan1_input, fan, 0);
1918c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(fan2_input, fan, 1);
1928c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(fan3_input, fan, 2);
1938c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(fan4_input, fan, 3);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic struct attribute *smsc47b397_attrs[] = {
1968c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp1_input.dev_attr.attr,
1978c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp2_input.dev_attr.attr,
1988c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp3_input.dev_attr.attr,
1998c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp4_input.dev_attr.attr,
2008c2ecf20Sopenharmony_ci	&sensor_dev_attr_fan1_input.dev_attr.attr,
2018c2ecf20Sopenharmony_ci	&sensor_dev_attr_fan2_input.dev_attr.attr,
2028c2ecf20Sopenharmony_ci	&sensor_dev_attr_fan3_input.dev_attr.attr,
2038c2ecf20Sopenharmony_ci	&sensor_dev_attr_fan4_input.dev_attr.attr,
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	NULL
2068c2ecf20Sopenharmony_ci};
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(smsc47b397);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_cistatic int smsc47b397_probe(struct platform_device *pdev);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_cistatic struct platform_driver smsc47b397_driver = {
2138c2ecf20Sopenharmony_ci	.driver = {
2148c2ecf20Sopenharmony_ci		.name	= DRVNAME,
2158c2ecf20Sopenharmony_ci	},
2168c2ecf20Sopenharmony_ci	.probe		= smsc47b397_probe,
2178c2ecf20Sopenharmony_ci};
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_cistatic int smsc47b397_probe(struct platform_device *pdev)
2208c2ecf20Sopenharmony_ci{
2218c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
2228c2ecf20Sopenharmony_ci	struct smsc47b397_data *data;
2238c2ecf20Sopenharmony_ci	struct device *hwmon_dev;
2248c2ecf20Sopenharmony_ci	struct resource *res;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
2278c2ecf20Sopenharmony_ci	if (!devm_request_region(dev, res->start, SMSC_EXTENT,
2288c2ecf20Sopenharmony_ci				 smsc47b397_driver.driver.name)) {
2298c2ecf20Sopenharmony_ci		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
2308c2ecf20Sopenharmony_ci			(unsigned long)res->start,
2318c2ecf20Sopenharmony_ci			(unsigned long)res->start + SMSC_EXTENT - 1);
2328c2ecf20Sopenharmony_ci		return -EBUSY;
2338c2ecf20Sopenharmony_ci	}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	data = devm_kzalloc(dev, sizeof(struct smsc47b397_data), GFP_KERNEL);
2368c2ecf20Sopenharmony_ci	if (!data)
2378c2ecf20Sopenharmony_ci		return -ENOMEM;
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	data->addr = res->start;
2408c2ecf20Sopenharmony_ci	mutex_init(&data->lock);
2418c2ecf20Sopenharmony_ci	mutex_init(&data->update_lock);
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	hwmon_dev = devm_hwmon_device_register_with_groups(dev, "smsc47b397",
2448c2ecf20Sopenharmony_ci							   data,
2458c2ecf20Sopenharmony_ci							   smsc47b397_groups);
2468c2ecf20Sopenharmony_ci	return PTR_ERR_OR_ZERO(hwmon_dev);
2478c2ecf20Sopenharmony_ci}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_cistatic int __init smsc47b397_device_add(unsigned short address)
2508c2ecf20Sopenharmony_ci{
2518c2ecf20Sopenharmony_ci	struct resource res = {
2528c2ecf20Sopenharmony_ci		.start	= address,
2538c2ecf20Sopenharmony_ci		.end	= address + SMSC_EXTENT - 1,
2548c2ecf20Sopenharmony_ci		.name	= DRVNAME,
2558c2ecf20Sopenharmony_ci		.flags	= IORESOURCE_IO,
2568c2ecf20Sopenharmony_ci	};
2578c2ecf20Sopenharmony_ci	int err;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	err = acpi_check_resource_conflict(&res);
2608c2ecf20Sopenharmony_ci	if (err)
2618c2ecf20Sopenharmony_ci		goto exit;
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	pdev = platform_device_alloc(DRVNAME, address);
2648c2ecf20Sopenharmony_ci	if (!pdev) {
2658c2ecf20Sopenharmony_ci		err = -ENOMEM;
2668c2ecf20Sopenharmony_ci		pr_err("Device allocation failed\n");
2678c2ecf20Sopenharmony_ci		goto exit;
2688c2ecf20Sopenharmony_ci	}
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	err = platform_device_add_resources(pdev, &res, 1);
2718c2ecf20Sopenharmony_ci	if (err) {
2728c2ecf20Sopenharmony_ci		pr_err("Device resource addition failed (%d)\n", err);
2738c2ecf20Sopenharmony_ci		goto exit_device_put;
2748c2ecf20Sopenharmony_ci	}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	err = platform_device_add(pdev);
2778c2ecf20Sopenharmony_ci	if (err) {
2788c2ecf20Sopenharmony_ci		pr_err("Device addition failed (%d)\n", err);
2798c2ecf20Sopenharmony_ci		goto exit_device_put;
2808c2ecf20Sopenharmony_ci	}
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	return 0;
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ciexit_device_put:
2858c2ecf20Sopenharmony_ci	platform_device_put(pdev);
2868c2ecf20Sopenharmony_ciexit:
2878c2ecf20Sopenharmony_ci	return err;
2888c2ecf20Sopenharmony_ci}
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_cistatic int __init smsc47b397_find(void)
2918c2ecf20Sopenharmony_ci{
2928c2ecf20Sopenharmony_ci	u8 id, rev;
2938c2ecf20Sopenharmony_ci	char *name;
2948c2ecf20Sopenharmony_ci	unsigned short addr;
2958c2ecf20Sopenharmony_ci	int err;
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	err = superio_enter();
2988c2ecf20Sopenharmony_ci	if (err)
2998c2ecf20Sopenharmony_ci		return err;
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	id = force_id ? force_id : superio_inb(SUPERIO_REG_DEVID);
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_ci	switch (id) {
3048c2ecf20Sopenharmony_ci	case 0x81:
3058c2ecf20Sopenharmony_ci		name = "SCH5307-NS";
3068c2ecf20Sopenharmony_ci		break;
3078c2ecf20Sopenharmony_ci	case 0x6f:
3088c2ecf20Sopenharmony_ci		name = "LPC47B397-NC";
3098c2ecf20Sopenharmony_ci		break;
3108c2ecf20Sopenharmony_ci	case 0x85:
3118c2ecf20Sopenharmony_ci	case 0x8c:
3128c2ecf20Sopenharmony_ci		name = "SCH5317";
3138c2ecf20Sopenharmony_ci		break;
3148c2ecf20Sopenharmony_ci	default:
3158c2ecf20Sopenharmony_ci		superio_exit();
3168c2ecf20Sopenharmony_ci		return -ENODEV;
3178c2ecf20Sopenharmony_ci	}
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	rev = superio_inb(SUPERIO_REG_DEVREV);
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	superio_select(SUPERIO_REG_LD8);
3228c2ecf20Sopenharmony_ci	addr = (superio_inb(SUPERIO_REG_BASE_MSB) << 8)
3238c2ecf20Sopenharmony_ci		 |  superio_inb(SUPERIO_REG_BASE_LSB);
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci	pr_info("found SMSC %s (base address 0x%04x, revision %u)\n",
3268c2ecf20Sopenharmony_ci		name, addr, rev);
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	superio_exit();
3298c2ecf20Sopenharmony_ci	return addr;
3308c2ecf20Sopenharmony_ci}
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_cistatic int __init smsc47b397_init(void)
3338c2ecf20Sopenharmony_ci{
3348c2ecf20Sopenharmony_ci	unsigned short address;
3358c2ecf20Sopenharmony_ci	int ret;
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	ret = smsc47b397_find();
3388c2ecf20Sopenharmony_ci	if (ret < 0)
3398c2ecf20Sopenharmony_ci		return ret;
3408c2ecf20Sopenharmony_ci	address = ret;
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_ci	ret = platform_driver_register(&smsc47b397_driver);
3438c2ecf20Sopenharmony_ci	if (ret)
3448c2ecf20Sopenharmony_ci		goto exit;
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_ci	/* Sets global pdev as a side effect */
3478c2ecf20Sopenharmony_ci	ret = smsc47b397_device_add(address);
3488c2ecf20Sopenharmony_ci	if (ret)
3498c2ecf20Sopenharmony_ci		goto exit_driver;
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_ci	return 0;
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ciexit_driver:
3548c2ecf20Sopenharmony_ci	platform_driver_unregister(&smsc47b397_driver);
3558c2ecf20Sopenharmony_ciexit:
3568c2ecf20Sopenharmony_ci	return ret;
3578c2ecf20Sopenharmony_ci}
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_cistatic void __exit smsc47b397_exit(void)
3608c2ecf20Sopenharmony_ci{
3618c2ecf20Sopenharmony_ci	platform_device_unregister(pdev);
3628c2ecf20Sopenharmony_ci	platform_driver_unregister(&smsc47b397_driver);
3638c2ecf20Sopenharmony_ci}
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>");
3668c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SMSC LPC47B397 driver");
3678c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_cimodule_init(smsc47b397_init);
3708c2ecf20Sopenharmony_cimodule_exit(smsc47b397_exit);
371