18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/***************************************************************************
38c2ecf20Sopenharmony_ci *   Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com>           *
48c2ecf20Sopenharmony_ci *                                                                         *
58c2ecf20Sopenharmony_ci ***************************************************************************/
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/init.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci#include <linux/err.h>
138c2ecf20Sopenharmony_ci#include <linux/io.h>
148c2ecf20Sopenharmony_ci#include <linux/acpi.h>
158c2ecf20Sopenharmony_ci#include <linux/delay.h>
168c2ecf20Sopenharmony_ci#include <linux/fs.h>
178c2ecf20Sopenharmony_ci#include <linux/watchdog.h>
188c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
198c2ecf20Sopenharmony_ci#include <linux/slab.h>
208c2ecf20Sopenharmony_ci#include "sch56xx-common.h"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci/* Insmod parameters */
238c2ecf20Sopenharmony_cistatic int nowayout = WATCHDOG_NOWAYOUT;
248c2ecf20Sopenharmony_cimodule_param(nowayout, int, 0);
258c2ecf20Sopenharmony_ciMODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
268c2ecf20Sopenharmony_ci	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci#define SIO_SCH56XX_LD_EM	0x0C	/* Embedded uController Logical Dev */
298c2ecf20Sopenharmony_ci#define SIO_UNLOCK_KEY		0x55	/* Key to enable Super-I/O */
308c2ecf20Sopenharmony_ci#define SIO_LOCK_KEY		0xAA	/* Key to disable Super-I/O */
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#define SIO_REG_LDSEL		0x07	/* Logical device select */
338c2ecf20Sopenharmony_ci#define SIO_REG_DEVID		0x20	/* Device ID */
348c2ecf20Sopenharmony_ci#define SIO_REG_ENABLE		0x30	/* Logical device enable */
358c2ecf20Sopenharmony_ci#define SIO_REG_ADDR		0x66	/* Logical device address (2 bytes) */
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci#define SIO_SCH5627_ID		0xC6	/* Chipset ID */
388c2ecf20Sopenharmony_ci#define SIO_SCH5636_ID		0xC7	/* Chipset ID */
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#define REGION_LENGTH		10
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define SCH56XX_CMD_READ	0x02
438c2ecf20Sopenharmony_ci#define SCH56XX_CMD_WRITE	0x03
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/* Watchdog registers */
468c2ecf20Sopenharmony_ci#define SCH56XX_REG_WDOG_PRESET		0x58B
478c2ecf20Sopenharmony_ci#define SCH56XX_REG_WDOG_CONTROL	0x58C
488c2ecf20Sopenharmony_ci#define SCH56XX_WDOG_TIME_BASE_SEC	0x01
498c2ecf20Sopenharmony_ci#define SCH56XX_REG_WDOG_OUTPUT_ENABLE	0x58E
508c2ecf20Sopenharmony_ci#define SCH56XX_WDOG_OUTPUT_ENABLE	0x02
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistruct sch56xx_watchdog_data {
538c2ecf20Sopenharmony_ci	u16 addr;
548c2ecf20Sopenharmony_ci	struct mutex *io_lock;
558c2ecf20Sopenharmony_ci	struct watchdog_info wdinfo;
568c2ecf20Sopenharmony_ci	struct watchdog_device wddev;
578c2ecf20Sopenharmony_ci	u8 watchdog_preset;
588c2ecf20Sopenharmony_ci	u8 watchdog_control;
598c2ecf20Sopenharmony_ci	u8 watchdog_output_enable;
608c2ecf20Sopenharmony_ci};
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistatic struct platform_device *sch56xx_pdev;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci/* Super I/O functions */
658c2ecf20Sopenharmony_cistatic inline int superio_inb(int base, int reg)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	outb(reg, base);
688c2ecf20Sopenharmony_ci	return inb(base + 1);
698c2ecf20Sopenharmony_ci}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_cistatic inline int superio_enter(int base)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	/* Don't step on other drivers' I/O space by accident */
748c2ecf20Sopenharmony_ci	if (!request_muxed_region(base, 2, "sch56xx")) {
758c2ecf20Sopenharmony_ci		pr_err("I/O address 0x%04x already in use\n", base);
768c2ecf20Sopenharmony_ci		return -EBUSY;
778c2ecf20Sopenharmony_ci	}
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	outb(SIO_UNLOCK_KEY, base);
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	return 0;
828c2ecf20Sopenharmony_ci}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_cistatic inline void superio_select(int base, int ld)
858c2ecf20Sopenharmony_ci{
868c2ecf20Sopenharmony_ci	outb(SIO_REG_LDSEL, base);
878c2ecf20Sopenharmony_ci	outb(ld, base + 1);
888c2ecf20Sopenharmony_ci}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic inline void superio_exit(int base)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	outb(SIO_LOCK_KEY, base);
938c2ecf20Sopenharmony_ci	release_region(base, 2);
948c2ecf20Sopenharmony_ci}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_cistatic int sch56xx_send_cmd(u16 addr, u8 cmd, u16 reg, u8 v)
978c2ecf20Sopenharmony_ci{
988c2ecf20Sopenharmony_ci	u8 val;
998c2ecf20Sopenharmony_ci	int i;
1008c2ecf20Sopenharmony_ci	/*
1018c2ecf20Sopenharmony_ci	 * According to SMSC for the commands we use the maximum time for
1028c2ecf20Sopenharmony_ci	 * the EM to respond is 15 ms, but testing shows in practice it
1038c2ecf20Sopenharmony_ci	 * responds within 15-32 reads, so we first busy poll, and if
1048c2ecf20Sopenharmony_ci	 * that fails sleep a bit and try again until we are way past
1058c2ecf20Sopenharmony_ci	 * the 15 ms maximum response time.
1068c2ecf20Sopenharmony_ci	 */
1078c2ecf20Sopenharmony_ci	const int max_busy_polls = 64;
1088c2ecf20Sopenharmony_ci	const int max_lazy_polls = 32;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	/* (Optional) Write-Clear the EC to Host Mailbox Register */
1118c2ecf20Sopenharmony_ci	val = inb(addr + 1);
1128c2ecf20Sopenharmony_ci	outb(val, addr + 1);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	/* Set Mailbox Address Pointer to first location in Region 1 */
1158c2ecf20Sopenharmony_ci	outb(0x00, addr + 2);
1168c2ecf20Sopenharmony_ci	outb(0x80, addr + 3);
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	/* Write Request Packet Header */
1198c2ecf20Sopenharmony_ci	outb(cmd, addr + 4); /* VREG Access Type read:0x02 write:0x03 */
1208c2ecf20Sopenharmony_ci	outb(0x01, addr + 5); /* # of Entries: 1 Byte (8-bit) */
1218c2ecf20Sopenharmony_ci	outb(0x04, addr + 2); /* Mailbox AP to first data entry loc. */
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	/* Write Value field */
1248c2ecf20Sopenharmony_ci	if (cmd == SCH56XX_CMD_WRITE)
1258c2ecf20Sopenharmony_ci		outb(v, addr + 4);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	/* Write Address field */
1288c2ecf20Sopenharmony_ci	outb(reg & 0xff, addr + 6);
1298c2ecf20Sopenharmony_ci	outb(reg >> 8, addr + 7);
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	/* Execute the Random Access Command */
1328c2ecf20Sopenharmony_ci	outb(0x01, addr); /* Write 01h to the Host-to-EC register */
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	/* EM Interface Polling "Algorithm" */
1358c2ecf20Sopenharmony_ci	for (i = 0; i < max_busy_polls + max_lazy_polls; i++) {
1368c2ecf20Sopenharmony_ci		if (i >= max_busy_polls)
1378c2ecf20Sopenharmony_ci			msleep(1);
1388c2ecf20Sopenharmony_ci		/* Read Interrupt source Register */
1398c2ecf20Sopenharmony_ci		val = inb(addr + 8);
1408c2ecf20Sopenharmony_ci		/* Write Clear the interrupt source bits */
1418c2ecf20Sopenharmony_ci		if (val)
1428c2ecf20Sopenharmony_ci			outb(val, addr + 8);
1438c2ecf20Sopenharmony_ci		/* Command Completed ? */
1448c2ecf20Sopenharmony_ci		if (val & 0x01)
1458c2ecf20Sopenharmony_ci			break;
1468c2ecf20Sopenharmony_ci	}
1478c2ecf20Sopenharmony_ci	if (i == max_busy_polls + max_lazy_polls) {
1488c2ecf20Sopenharmony_ci		pr_err("Max retries exceeded reading virtual register 0x%04hx (%d)\n",
1498c2ecf20Sopenharmony_ci		       reg, 1);
1508c2ecf20Sopenharmony_ci		return -EIO;
1518c2ecf20Sopenharmony_ci	}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	/*
1548c2ecf20Sopenharmony_ci	 * According to SMSC we may need to retry this, but sofar I've always
1558c2ecf20Sopenharmony_ci	 * seen this succeed in 1 try.
1568c2ecf20Sopenharmony_ci	 */
1578c2ecf20Sopenharmony_ci	for (i = 0; i < max_busy_polls; i++) {
1588c2ecf20Sopenharmony_ci		/* Read EC-to-Host Register */
1598c2ecf20Sopenharmony_ci		val = inb(addr + 1);
1608c2ecf20Sopenharmony_ci		/* Command Completed ? */
1618c2ecf20Sopenharmony_ci		if (val == 0x01)
1628c2ecf20Sopenharmony_ci			break;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci		if (i == 0)
1658c2ecf20Sopenharmony_ci			pr_warn("EC reports: 0x%02x reading virtual register 0x%04hx\n",
1668c2ecf20Sopenharmony_ci				(unsigned int)val, reg);
1678c2ecf20Sopenharmony_ci	}
1688c2ecf20Sopenharmony_ci	if (i == max_busy_polls) {
1698c2ecf20Sopenharmony_ci		pr_err("Max retries exceeded reading virtual register 0x%04hx (%d)\n",
1708c2ecf20Sopenharmony_ci		       reg, 2);
1718c2ecf20Sopenharmony_ci		return -EIO;
1728c2ecf20Sopenharmony_ci	}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	/*
1758c2ecf20Sopenharmony_ci	 * According to the SMSC app note we should now do:
1768c2ecf20Sopenharmony_ci	 *
1778c2ecf20Sopenharmony_ci	 * Set Mailbox Address Pointer to first location in Region 1 *
1788c2ecf20Sopenharmony_ci	 * outb(0x00, addr + 2);
1798c2ecf20Sopenharmony_ci	 * outb(0x80, addr + 3);
1808c2ecf20Sopenharmony_ci	 *
1818c2ecf20Sopenharmony_ci	 * But if we do that things don't work, so let's not.
1828c2ecf20Sopenharmony_ci	 */
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	/* Read Value field */
1858c2ecf20Sopenharmony_ci	if (cmd == SCH56XX_CMD_READ)
1868c2ecf20Sopenharmony_ci		return inb(addr + 4);
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	return 0;
1898c2ecf20Sopenharmony_ci}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ciint sch56xx_read_virtual_reg(u16 addr, u16 reg)
1928c2ecf20Sopenharmony_ci{
1938c2ecf20Sopenharmony_ci	return sch56xx_send_cmd(addr, SCH56XX_CMD_READ, reg, 0);
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sch56xx_read_virtual_reg);
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ciint sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val)
1988c2ecf20Sopenharmony_ci{
1998c2ecf20Sopenharmony_ci	return sch56xx_send_cmd(addr, SCH56XX_CMD_WRITE, reg, val);
2008c2ecf20Sopenharmony_ci}
2018c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sch56xx_write_virtual_reg);
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ciint sch56xx_read_virtual_reg16(u16 addr, u16 reg)
2048c2ecf20Sopenharmony_ci{
2058c2ecf20Sopenharmony_ci	int lsb, msb;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	/* Read LSB first, this will cause the matching MSB to be latched */
2088c2ecf20Sopenharmony_ci	lsb = sch56xx_read_virtual_reg(addr, reg);
2098c2ecf20Sopenharmony_ci	if (lsb < 0)
2108c2ecf20Sopenharmony_ci		return lsb;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	msb = sch56xx_read_virtual_reg(addr, reg + 1);
2138c2ecf20Sopenharmony_ci	if (msb < 0)
2148c2ecf20Sopenharmony_ci		return msb;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	return lsb | (msb << 8);
2178c2ecf20Sopenharmony_ci}
2188c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sch56xx_read_virtual_reg16);
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ciint sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg,
2218c2ecf20Sopenharmony_ci			       int high_nibble)
2228c2ecf20Sopenharmony_ci{
2238c2ecf20Sopenharmony_ci	int msb, lsn;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	/* Read MSB first, this will cause the matching LSN to be latched */
2268c2ecf20Sopenharmony_ci	msb = sch56xx_read_virtual_reg(addr, msb_reg);
2278c2ecf20Sopenharmony_ci	if (msb < 0)
2288c2ecf20Sopenharmony_ci		return msb;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	lsn = sch56xx_read_virtual_reg(addr, lsn_reg);
2318c2ecf20Sopenharmony_ci	if (lsn < 0)
2328c2ecf20Sopenharmony_ci		return lsn;
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	if (high_nibble)
2358c2ecf20Sopenharmony_ci		return (msb << 4) | (lsn >> 4);
2368c2ecf20Sopenharmony_ci	else
2378c2ecf20Sopenharmony_ci		return (msb << 4) | (lsn & 0x0f);
2388c2ecf20Sopenharmony_ci}
2398c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sch56xx_read_virtual_reg12);
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci/*
2428c2ecf20Sopenharmony_ci * Watchdog routines
2438c2ecf20Sopenharmony_ci */
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_cistatic int watchdog_set_timeout(struct watchdog_device *wddev,
2468c2ecf20Sopenharmony_ci				unsigned int timeout)
2478c2ecf20Sopenharmony_ci{
2488c2ecf20Sopenharmony_ci	struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
2498c2ecf20Sopenharmony_ci	unsigned int resolution;
2508c2ecf20Sopenharmony_ci	u8 control;
2518c2ecf20Sopenharmony_ci	int ret;
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	/* 1 second or 60 second resolution? */
2548c2ecf20Sopenharmony_ci	if (timeout <= 255)
2558c2ecf20Sopenharmony_ci		resolution = 1;
2568c2ecf20Sopenharmony_ci	else
2578c2ecf20Sopenharmony_ci		resolution = 60;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	if (timeout < resolution || timeout > (resolution * 255))
2608c2ecf20Sopenharmony_ci		return -EINVAL;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	if (resolution == 1)
2638c2ecf20Sopenharmony_ci		control = data->watchdog_control | SCH56XX_WDOG_TIME_BASE_SEC;
2648c2ecf20Sopenharmony_ci	else
2658c2ecf20Sopenharmony_ci		control = data->watchdog_control & ~SCH56XX_WDOG_TIME_BASE_SEC;
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	if (data->watchdog_control != control) {
2688c2ecf20Sopenharmony_ci		mutex_lock(data->io_lock);
2698c2ecf20Sopenharmony_ci		ret = sch56xx_write_virtual_reg(data->addr,
2708c2ecf20Sopenharmony_ci						SCH56XX_REG_WDOG_CONTROL,
2718c2ecf20Sopenharmony_ci						control);
2728c2ecf20Sopenharmony_ci		mutex_unlock(data->io_lock);
2738c2ecf20Sopenharmony_ci		if (ret)
2748c2ecf20Sopenharmony_ci			return ret;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci		data->watchdog_control = control;
2778c2ecf20Sopenharmony_ci	}
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	/*
2808c2ecf20Sopenharmony_ci	 * Remember new timeout value, but do not write as that (re)starts
2818c2ecf20Sopenharmony_ci	 * the watchdog countdown.
2828c2ecf20Sopenharmony_ci	 */
2838c2ecf20Sopenharmony_ci	data->watchdog_preset = DIV_ROUND_UP(timeout, resolution);
2848c2ecf20Sopenharmony_ci	wddev->timeout = data->watchdog_preset * resolution;
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	return 0;
2878c2ecf20Sopenharmony_ci}
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_cistatic int watchdog_start(struct watchdog_device *wddev)
2908c2ecf20Sopenharmony_ci{
2918c2ecf20Sopenharmony_ci	struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
2928c2ecf20Sopenharmony_ci	int ret;
2938c2ecf20Sopenharmony_ci	u8 val;
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	/*
2968c2ecf20Sopenharmony_ci	 * The sch56xx's watchdog cannot really be started / stopped
2978c2ecf20Sopenharmony_ci	 * it is always running, but we can avoid the timer expiring
2988c2ecf20Sopenharmony_ci	 * from causing a system reset by clearing the output enable bit.
2998c2ecf20Sopenharmony_ci	 *
3008c2ecf20Sopenharmony_ci	 * The sch56xx's watchdog will set the watchdog event bit, bit 0
3018c2ecf20Sopenharmony_ci	 * of the second interrupt source register (at base-address + 9),
3028c2ecf20Sopenharmony_ci	 * when the timer expires.
3038c2ecf20Sopenharmony_ci	 *
3048c2ecf20Sopenharmony_ci	 * This will only cause a system reset if the 0-1 flank happens when
3058c2ecf20Sopenharmony_ci	 * output enable is true. Setting output enable after the flank will
3068c2ecf20Sopenharmony_ci	 * not cause a reset, nor will the timer expiring a second time.
3078c2ecf20Sopenharmony_ci	 * This means we must clear the watchdog event bit in case it is set.
3088c2ecf20Sopenharmony_ci	 *
3098c2ecf20Sopenharmony_ci	 * The timer may still be running (after a recent watchdog_stop) and
3108c2ecf20Sopenharmony_ci	 * mere milliseconds away from expiring, so the timer must be reset
3118c2ecf20Sopenharmony_ci	 * first!
3128c2ecf20Sopenharmony_ci	 */
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci	mutex_lock(data->io_lock);
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	/* 1. Reset the watchdog countdown counter */
3178c2ecf20Sopenharmony_ci	ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET,
3188c2ecf20Sopenharmony_ci					data->watchdog_preset);
3198c2ecf20Sopenharmony_ci	if (ret)
3208c2ecf20Sopenharmony_ci		goto leave;
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_ci	/* 2. Enable output */
3238c2ecf20Sopenharmony_ci	val = data->watchdog_output_enable | SCH56XX_WDOG_OUTPUT_ENABLE;
3248c2ecf20Sopenharmony_ci	ret = sch56xx_write_virtual_reg(data->addr,
3258c2ecf20Sopenharmony_ci					SCH56XX_REG_WDOG_OUTPUT_ENABLE, val);
3268c2ecf20Sopenharmony_ci	if (ret)
3278c2ecf20Sopenharmony_ci		goto leave;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	data->watchdog_output_enable = val;
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	/* 3. Clear the watchdog event bit if set */
3328c2ecf20Sopenharmony_ci	val = inb(data->addr + 9);
3338c2ecf20Sopenharmony_ci	if (val & 0x01)
3348c2ecf20Sopenharmony_ci		outb(0x01, data->addr + 9);
3358c2ecf20Sopenharmony_ci
3368c2ecf20Sopenharmony_cileave:
3378c2ecf20Sopenharmony_ci	mutex_unlock(data->io_lock);
3388c2ecf20Sopenharmony_ci	return ret;
3398c2ecf20Sopenharmony_ci}
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_cistatic int watchdog_trigger(struct watchdog_device *wddev)
3428c2ecf20Sopenharmony_ci{
3438c2ecf20Sopenharmony_ci	struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
3448c2ecf20Sopenharmony_ci	int ret;
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_ci	/* Reset the watchdog countdown counter */
3478c2ecf20Sopenharmony_ci	mutex_lock(data->io_lock);
3488c2ecf20Sopenharmony_ci	ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET,
3498c2ecf20Sopenharmony_ci					data->watchdog_preset);
3508c2ecf20Sopenharmony_ci	mutex_unlock(data->io_lock);
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	return ret;
3538c2ecf20Sopenharmony_ci}
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_cistatic int watchdog_stop(struct watchdog_device *wddev)
3568c2ecf20Sopenharmony_ci{
3578c2ecf20Sopenharmony_ci	struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
3588c2ecf20Sopenharmony_ci	int ret = 0;
3598c2ecf20Sopenharmony_ci	u8 val;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	val = data->watchdog_output_enable & ~SCH56XX_WDOG_OUTPUT_ENABLE;
3628c2ecf20Sopenharmony_ci	mutex_lock(data->io_lock);
3638c2ecf20Sopenharmony_ci	ret = sch56xx_write_virtual_reg(data->addr,
3648c2ecf20Sopenharmony_ci					SCH56XX_REG_WDOG_OUTPUT_ENABLE, val);
3658c2ecf20Sopenharmony_ci	mutex_unlock(data->io_lock);
3668c2ecf20Sopenharmony_ci	if (ret)
3678c2ecf20Sopenharmony_ci		return ret;
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci	data->watchdog_output_enable = val;
3708c2ecf20Sopenharmony_ci	return 0;
3718c2ecf20Sopenharmony_ci}
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_cistatic const struct watchdog_ops watchdog_ops = {
3748c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
3758c2ecf20Sopenharmony_ci	.start		= watchdog_start,
3768c2ecf20Sopenharmony_ci	.stop		= watchdog_stop,
3778c2ecf20Sopenharmony_ci	.ping		= watchdog_trigger,
3788c2ecf20Sopenharmony_ci	.set_timeout	= watchdog_set_timeout,
3798c2ecf20Sopenharmony_ci};
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_cistruct sch56xx_watchdog_data *sch56xx_watchdog_register(struct device *parent,
3828c2ecf20Sopenharmony_ci	u16 addr, u32 revision, struct mutex *io_lock, int check_enabled)
3838c2ecf20Sopenharmony_ci{
3848c2ecf20Sopenharmony_ci	struct sch56xx_watchdog_data *data;
3858c2ecf20Sopenharmony_ci	int err, control, output_enable;
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	/* Cache the watchdog registers */
3888c2ecf20Sopenharmony_ci	mutex_lock(io_lock);
3898c2ecf20Sopenharmony_ci	control =
3908c2ecf20Sopenharmony_ci		sch56xx_read_virtual_reg(addr, SCH56XX_REG_WDOG_CONTROL);
3918c2ecf20Sopenharmony_ci	output_enable =
3928c2ecf20Sopenharmony_ci		sch56xx_read_virtual_reg(addr, SCH56XX_REG_WDOG_OUTPUT_ENABLE);
3938c2ecf20Sopenharmony_ci	mutex_unlock(io_lock);
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_ci	if (control < 0)
3968c2ecf20Sopenharmony_ci		return NULL;
3978c2ecf20Sopenharmony_ci	if (output_enable < 0)
3988c2ecf20Sopenharmony_ci		return NULL;
3998c2ecf20Sopenharmony_ci	if (check_enabled && !(output_enable & SCH56XX_WDOG_OUTPUT_ENABLE)) {
4008c2ecf20Sopenharmony_ci		pr_warn("Watchdog not enabled by BIOS, not registering\n");
4018c2ecf20Sopenharmony_ci		return NULL;
4028c2ecf20Sopenharmony_ci	}
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_ci	data = kzalloc(sizeof(struct sch56xx_watchdog_data), GFP_KERNEL);
4058c2ecf20Sopenharmony_ci	if (!data)
4068c2ecf20Sopenharmony_ci		return NULL;
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_ci	data->addr = addr;
4098c2ecf20Sopenharmony_ci	data->io_lock = io_lock;
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_ci	strlcpy(data->wdinfo.identity, "sch56xx watchdog",
4128c2ecf20Sopenharmony_ci		sizeof(data->wdinfo.identity));
4138c2ecf20Sopenharmony_ci	data->wdinfo.firmware_version = revision;
4148c2ecf20Sopenharmony_ci	data->wdinfo.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT;
4158c2ecf20Sopenharmony_ci	if (!nowayout)
4168c2ecf20Sopenharmony_ci		data->wdinfo.options |= WDIOF_MAGICCLOSE;
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_ci	data->wddev.info = &data->wdinfo;
4198c2ecf20Sopenharmony_ci	data->wddev.ops = &watchdog_ops;
4208c2ecf20Sopenharmony_ci	data->wddev.parent = parent;
4218c2ecf20Sopenharmony_ci	data->wddev.timeout = 60;
4228c2ecf20Sopenharmony_ci	data->wddev.min_timeout = 1;
4238c2ecf20Sopenharmony_ci	data->wddev.max_timeout = 255 * 60;
4248c2ecf20Sopenharmony_ci	if (nowayout)
4258c2ecf20Sopenharmony_ci		set_bit(WDOG_NO_WAY_OUT, &data->wddev.status);
4268c2ecf20Sopenharmony_ci	if (output_enable & SCH56XX_WDOG_OUTPUT_ENABLE)
4278c2ecf20Sopenharmony_ci		set_bit(WDOG_HW_RUNNING, &data->wddev.status);
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	/* Since the watchdog uses a downcounter there is no register to read
4308c2ecf20Sopenharmony_ci	   the BIOS set timeout from (if any was set at all) ->
4318c2ecf20Sopenharmony_ci	   Choose a preset which will give us a 1 minute timeout */
4328c2ecf20Sopenharmony_ci	if (control & SCH56XX_WDOG_TIME_BASE_SEC)
4338c2ecf20Sopenharmony_ci		data->watchdog_preset = 60; /* seconds */
4348c2ecf20Sopenharmony_ci	else
4358c2ecf20Sopenharmony_ci		data->watchdog_preset = 1; /* minute */
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ci	data->watchdog_control = control;
4388c2ecf20Sopenharmony_ci	data->watchdog_output_enable = output_enable;
4398c2ecf20Sopenharmony_ci
4408c2ecf20Sopenharmony_ci	watchdog_set_drvdata(&data->wddev, data);
4418c2ecf20Sopenharmony_ci	err = watchdog_register_device(&data->wddev);
4428c2ecf20Sopenharmony_ci	if (err) {
4438c2ecf20Sopenharmony_ci		pr_err("Registering watchdog chardev: %d\n", err);
4448c2ecf20Sopenharmony_ci		kfree(data);
4458c2ecf20Sopenharmony_ci		return NULL;
4468c2ecf20Sopenharmony_ci	}
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci	return data;
4498c2ecf20Sopenharmony_ci}
4508c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sch56xx_watchdog_register);
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_civoid sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data)
4538c2ecf20Sopenharmony_ci{
4548c2ecf20Sopenharmony_ci	watchdog_unregister_device(&data->wddev);
4558c2ecf20Sopenharmony_ci	kfree(data);
4568c2ecf20Sopenharmony_ci}
4578c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sch56xx_watchdog_unregister);
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci/*
4608c2ecf20Sopenharmony_ci * platform dev find, add and remove functions
4618c2ecf20Sopenharmony_ci */
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_cistatic int __init sch56xx_find(int sioaddr, const char **name)
4648c2ecf20Sopenharmony_ci{
4658c2ecf20Sopenharmony_ci	u8 devid;
4668c2ecf20Sopenharmony_ci	unsigned short address;
4678c2ecf20Sopenharmony_ci	int err;
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_ci	err = superio_enter(sioaddr);
4708c2ecf20Sopenharmony_ci	if (err)
4718c2ecf20Sopenharmony_ci		return err;
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci	devid = superio_inb(sioaddr, SIO_REG_DEVID);
4748c2ecf20Sopenharmony_ci	switch (devid) {
4758c2ecf20Sopenharmony_ci	case SIO_SCH5627_ID:
4768c2ecf20Sopenharmony_ci		*name = "sch5627";
4778c2ecf20Sopenharmony_ci		break;
4788c2ecf20Sopenharmony_ci	case SIO_SCH5636_ID:
4798c2ecf20Sopenharmony_ci		*name = "sch5636";
4808c2ecf20Sopenharmony_ci		break;
4818c2ecf20Sopenharmony_ci	default:
4828c2ecf20Sopenharmony_ci		pr_debug("Unsupported device id: 0x%02x\n",
4838c2ecf20Sopenharmony_ci			 (unsigned int)devid);
4848c2ecf20Sopenharmony_ci		err = -ENODEV;
4858c2ecf20Sopenharmony_ci		goto exit;
4868c2ecf20Sopenharmony_ci	}
4878c2ecf20Sopenharmony_ci
4888c2ecf20Sopenharmony_ci	superio_select(sioaddr, SIO_SCH56XX_LD_EM);
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_ci	if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) {
4918c2ecf20Sopenharmony_ci		pr_warn("Device not activated\n");
4928c2ecf20Sopenharmony_ci		err = -ENODEV;
4938c2ecf20Sopenharmony_ci		goto exit;
4948c2ecf20Sopenharmony_ci	}
4958c2ecf20Sopenharmony_ci
4968c2ecf20Sopenharmony_ci	/*
4978c2ecf20Sopenharmony_ci	 * Warning the order of the low / high byte is the other way around
4988c2ecf20Sopenharmony_ci	 * as on most other superio devices!!
4998c2ecf20Sopenharmony_ci	 */
5008c2ecf20Sopenharmony_ci	address = superio_inb(sioaddr, SIO_REG_ADDR) |
5018c2ecf20Sopenharmony_ci		   superio_inb(sioaddr, SIO_REG_ADDR + 1) << 8;
5028c2ecf20Sopenharmony_ci	if (address == 0) {
5038c2ecf20Sopenharmony_ci		pr_warn("Base address not set\n");
5048c2ecf20Sopenharmony_ci		err = -ENODEV;
5058c2ecf20Sopenharmony_ci		goto exit;
5068c2ecf20Sopenharmony_ci	}
5078c2ecf20Sopenharmony_ci	err = address;
5088c2ecf20Sopenharmony_ci
5098c2ecf20Sopenharmony_ciexit:
5108c2ecf20Sopenharmony_ci	superio_exit(sioaddr);
5118c2ecf20Sopenharmony_ci	return err;
5128c2ecf20Sopenharmony_ci}
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_cistatic int __init sch56xx_device_add(int address, const char *name)
5158c2ecf20Sopenharmony_ci{
5168c2ecf20Sopenharmony_ci	struct resource res = {
5178c2ecf20Sopenharmony_ci		.start	= address,
5188c2ecf20Sopenharmony_ci		.end	= address + REGION_LENGTH - 1,
5198c2ecf20Sopenharmony_ci		.flags	= IORESOURCE_IO,
5208c2ecf20Sopenharmony_ci	};
5218c2ecf20Sopenharmony_ci	int err;
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ci	sch56xx_pdev = platform_device_alloc(name, address);
5248c2ecf20Sopenharmony_ci	if (!sch56xx_pdev)
5258c2ecf20Sopenharmony_ci		return -ENOMEM;
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci	res.name = sch56xx_pdev->name;
5288c2ecf20Sopenharmony_ci	err = acpi_check_resource_conflict(&res);
5298c2ecf20Sopenharmony_ci	if (err)
5308c2ecf20Sopenharmony_ci		goto exit_device_put;
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_ci	err = platform_device_add_resources(sch56xx_pdev, &res, 1);
5338c2ecf20Sopenharmony_ci	if (err) {
5348c2ecf20Sopenharmony_ci		pr_err("Device resource addition failed\n");
5358c2ecf20Sopenharmony_ci		goto exit_device_put;
5368c2ecf20Sopenharmony_ci	}
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_ci	err = platform_device_add(sch56xx_pdev);
5398c2ecf20Sopenharmony_ci	if (err) {
5408c2ecf20Sopenharmony_ci		pr_err("Device addition failed\n");
5418c2ecf20Sopenharmony_ci		goto exit_device_put;
5428c2ecf20Sopenharmony_ci	}
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci	return 0;
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ciexit_device_put:
5478c2ecf20Sopenharmony_ci	platform_device_put(sch56xx_pdev);
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	return err;
5508c2ecf20Sopenharmony_ci}
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_cistatic int __init sch56xx_init(void)
5538c2ecf20Sopenharmony_ci{
5548c2ecf20Sopenharmony_ci	int address;
5558c2ecf20Sopenharmony_ci	const char *name = NULL;
5568c2ecf20Sopenharmony_ci
5578c2ecf20Sopenharmony_ci	address = sch56xx_find(0x4e, &name);
5588c2ecf20Sopenharmony_ci	if (address < 0)
5598c2ecf20Sopenharmony_ci		address = sch56xx_find(0x2e, &name);
5608c2ecf20Sopenharmony_ci	if (address < 0)
5618c2ecf20Sopenharmony_ci		return address;
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci	return sch56xx_device_add(address, name);
5648c2ecf20Sopenharmony_ci}
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_cistatic void __exit sch56xx_exit(void)
5678c2ecf20Sopenharmony_ci{
5688c2ecf20Sopenharmony_ci	platform_device_unregister(sch56xx_pdev);
5698c2ecf20Sopenharmony_ci}
5708c2ecf20Sopenharmony_ci
5718c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SMSC SCH56xx Hardware Monitoring Common Code");
5728c2ecf20Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
5738c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
5748c2ecf20Sopenharmony_ci
5758c2ecf20Sopenharmony_cimodule_init(sch56xx_init);
5768c2ecf20Sopenharmony_cimodule_exit(sch56xx_exit);
577