1331722Seadler// SPDX-License-Identifier: GPL-2.0-or-later
21590Srgrimes/*
31590Srgrimes * smsc47b397.c - Part of lm_sensors, Linux kernel modules
41590Srgrimes * for hardware monitoring
51590Srgrimes *
61590Srgrimes * Supports the SMSC LPC47B397-NC Super-I/O chip.
71590Srgrimes *
81590Srgrimes * Author/Maintainer: Mark M. Hoffman <mhoffman@lightlink.com>
91590Srgrimes * Copyright (C) 2004 Utilitek Systems, Inc.
101590Srgrimes *
111590Srgrimes * derived in part from smsc47m1.c:
121590Srgrimes * Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
131590Srgrimes * Copyright (C) 2004 Jean Delvare <jdelvare@suse.de>
141590Srgrimes */
151590Srgrimes
161590Srgrimes#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
171590Srgrimes
181590Srgrimes#include <linux/module.h>
191590Srgrimes#include <linux/slab.h>
201590Srgrimes#include <linux/ioport.h>
211590Srgrimes#include <linux/jiffies.h>
221590Srgrimes#include <linux/platform_device.h>
231590Srgrimes#include <linux/hwmon.h>
241590Srgrimes#include <linux/hwmon-sysfs.h>
251590Srgrimes#include <linux/err.h>
261590Srgrimes#include <linux/init.h>
271590Srgrimes#include <linux/mutex.h>
281590Srgrimes#include <linux/acpi.h>
291590Srgrimes#include <linux/io.h>
301590Srgrimes
3174769Smikehstatic unsigned short force_id;
321590Srgrimesmodule_param(force_id, ushort, 0);
3374769SmikehMODULE_PARM_DESC(force_id, "Override the detected device ID");
341590Srgrimes
3599112Sobrienstatic struct platform_device *pdev;
3699112Sobrien
371590Srgrimes#define DRVNAME "smsc47b397"
3891227Sbde
3991227Sbde/* Super-I/0 registers and commands */
40298157Sjilles
41298157Sjilles#define	REG	0x2e	/* The register to read/write */
421590Srgrimes#define	VAL	0x2f	/* The value to read/write */
431590Srgrimes
441590Srgrimesstatic inline void superio_outb(int reg, int val)
451590Srgrimes{
461590Srgrimes	outb(reg, REG);
471590Srgrimes	outb(val, VAL);
481590Srgrimes}
491590Srgrimes
501590Srgrimesstatic inline int superio_inb(int reg)
5192921Simp{
5277274Smikeh	outb(reg, REG);
531590Srgrimes	return inb(VAL);
541590Srgrimes}
551590Srgrimes
561590Srgrimes/* select superio logical device */
57216564Scharnierstatic inline void superio_select(int ld)
581590Srgrimes{
591590Srgrimes	superio_outb(0x07, ld);
601590Srgrimes}
611590Srgrimes
6277274Smikehstatic inline int superio_enter(void)
631590Srgrimes{
6477274Smikeh	if (!request_muxed_region(REG, 2, DRVNAME))
651590Srgrimes		return -EBUSY;
661590Srgrimes
671590Srgrimes	outb(0x55, REG);
681590Srgrimes	return 0;
691590Srgrimes}
70173438Sdds
71216564Scharnierstatic inline void superio_exit(void)
721590Srgrimes{
731590Srgrimes	outb(0xAA, REG);
741590Srgrimes	release_region(REG, 2);
751590Srgrimes}
761590Srgrimes
7777274Smikeh#define SUPERIO_REG_DEVID	0x20
781590Srgrimes#define SUPERIO_REG_DEVREV	0x21
791590Srgrimes#define SUPERIO_REG_BASE_MSB	0x60
801590Srgrimes#define SUPERIO_REG_BASE_LSB	0x61
811590Srgrimes#define SUPERIO_REG_LD8		0x08
821590Srgrimes
831590Srgrimes#define SMSC_EXTENT		0x02
8477274Smikeh
851590Srgrimes/* 0 <= nr <= 3 */
861590Srgrimesstatic u8 smsc47b397_reg_temp[] = {0x25, 0x26, 0x27, 0x80};
871590Srgrimes#define SMSC47B397_REG_TEMP(nr)	(smsc47b397_reg_temp[(nr)])
881590Srgrimes
891590Srgrimes/* 0 <= nr <= 3 */
901590Srgrimes#define SMSC47B397_REG_FAN_LSB(nr) (0x28 + 2 * (nr))
911590Srgrimes#define SMSC47B397_REG_FAN_MSB(nr) (0x29 + 2 * (nr))
921590Srgrimes
93216564Scharnierstruct smsc47b397_data {
941590Srgrimes	unsigned short addr;
951590Srgrimes	struct mutex lock;
961590Srgrimes
971590Srgrimes	struct mutex update_lock;
981590Srgrimes	unsigned long last_updated; /* in jiffies */
991590Srgrimes	bool valid;
1001590Srgrimes
1011590Srgrimes	/* register values */
1021590Srgrimes	u16 fan[4];
1031590Srgrimes	u8 temp[4];
1041590Srgrimes};
1051590Srgrimes
106216564Scharnierstatic int smsc47b397_read_value(struct smsc47b397_data *data, u8 reg)
1071590Srgrimes{
1081590Srgrimes	int res;
1091590Srgrimes
1101590Srgrimes	mutex_lock(&data->lock);
11177274Smikeh	outb(reg, data->addr);
11277274Smikeh	res = inb_p(data->addr + 1);
1131590Srgrimes	mutex_unlock(&data->lock);
1141590Srgrimes	return res;
1151590Srgrimes}
1161590Srgrimes
1171590Srgrimesstatic struct smsc47b397_data *smsc47b397_update_device(struct device *dev)
1181590Srgrimes{
119216564Scharnier	struct smsc47b397_data *data = dev_get_drvdata(dev);
1201590Srgrimes	int i;
12177274Smikeh
1221590Srgrimes	mutex_lock(&data->update_lock);
12377274Smikeh
1248874Srgrimes	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
12577274Smikeh		dev_dbg(dev, "starting device update...\n");
1261590Srgrimes
1271590Srgrimes		/* 4 temperature inputs, 4 fan inputs */
1281590Srgrimes		for (i = 0; i < 4; i++) {
1291590Srgrimes			data->temp[i] = smsc47b397_read_value(data,
13077274Smikeh					SMSC47B397_REG_TEMP(i));
1311590Srgrimes
1321590Srgrimes			/* must read LSB first */
133216564Scharnier			data->fan[i]  = smsc47b397_read_value(data,
1341590Srgrimes					SMSC47B397_REG_FAN_LSB(i));
13577274Smikeh			data->fan[i] |= smsc47b397_read_value(data,
1361590Srgrimes					SMSC47B397_REG_FAN_MSB(i)) << 8;
13777274Smikeh		}
13877274Smikeh
13977274Smikeh		data->last_updated = jiffies;
1401590Srgrimes		data->valid = true;
1411590Srgrimes
1421590Srgrimes		dev_dbg(dev, "... device update complete\n");
14377274Smikeh	}
1441590Srgrimes
14577274Smikeh	mutex_unlock(&data->update_lock);
1461590Srgrimes
1471590Srgrimes	return data;
14877274Smikeh}
14927643Scharnier
1501590Srgrimes/*
1511590Srgrimes * TEMP: 0.001C/bit (-128C to +127C)
15277274Smikeh * REG: 1C/bit, two's complement
1531590Srgrimes */
1541590Srgrimesstatic int temp_from_reg(u8 reg)
1551590Srgrimes{
1561590Srgrimes	return (s8)reg * 1000;
1571590Srgrimes}
1581590Srgrimes
1591590Srgrimesstatic ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
1601590Srgrimes			 char *buf)
1611590Srgrimes{
162216564Scharnier	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
1631590Srgrimes	struct smsc47b397_data *data = smsc47b397_update_device(dev);
1641590Srgrimes	return sprintf(buf, "%d\n", temp_from_reg(data->temp[attr->index]));
16577274Smikeh}
16677274Smikeh
1671590Srgrimesstatic SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0);
1681590Srgrimesstatic SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1);
1691590Srgrimesstatic SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2);
17077274Smikehstatic SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3);
1711590Srgrimes
17277274Smikeh/*
17388227Sache * FAN: 1 RPM/bit
17477274Smikeh * REG: count of 90kHz pulses / revolution
1751590Srgrimes */
1761590Srgrimesstatic int fan_from_reg(u16 reg)
1771590Srgrimes{
1781590Srgrimes	if (reg == 0 || reg == 0xffff)
1791590Srgrimes		return 0;
1801590Srgrimes	return 90000 * 60 / reg;
1811590Srgrimes}
1821590Srgrimes
1831590Srgrimesstatic ssize_t fan_show(struct device *dev, struct device_attribute *devattr,
1841590Srgrimes			char *buf)
1851590Srgrimes{
1861590Srgrimes	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
1871590Srgrimes	struct smsc47b397_data *data = smsc47b397_update_device(dev);
1881590Srgrimes	return sprintf(buf, "%d\n", fan_from_reg(data->fan[attr->index]));
1891590Srgrimes}
1901590Srgrimesstatic SENSOR_DEVICE_ATTR_RO(fan1_input, fan, 0);
1911590Srgrimesstatic SENSOR_DEVICE_ATTR_RO(fan2_input, fan, 1);
1921590Srgrimesstatic SENSOR_DEVICE_ATTR_RO(fan3_input, fan, 2);
1931590Srgrimesstatic SENSOR_DEVICE_ATTR_RO(fan4_input, fan, 3);
1941590Srgrimes
1951590Srgrimesstatic struct attribute *smsc47b397_attrs[] = {
1961590Srgrimes	&sensor_dev_attr_temp1_input.dev_attr.attr,
1971590Srgrimes	&sensor_dev_attr_temp2_input.dev_attr.attr,
1981590Srgrimes	&sensor_dev_attr_temp3_input.dev_attr.attr,
1991590Srgrimes	&sensor_dev_attr_temp4_input.dev_attr.attr,
2001590Srgrimes	&sensor_dev_attr_fan1_input.dev_attr.attr,
2011590Srgrimes	&sensor_dev_attr_fan2_input.dev_attr.attr,
2021590Srgrimes	&sensor_dev_attr_fan3_input.dev_attr.attr,
2031590Srgrimes	&sensor_dev_attr_fan4_input.dev_attr.attr,
2041590Srgrimes
2051590Srgrimes	NULL
20677274Smikeh};
2071590Srgrimes
2081590SrgrimesATTRIBUTE_GROUPS(smsc47b397);
2091590Srgrimes
2101590Srgrimesstatic int smsc47b397_probe(struct platform_device *pdev);
2111590Srgrimes
2121590Srgrimesstatic struct platform_driver smsc47b397_driver = {
2131590Srgrimes	.driver = {
2141590Srgrimes		.name	= DRVNAME,
2151590Srgrimes	},
2161590Srgrimes	.probe		= smsc47b397_probe,
217216564Scharnier};
2181590Srgrimes
21977274Smikehstatic int smsc47b397_probe(struct platform_device *pdev)
2201590Srgrimes{
2211590Srgrimes	struct device *dev = &pdev->dev;
2221590Srgrimes	struct smsc47b397_data *data;
2231590Srgrimes	struct device *hwmon_dev;
22477274Smikeh	struct resource *res;
2251590Srgrimes
2261590Srgrimes	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
2271590Srgrimes	if (!devm_request_region(dev, res->start, SMSC_EXTENT,
2281590Srgrimes				 smsc47b397_driver.driver.name)) {
22977274Smikeh		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
2301590Srgrimes			(unsigned long)res->start,
2311590Srgrimes			(unsigned long)res->start + SMSC_EXTENT - 1);
2321590Srgrimes		return -EBUSY;
23374769Smikeh	}
23474769Smikeh
2351590Srgrimes	data = devm_kzalloc(dev, sizeof(struct smsc47b397_data), GFP_KERNEL);
2361590Srgrimes	if (!data)
237216564Scharnier		return -ENOMEM;
2381590Srgrimes
2391590Srgrimes	data->addr = res->start;
24074769Smikeh	mutex_init(&data->lock);
241246860Sdim	mutex_init(&data->update_lock);
242246860Sdim
2431590Srgrimes	hwmon_dev = devm_hwmon_device_register_with_groups(dev, "smsc47b397",
2441590Srgrimes							   data,
2451590Srgrimes							   smsc47b397_groups);
2461590Srgrimes	return PTR_ERR_OR_ZERO(hwmon_dev);
2471590Srgrimes}
2481590Srgrimes
2491590Srgrimesstatic int __init smsc47b397_device_add(unsigned short address)
2501590Srgrimes{
2511590Srgrimes	struct resource res = {
2521590Srgrimes		.start	= address,
2531590Srgrimes		.end	= address + SMSC_EXTENT - 1,
2541590Srgrimes		.name	= DRVNAME,
2551590Srgrimes		.flags	= IORESOURCE_IO,
25618532Sbde	};
25718532Sbde	int err;
25818532Sbde
2591590Srgrimes	err = acpi_check_resource_conflict(&res);
2601590Srgrimes	if (err)
2611590Srgrimes		goto exit;
2621590Srgrimes
2631590Srgrimes	pdev = platform_device_alloc(DRVNAME, address);
2641590Srgrimes	if (!pdev) {
2651590Srgrimes		err = -ENOMEM;
266216564Scharnier		pr_err("Device allocation failed\n");
2671590Srgrimes		goto exit;
2681590Srgrimes	}
2691590Srgrimes
2701590Srgrimes	err = platform_device_add_resources(pdev, &res, 1);
27177274Smikeh	if (err) {
27277274Smikeh		pr_err("Device resource addition failed (%d)\n", err);
2731590Srgrimes		goto exit_device_put;
27474769Smikeh	}
27577274Smikeh
2761590Srgrimes	err = platform_device_add(pdev);
27718532Sbde	if (err) {
2781590Srgrimes		pr_err("Device addition failed (%d)\n", err);
27977274Smikeh		goto exit_device_put;
28077274Smikeh	}
2811590Srgrimes
2821590Srgrimes	return 0;
2831590Srgrimes
2841590Srgrimesexit_device_put:
2851590Srgrimes	platform_device_put(pdev);
2861590Srgrimesexit:
2871590Srgrimes	return err;
2881590Srgrimes}
2891590Srgrimes
29077274Smikehstatic int __init smsc47b397_find(void)
2911590Srgrimes{
2921590Srgrimes	u8 id, rev;
2931590Srgrimes	char *name;
2941590Srgrimes	unsigned short addr;
2951590Srgrimes	int err;
2961590Srgrimes
2971590Srgrimes	err = superio_enter();
298216564Scharnier	if (err)
2991590Srgrimes		return err;
3001590Srgrimes
3011590Srgrimes	id = force_id ? force_id : superio_inb(SUPERIO_REG_DEVID);
3021590Srgrimes
30377274Smikeh	switch (id) {
3041590Srgrimes	case 0x81:
30577274Smikeh		name = "SCH5307-NS";
3061590Srgrimes		break;
3071590Srgrimes	case 0x6f:
3081590Srgrimes		name = "LPC47B397-NC";
3091590Srgrimes		break;
3101590Srgrimes	case 0x85:
3111590Srgrimes	case 0x8c:
3121590Srgrimes		name = "SCH5317";
3131590Srgrimes		break;
31477274Smikeh	default:
3151590Srgrimes		superio_exit();
3161590Srgrimes		return -ENODEV;
3171590Srgrimes	}
3181590Srgrimes
3191590Srgrimes	rev = superio_inb(SUPERIO_REG_DEVREV);
3201590Srgrimes
3211590Srgrimes	superio_select(SUPERIO_REG_LD8);
322216564Scharnier	addr = (superio_inb(SUPERIO_REG_BASE_MSB) << 8)
3231590Srgrimes		 |  superio_inb(SUPERIO_REG_BASE_LSB);
324298157Sjilles
3251590Srgrimes	pr_info("found SMSC %s (base address 0x%04x, revision %u)\n",
326298157Sjilles		name, addr, rev);
327298157Sjilles
328298157Sjilles	superio_exit();
329298157Sjilles	return addr;
330298157Sjilles}
3311590Srgrimes
3321590Srgrimesstatic int __init smsc47b397_init(void)
3331590Srgrimes{
3341590Srgrimes	unsigned short address;
3351590Srgrimes	int ret;
3361590Srgrimes
3371590Srgrimes	ret = smsc47b397_find();
3381590Srgrimes	if (ret < 0)
339216564Scharnier		return ret;
3401590Srgrimes	address = ret;
34177274Smikeh
3421590Srgrimes	ret = platform_driver_register(&smsc47b397_driver);
3431590Srgrimes	if (ret)
3441590Srgrimes		goto exit;
34577274Smikeh
34674769Smikeh	/* Sets global pdev as a side effect */
3471590Srgrimes	ret = smsc47b397_device_add(address);
3481590Srgrimes	if (ret)
3491590Srgrimes		goto exit_driver;
3501590Srgrimes
35177274Smikeh	return 0;
35277274Smikeh
3531590Srgrimesexit_driver:
3541590Srgrimes	platform_driver_unregister(&smsc47b397_driver);
3551590Srgrimesexit:
3561590Srgrimes	return ret;
3571590Srgrimes}
3581590Srgrimes
3591590Srgrimesstatic void __exit smsc47b397_exit(void)
360216564Scharnier{
3611590Srgrimes	platform_device_unregister(pdev);
36277274Smikeh	platform_driver_unregister(&smsc47b397_driver);
3631590Srgrimes}
3641590Srgrimes
3651590SrgrimesMODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>");
3661590SrgrimesMODULE_DESCRIPTION("SMSC LPC47B397 driver");
3671590SrgrimesMODULE_LICENSE("GPL");
3681590Srgrimes
3691590Srgrimesmodule_init(smsc47b397_init);
3701590Srgrimesmodule_exit(smsc47b397_exit);
3711590Srgrimes