aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/watchdog/indydog.c
blob: 1cc5609666d10fe45f513c24393232f8180a3ec9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/*
 *	IndyDog	0.3	A Hardware Watchdog Device for SGI IP22
 *
 *	(c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>,
 *						All Rights Reserved.
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
 *	2 of the License, or (at your option) any later version.
 *
 *	based on softdog.c by Alan Cox <alan@lxorguk.ukuu.org.uk>
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <asm/sgi/mc.h>

#define PFX "indydog: "
static unsigned long indydog_alive;
static spinlock_t indydog_lock;

#define WATCHDOG_TIMEOUT 30		/* 30 sec default timeout */

static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout,
		"Watchdog cannot be stopped once started (default="
				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

static void indydog_start(void)
{
	u32 mc_ctrl0;

	spin_lock(&indydog_lock);
	mc_ctrl0 = sgimc->cpuctrl0;
	mc_ctrl0 = sgimc->cpuctrl0 | SGIMC_CCTRL0_WDOG;
	sgimc->cpuctrl0 = mc_ctrl0;
	spin_unlock(&indydog_lock);
}

static void indydog_stop(void)
{
	u32 mc_ctrl0;

	spin_lock(&indydog_lock);

	mc_ctrl0 = sgimc->cpuctrl0;
	mc_ctrl0 &= ~SGIMC_CCTRL0_WDOG;
	sgimc->cpuctrl0 = mc_ctrl0;
	spin_unlock(&indydog_lock);

	printk(KERN_INFO PFX "Stopped watchdog timer.\n");
}

static void indydog_ping(void)
{
	sgimc->watchdogt = 0;
}

/*
 *	Allow only one person to hold it open
 */
static int indydog_open(struct inode *inode, struct file *file)
{
	if (test_and_set_bit(0, &indydog_alive))
		return -EBUSY;

	if (nowayout)
		__module_get(THIS_MODULE);

	/* Activate timer */
	indydog_start();
	indydog_ping();

	printk(KERN_INFO "Started watchdog timer.\n");

	return nonseekable_open(inode, file);
}

static int indydog_release(struct inode *inode, struct file *file)
{
	/* Shut off the timer.
	 * Lock it in if it's a module and we defined ...NOWAYOUT */
	if (!nowayout)
		indydog_stop();		/* Turn the WDT off */
	clear_bit(0, &indydog_alive);
	return 0;
}

static ssize_t indydog_write(struct file *file, const char *data,
						size_t len, loff_t *ppos)
{
	/* Refresh the timer. */
	if (len)
		indydog_ping();
	return len;
}

static long indydog_ioctl(struct file *file, unsigned int cmd,
							unsigned long arg)
{
	int options, retval = -EINVAL;
	static const struct watchdog_info ident = {
		.options		= WDIOF_KEEPALIVEPING,
		.firmware_version	= 0,
		.identity		= "Hardware Watchdog for SGI IP22",
	};

	switch (cmd) {
	case WDIOC_GETSUPPORT:
		if (copy_to_user((struct watchdog_info *)arg,
				 &ident, sizeof(ident)))
			return -EFAULT;
		return 0;
	case WDIOC_GETSTATUS:
	case WDIOC_GETBOOTSTATUS:
		return put_user(0, (int *)arg);
	case WDIOC_SETOPTIONS:
	{
		if (get_user(options, (int *)arg))
			return -EFAULT;
		if (options & WDIOS_DISABLECARD) {
			indydog_stop();
			retval = 0;
		}
		if (options & WDIOS_ENABLECARD) {
			indydog_start();
			retval = 0;
		}
		return retval;
	}
	case WDIOC_KEEPALIVE:
		indydog_ping();
		return 0;
	case WDIOC_GETTIMEOUT:
		return put_user(WATCHDOG_TIMEOUT, (int *)arg);
	default:
		return -ENOTTY;
	}
}

static int indydog_notify_sys(struct notifier_block *this,
					unsigned long code, void *unused)
{
	if (code == SYS_DOWN || code == SYS_HALT)
		indydog_stop();		/* Turn the WDT off */

	return NOTIFY_DONE;
}

static const struct file_operations indydog_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.write		= indydog_write,
	.unlocked_ioctl	= indydog_ioctl,
	.open		= indydog_open,
	.release	= indydog_release,
};

static struct miscdevice indydog_miscdev = {
	.minor		= WATCHDOG_MINOR,
	.name		= "watchdog",
	.fops		= &indydog_fops,
};

static struct notifier_block indydog_notifier = {
	.notifier_call = indydog_notify_sys,
};

static char banner[] __initdata =
	KERN_INFO PFX "Hardware Watchdog Timer for SGI IP22: 0.3\n";

static int __init watchdog_init(void)
{
	int ret;

	spin_lock_init(&indydog_lock);

	ret = register_reboot_notifier(&indydog_notifier);
	if (ret) {
		printk(KERN_ERR PFX
			"cannot register reboot notifier (err=%d)\n", ret);
		return ret;
	}

	ret = misc_register(&indydog_miscdev);
	if (ret) {
		printk(KERN_ERR PFX
			"cannot register miscdev on minor=%d (err=%d)\n",
							WATCHDOG_MINOR, ret);
		unregister_reboot_notifier(&indydog_notifier);
		return ret;
	}

	printk(banner);

	return 0;
}

static void __exit watchdog_exit(void)
{
	misc_deregister(&indydog_miscdev);
	unregister_reboot_notifier(&indydog_notifier);
}

module_init(watchdog_init);
module_exit(watchdog_exit);

MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>");
MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
pan>clk_lock); BUG_ON(arizona->clk32k_ref <= 0); arizona->clk32k_ref--; if (arizona->clk32k_ref == 0) { regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1, ARIZONA_CLK_32K_ENA, 0); switch (arizona->pdata.clk32k_src) { case ARIZONA_32KZ_MCLK1: pm_runtime_put_sync(arizona->dev); break; } } mutex_unlock(&arizona->clk_lock); return ret; } EXPORT_SYMBOL_GPL(arizona_clk32k_disable); static irqreturn_t arizona_clkgen_err(int irq, void *data) { struct arizona *arizona = data; dev_err(arizona->dev, "CLKGEN error\n"); return IRQ_HANDLED; } static irqreturn_t arizona_underclocked(int irq, void *data) { struct arizona *arizona = data; unsigned int val; int ret; ret = regmap_read(arizona->regmap, ARIZONA_INTERRUPT_RAW_STATUS_8, &val); if (ret != 0) { dev_err(arizona->dev, "Failed to read underclock status: %d\n", ret); return IRQ_NONE; } if (val & ARIZONA_AIF3_UNDERCLOCKED_STS) dev_err(arizona->dev, "AIF3 underclocked\n"); if (val & ARIZONA_AIF2_UNDERCLOCKED_STS) dev_err(arizona->dev, "AIF2 underclocked\n"); if (val & ARIZONA_AIF1_UNDERCLOCKED_STS) dev_err(arizona->dev, "AIF1 underclocked\n"); if (val & ARIZONA_ISRC2_UNDERCLOCKED_STS) dev_err(arizona->dev, "ISRC2 underclocked\n"); if (val & ARIZONA_ISRC1_UNDERCLOCKED_STS) dev_err(arizona->dev, "ISRC1 underclocked\n"); if (val & ARIZONA_FX_UNDERCLOCKED_STS) dev_err(arizona->dev, "FX underclocked\n"); if (val & ARIZONA_ASRC_UNDERCLOCKED_STS) dev_err(arizona->dev, "ASRC underclocked\n"); if (val & ARIZONA_DAC_UNDERCLOCKED_STS) dev_err(arizona->dev, "DAC underclocked\n"); if (val & ARIZONA_ADC_UNDERCLOCKED_STS) dev_err(arizona->dev, "ADC underclocked\n"); if (val & ARIZONA_MIXER_UNDERCLOCKED_STS) dev_err(arizona->dev, "Mixer dropped sample\n"); return IRQ_HANDLED; } static irqreturn_t arizona_overclocked(int irq, void *data) { struct arizona *arizona = data; unsigned int val[2]; int ret; ret = regmap_bulk_read(arizona->regmap, ARIZONA_INTERRUPT_RAW_STATUS_6, &val[0], 2); if (ret != 0) { dev_err(arizona->dev, "Failed to read overclock status: %d\n", ret); return IRQ_NONE; } if (val[0] & ARIZONA_PWM_OVERCLOCKED_STS) dev_err(arizona->dev, "PWM overclocked\n"); if (val[0] & ARIZONA_FX_CORE_OVERCLOCKED_STS) dev_err(arizona->dev, "FX core overclocked\n"); if (val[0] & ARIZONA_DAC_SYS_OVERCLOCKED_STS) dev_err(arizona->dev, "DAC SYS overclocked\n"); if (val[0] & ARIZONA_DAC_WARP_OVERCLOCKED_STS) dev_err(arizona->dev, "DAC WARP overclocked\n"); if (val[0] & ARIZONA_ADC_OVERCLOCKED_STS) dev_err(arizona->dev, "ADC overclocked\n"); if (val[0] & ARIZONA_MIXER_OVERCLOCKED_STS) dev_err(arizona->dev, "Mixer overclocked\n"); if (val[0] & ARIZONA_AIF3_SYNC_OVERCLOCKED_STS) dev_err(arizona->dev, "AIF3 overclocked\n"); if (val[0] & ARIZONA_AIF2_SYNC_OVERCLOCKED_STS) dev_err(arizona->dev, "AIF2 overclocked\n"); if (val[0] & ARIZONA_AIF1_SYNC_OVERCLOCKED_STS) dev_err(arizona->dev, "AIF1 overclocked\n"); if (val[0] & ARIZONA_PAD_CTRL_OVERCLOCKED_STS) dev_err(arizona->dev, "Pad control overclocked\n"); if (val[1] & ARIZONA_SLIMBUS_SUBSYS_OVERCLOCKED_STS) dev_err(arizona->dev, "Slimbus subsystem overclocked\n"); if (val[1] & ARIZONA_SLIMBUS_ASYNC_OVERCLOCKED_STS) dev_err(arizona->dev, "Slimbus async overclocked\n"); if (val[1] & ARIZONA_SLIMBUS_SYNC_OVERCLOCKED_STS) dev_err(arizona->dev, "Slimbus sync overclocked\n"); if (val[1] & ARIZONA_ASRC_ASYNC_SYS_OVERCLOCKED_STS) dev_err(arizona->dev, "ASRC async system overclocked\n"); if (val[1] & ARIZONA_ASRC_ASYNC_WARP_OVERCLOCKED_STS) dev_err(arizona->dev, "ASRC async WARP overclocked\n"); if (val[1] & ARIZONA_ASRC_SYNC_SYS_OVERCLOCKED_STS) dev_err(arizona->dev, "ASRC sync system overclocked\n"); if (val[1] & ARIZONA_ASRC_SYNC_WARP_OVERCLOCKED_STS) dev_err(arizona->dev, "ASRC sync WARP overclocked\n"); if (val[1] & ARIZONA_ADSP2_1_OVERCLOCKED_STS) dev_err(arizona->dev, "DSP1 overclocked\n"); if (val[1] & ARIZONA_ISRC2_OVERCLOCKED_STS) dev_err(arizona->dev, "ISRC2 overclocked\n"); if (val[1] & ARIZONA_ISRC1_OVERCLOCKED_STS) dev_err(arizona->dev, "ISRC1 overclocked\n"); return IRQ_HANDLED; } static int arizona_poll_reg(struct arizona *arizona, int timeout, unsigned int reg, unsigned int mask, unsigned int target) { unsigned int val = 0; int ret, i; for (i = 0; i < timeout; i++) { ret = regmap_read(arizona->regmap, reg, &val); if (ret != 0) { dev_err(arizona->dev, "Failed to read reg %u: %d\n", reg, ret); continue; } if ((val & mask) == target) return 0; msleep(1); } dev_err(arizona->dev, "Polling reg %u timed out: %x\n", reg, val); return -ETIMEDOUT; } static int arizona_wait_for_boot(struct arizona *arizona) { int ret; /* * We can't use an interrupt as we need to runtime resume to do so, * we won't race with the interrupt handler as it'll be blocked on * runtime resume. */ ret = arizona_poll_reg(arizona, 5, ARIZONA_INTERRUPT_RAW_STATUS_5, ARIZONA_BOOT_DONE_STS, ARIZONA_BOOT_DONE_STS); if (!ret) regmap_write(arizona->regmap, ARIZONA_INTERRUPT_STATUS_5, ARIZONA_BOOT_DONE_STS); pm_runtime_mark_last_busy(arizona->dev); return ret; } static int arizona_apply_hardware_patch(struct arizona* arizona) { unsigned int fll, sysclk; int ret, err; regcache_cache_bypass(arizona->regmap, true); /* Cache existing FLL and SYSCLK settings */ ret = regmap_read(arizona->regmap, ARIZONA_FLL1_CONTROL_1, &fll); if (ret != 0) { dev_err(arizona->dev, "Failed to cache FLL settings: %d\n", ret); return ret; } ret = regmap_read(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, &sysclk); if (ret != 0) { dev_err(arizona->dev, "Failed to cache SYSCLK settings: %d\n", ret); return ret; } /* Start up SYSCLK using the FLL in free running mode */ ret = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, ARIZONA_FLL1_ENA | ARIZONA_FLL1_FREERUN); if (ret != 0) { dev_err(arizona->dev, "Failed to start FLL in freerunning mode: %d\n", ret); return ret; } ret = arizona_poll_reg(arizona, 25, ARIZONA_INTERRUPT_RAW_STATUS_5, ARIZONA_FLL1_CLOCK_OK_STS, ARIZONA_FLL1_CLOCK_OK_STS); if (ret != 0) { ret = -ETIMEDOUT; goto err_fll; } ret = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, 0x0144); if (ret != 0) { dev_err(arizona->dev, "Failed to start SYSCLK: %d\n", ret); goto err_fll; } /* Start the write sequencer and wait for it to finish */ ret = regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_0, ARIZONA_WSEQ_ENA | ARIZONA_WSEQ_START | 160); if (ret != 0) { dev_err(arizona->dev, "Failed to start write sequencer: %d\n", ret); goto err_sysclk; } ret = arizona_poll_reg(arizona, 5, ARIZONA_WRITE_SEQUENCER_CTRL_1, ARIZONA_WSEQ_BUSY, 0); if (ret != 0) { regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_0, ARIZONA_WSEQ_ABORT); ret = -ETIMEDOUT; } err_sysclk: err = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, sysclk); if (err != 0) { dev_err(arizona->dev, "Failed to re-apply old SYSCLK settings: %d\n", err); } err_fll: err = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, fll); if (err != 0) { dev_err(arizona->dev, "Failed to re-apply old FLL settings: %d\n", err); } regcache_cache_bypass(arizona->regmap, false); if (ret != 0) return ret; else return err; } #ifdef CONFIG_PM_RUNTIME static int arizona_runtime_resume(struct device *dev) { struct arizona *arizona = dev_get_drvdata(dev); int ret; dev_dbg(arizona->dev, "Leaving AoD mode\n"); ret = regulator_enable(arizona->dcvdd); if (ret != 0) { dev_err(arizona->dev, "Failed to enable DCVDD: %d\n", ret); return ret; } regcache_cache_only(arizona->regmap, false); switch (arizona->type) { case WM5102: ret = wm5102_patch(arizona); if (ret != 0) { dev_err(arizona->dev, "Failed to apply patch: %d\n", ret); goto err; } ret = arizona_apply_hardware_patch(arizona); if (ret != 0) { dev_err(arizona->dev, "Failed to apply hardware patch: %d\n", ret); goto err; } break; default: ret = arizona_wait_for_boot(arizona); if (ret != 0) { goto err; } break; } ret = regcache_sync(arizona->regmap); if (ret != 0) { dev_err(arizona->dev, "Failed to restore register cache\n"); goto err; } return 0; err: regcache_cache_only(arizona->regmap, true); regulator_disable(arizona->dcvdd); return ret; } static int arizona_runtime_suspend(struct device *dev) { struct arizona *arizona = dev_get_drvdata(dev); dev_dbg(arizona->dev, "Entering AoD mode\n"); regulator_disable(arizona->dcvdd); regcache_cache_only(arizona->regmap, true); regcache_mark_dirty(arizona->regmap); return 0; } #endif #ifdef CONFIG_PM_SLEEP static int arizona_resume_noirq(struct device *dev) { struct arizona *arizona = dev_get_drvdata(dev); dev_dbg(arizona->dev, "Early resume, disabling IRQ\n"); disable_irq(arizona->irq); return 0; } static int arizona_resume(struct device *dev) { struct arizona *arizona = dev_get_drvdata(dev); dev_dbg(arizona->dev, "Late resume, reenabling IRQ\n"); enable_irq(arizona->irq); return 0; } #endif const struct dev_pm_ops arizona_pm_ops = { SET_RUNTIME_PM_OPS(arizona_runtime_suspend, arizona_runtime_resume, NULL) SET_SYSTEM_SLEEP_PM_OPS(NULL, arizona_resume) #ifdef CONFIG_PM_SLEEP .resume_noirq = arizona_resume_noirq, #endif }; EXPORT_SYMBOL_GPL(arizona_pm_ops); static struct mfd_cell early_devs[] = { { .name = "arizona-ldo1" }, }; static struct mfd_cell wm5102_devs[] = { { .name = "arizona-micsupp" }, { .name = "arizona-extcon" }, { .name = "arizona-gpio" }, { .name = "arizona-haptics" }, { .name = "arizona-pwm" }, { .name = "wm5102-codec" }, }; static struct mfd_cell wm5110_devs[] = { { .name = "arizona-micsupp" }, { .name = "arizona-extcon" }, { .name = "arizona-gpio" }, { .name = "arizona-haptics" }, { .name = "arizona-pwm" }, { .name = "wm5110-codec" }, }; int arizona_dev_init(struct arizona *arizona) { struct device *dev = arizona->dev; const char *type_name; unsigned int reg, val; int (*apply_patch)(struct arizona *) = NULL; int ret, i; dev_set_drvdata(arizona->dev, arizona); mutex_init(&arizona->clk_lock); if (dev_get_platdata(arizona->dev)) memcpy(&arizona->pdata, dev_get_platdata(arizona->dev), sizeof(arizona->pdata)); regcache_cache_only(arizona->regmap, true); switch (arizona->type) { case WM5102: case WM5110: for (i = 0; i < ARRAY_SIZE(wm5102_core_supplies); i++) arizona->core_supplies[i].supply = wm5102_core_supplies[i]; arizona->num_core_supplies = ARRAY_SIZE(wm5102_core_supplies); break; default: dev_err(arizona->dev, "Unknown device type %d\n", arizona->type); return -EINVAL; } ret = mfd_add_devices(arizona->dev, -1, early_devs, ARRAY_SIZE(early_devs), NULL, 0, NULL); if (ret != 0) { dev_err(dev, "Failed to add early children: %d\n", ret); return ret; } ret = devm_regulator_bulk_get(dev, arizona->num_core_supplies, arizona->core_supplies); if (ret != 0) { dev_err(dev, "Failed to request core supplies: %d\n", ret); goto err_early; } arizona->dcvdd = devm_regulator_get(arizona->dev, "DCVDD"); if (IS_ERR(arizona->dcvdd)) { ret = PTR_ERR(arizona->dcvdd); dev_err(dev, "Failed to request DCVDD: %d\n", ret); goto err_early; } if (arizona->pdata.reset) { /* Start out with /RESET low to put the chip into reset */ ret = gpio_request_one(arizona->pdata.reset, GPIOF_DIR_OUT | GPIOF_INIT_LOW, "arizona /RESET"); if (ret != 0) { dev_err(dev, "Failed to request /RESET: %d\n", ret); goto err_early; } } ret = regulator_bulk_enable(arizona->num_core_supplies, arizona->core_supplies); if (ret != 0) { dev_err(dev, "Failed to enable core supplies: %d\n", ret); goto err_early; } ret = regulator_enable(arizona->dcvdd); if (ret != 0) { dev_err(dev, "Failed to enable DCVDD: %d\n", ret); goto err_enable; } if (arizona->pdata.reset) { gpio_set_value_cansleep(arizona->pdata.reset, 1); msleep(1); } regcache_cache_only(arizona->regmap, false); ret = regmap_read(arizona->regmap, ARIZONA_SOFTWARE_RESET, &reg); if (ret != 0) { dev_err(dev, "Failed to read ID register: %d\n", ret); goto err_reset; } ret = regmap_read(arizona->regmap, ARIZONA_DEVICE_REVISION, &arizona->rev); if (ret != 0) { dev_err(dev, "Failed to read revision register: %d\n", ret); goto err_reset; } arizona->rev &= ARIZONA_DEVICE_REVISION_MASK; switch (reg) { #ifdef CONFIG_MFD_WM5102 case 0x5102: type_name = "WM5102"; if (arizona->type != WM5102) { dev_err(arizona->dev, "WM5102 registered as %d\n", arizona->type); arizona->type = WM5102; } apply_patch = wm5102_patch; arizona->rev &= 0x7; break; #endif #ifdef CONFIG_MFD_WM5110 case 0x5110: type_name = "WM5110"; if (arizona->type != WM5110) { dev_err(arizona->dev, "WM5110 registered as %d\n", arizona->type); arizona->type = WM5110; } apply_patch = wm5110_patch; break; #endif default: dev_err(arizona->dev, "Unknown device ID %x\n", reg); goto err_reset; } dev_info(dev, "%s revision %c\n", type_name, arizona->rev + 'A'); /* If we have a /RESET GPIO we'll already be reset */