12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 215e28bf1SPriyanka Gupta /* 315e28bf1SPriyanka Gupta * sp5100_tco : TCO timer driver for sp5100 chipsets 415e28bf1SPriyanka Gupta * 515e28bf1SPriyanka Gupta * (c) Copyright 2009 Google Inc., All Rights Reserved. 615e28bf1SPriyanka Gupta * 715e28bf1SPriyanka Gupta * Based on i8xx_tco.c: 815e28bf1SPriyanka Gupta * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights 915e28bf1SPriyanka Gupta * Reserved. 102ab77a34SAlexander A. Klimov * https://www.kernelconcepts.de 1115e28bf1SPriyanka Gupta * 12740fbddfSTakahisa Tanaka * See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide", 13740fbddfSTakahisa Tanaka * AMD Publication 45482 "AMD SB800-Series Southbridges Register 14740fbddfSTakahisa Tanaka * Reference Guide" 15887d2ec5SGuenter Roeck * AMD Publication 48751 "BIOS and Kernel Developer’s Guide (BKDG) 16887d2ec5SGuenter Roeck * for AMD Family 16h Models 00h-0Fh Processors" 17887d2ec5SGuenter Roeck * AMD Publication 51192 "AMD Bolton FCH Register Reference Guide" 18887d2ec5SGuenter Roeck * AMD Publication 52740 "BIOS and Kernel Developer’s Guide (BKDG) 19887d2ec5SGuenter Roeck * for AMD Family 16h Models 30h-3Fh Processors" 20*09da89abSGuenter Roeck * AMD Publication 55570-B1-PUB "Processor Programming Reference (PPR) 21*09da89abSGuenter Roeck * for AMD Family 17h Model 18h, Revision B1 22*09da89abSGuenter Roeck * Processors (PUB) 23*09da89abSGuenter Roeck * AMD Publication 55772-A1-PUB "Processor Programming Reference (PPR) 24*09da89abSGuenter Roeck * for AMD Family 17h Model 20h, Revision A1 25*09da89abSGuenter Roeck * Processors (PUB) 2615e28bf1SPriyanka Gupta */ 2715e28bf1SPriyanka Gupta 2815e28bf1SPriyanka Gupta /* 2915e28bf1SPriyanka Gupta * Includes, defines, variables, module parameters, ... 3015e28bf1SPriyanka Gupta */ 3115e28bf1SPriyanka Gupta 3227c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 3327c766aaSJoe Perches 347cd9d5ffSGuenter Roeck #include <linux/init.h> 357cd9d5ffSGuenter Roeck #include <linux/io.h> 367cd9d5ffSGuenter Roeck #include <linux/ioport.h> 3715e28bf1SPriyanka Gupta #include <linux/module.h> 3815e28bf1SPriyanka Gupta #include <linux/moduleparam.h> 3915e28bf1SPriyanka Gupta #include <linux/pci.h> 4015e28bf1SPriyanka Gupta #include <linux/platform_device.h> 417cd9d5ffSGuenter Roeck #include <linux/types.h> 427cd9d5ffSGuenter Roeck #include <linux/watchdog.h> 4315e28bf1SPriyanka Gupta 4415e28bf1SPriyanka Gupta #include "sp5100_tco.h" 4515e28bf1SPriyanka Gupta 467cd9d5ffSGuenter Roeck #define TCO_DRIVER_NAME "sp5100-tco" 4715e28bf1SPriyanka Gupta 4815e28bf1SPriyanka Gupta /* internal variables */ 497cd9d5ffSGuenter Roeck 50887d2ec5SGuenter Roeck enum tco_reg_layout { 51887d2ec5SGuenter Roeck sp5100, sb800, efch 52887d2ec5SGuenter Roeck }; 53887d2ec5SGuenter Roeck 547cd9d5ffSGuenter Roeck struct sp5100_tco { 557cd9d5ffSGuenter Roeck struct watchdog_device wdd; 567cd9d5ffSGuenter Roeck void __iomem *tcobase; 57887d2ec5SGuenter Roeck enum tco_reg_layout tco_reg_layout; 587cd9d5ffSGuenter Roeck }; 5915e28bf1SPriyanka Gupta 6015e28bf1SPriyanka Gupta /* the watchdog platform device */ 6115e28bf1SPriyanka Gupta static struct platform_device *sp5100_tco_platform_device; 627cd9d5ffSGuenter Roeck /* the associated PCI device */ 637cd9d5ffSGuenter Roeck static struct pci_dev *sp5100_tco_pci; 6415e28bf1SPriyanka Gupta 6515e28bf1SPriyanka Gupta /* module parameters */ 6615e28bf1SPriyanka Gupta 6715e28bf1SPriyanka Gupta #define WATCHDOG_HEARTBEAT 60 /* 60 sec default heartbeat. */ 6815e28bf1SPriyanka Gupta static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ 6915e28bf1SPriyanka Gupta module_param(heartbeat, int, 0); 7015e28bf1SPriyanka Gupta MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default=" 7115e28bf1SPriyanka Gupta __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); 7215e28bf1SPriyanka Gupta 7386a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT; 7486a1e189SWim Van Sebroeck module_param(nowayout, bool, 0); 75740fbddfSTakahisa Tanaka MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started." 7615e28bf1SPriyanka Gupta " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 7715e28bf1SPriyanka Gupta 7815e28bf1SPriyanka Gupta /* 7915e28bf1SPriyanka Gupta * Some TCO specific functions 8015e28bf1SPriyanka Gupta */ 8146856fabSLucas Stach 82887d2ec5SGuenter Roeck static enum tco_reg_layout tco_reg_layout(struct pci_dev *dev) 8346856fabSLucas Stach { 84887d2ec5SGuenter Roeck if (dev->vendor == PCI_VENDOR_ID_ATI && 85887d2ec5SGuenter Roeck dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS && 86887d2ec5SGuenter Roeck dev->revision < 0x40) { 87887d2ec5SGuenter Roeck return sp5100; 88887d2ec5SGuenter Roeck } else if (dev->vendor == PCI_VENDOR_ID_AMD && 89887d2ec5SGuenter Roeck ((dev->device == PCI_DEVICE_ID_AMD_HUDSON2_SMBUS && 90887d2ec5SGuenter Roeck dev->revision >= 0x41) || 91887d2ec5SGuenter Roeck (dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS && 92887d2ec5SGuenter Roeck dev->revision >= 0x49))) { 93887d2ec5SGuenter Roeck return efch; 94887d2ec5SGuenter Roeck } 95887d2ec5SGuenter Roeck return sb800; 9646856fabSLucas Stach } 9746856fabSLucas Stach 987cd9d5ffSGuenter Roeck static int tco_timer_start(struct watchdog_device *wdd) 9915e28bf1SPriyanka Gupta { 1007cd9d5ffSGuenter Roeck struct sp5100_tco *tco = watchdog_get_drvdata(wdd); 10115e28bf1SPriyanka Gupta u32 val; 10215e28bf1SPriyanka Gupta 1037cd9d5ffSGuenter Roeck val = readl(SP5100_WDT_CONTROL(tco->tcobase)); 10415e28bf1SPriyanka Gupta val |= SP5100_WDT_START_STOP_BIT; 1057cd9d5ffSGuenter Roeck writel(val, SP5100_WDT_CONTROL(tco->tcobase)); 1067cd9d5ffSGuenter Roeck 1077cd9d5ffSGuenter Roeck return 0; 10815e28bf1SPriyanka Gupta } 10915e28bf1SPriyanka Gupta 1107cd9d5ffSGuenter Roeck static int tco_timer_stop(struct watchdog_device *wdd) 11115e28bf1SPriyanka Gupta { 1127cd9d5ffSGuenter Roeck struct sp5100_tco *tco = watchdog_get_drvdata(wdd); 11315e28bf1SPriyanka Gupta u32 val; 11415e28bf1SPriyanka Gupta 1157cd9d5ffSGuenter Roeck val = readl(SP5100_WDT_CONTROL(tco->tcobase)); 11615e28bf1SPriyanka Gupta val &= ~SP5100_WDT_START_STOP_BIT; 1177cd9d5ffSGuenter Roeck writel(val, SP5100_WDT_CONTROL(tco->tcobase)); 1187cd9d5ffSGuenter Roeck 1197cd9d5ffSGuenter Roeck return 0; 12015e28bf1SPriyanka Gupta } 12115e28bf1SPriyanka Gupta 1227cd9d5ffSGuenter Roeck static int tco_timer_ping(struct watchdog_device *wdd) 12315e28bf1SPriyanka Gupta { 1247cd9d5ffSGuenter Roeck struct sp5100_tco *tco = watchdog_get_drvdata(wdd); 12515e28bf1SPriyanka Gupta u32 val; 12615e28bf1SPriyanka Gupta 1277cd9d5ffSGuenter Roeck val = readl(SP5100_WDT_CONTROL(tco->tcobase)); 12815e28bf1SPriyanka Gupta val |= SP5100_WDT_TRIGGER_BIT; 1297cd9d5ffSGuenter Roeck writel(val, SP5100_WDT_CONTROL(tco->tcobase)); 1307cd9d5ffSGuenter Roeck 1317cd9d5ffSGuenter Roeck return 0; 13215e28bf1SPriyanka Gupta } 13315e28bf1SPriyanka Gupta 1347cd9d5ffSGuenter Roeck static int tco_timer_set_timeout(struct watchdog_device *wdd, 1357cd9d5ffSGuenter Roeck unsigned int t) 13615e28bf1SPriyanka Gupta { 1377cd9d5ffSGuenter Roeck struct sp5100_tco *tco = watchdog_get_drvdata(wdd); 13815e28bf1SPriyanka Gupta 13915e28bf1SPriyanka Gupta /* Write new heartbeat to watchdog */ 1407cd9d5ffSGuenter Roeck writel(t, SP5100_WDT_COUNT(tco->tcobase)); 14115e28bf1SPriyanka Gupta 1427cd9d5ffSGuenter Roeck wdd->timeout = t; 1437cd9d5ffSGuenter Roeck 14415e28bf1SPriyanka Gupta return 0; 14515e28bf1SPriyanka Gupta } 14615e28bf1SPriyanka Gupta 1472b750cffSGuenter Roeck static u8 sp5100_tco_read_pm_reg8(u8 index) 1482b750cffSGuenter Roeck { 1492b750cffSGuenter Roeck outb(index, SP5100_IO_PM_INDEX_REG); 1502b750cffSGuenter Roeck return inb(SP5100_IO_PM_DATA_REG); 1512b750cffSGuenter Roeck } 1522b750cffSGuenter Roeck 1532b750cffSGuenter Roeck static void sp5100_tco_update_pm_reg8(u8 index, u8 reset, u8 set) 1542b750cffSGuenter Roeck { 1552b750cffSGuenter Roeck u8 val; 1562b750cffSGuenter Roeck 1572b750cffSGuenter Roeck outb(index, SP5100_IO_PM_INDEX_REG); 1582b750cffSGuenter Roeck val = inb(SP5100_IO_PM_DATA_REG); 1592b750cffSGuenter Roeck val &= reset; 1602b750cffSGuenter Roeck val |= set; 1612b750cffSGuenter Roeck outb(val, SP5100_IO_PM_DATA_REG); 1622b750cffSGuenter Roeck } 1632b750cffSGuenter Roeck 164887d2ec5SGuenter Roeck static void tco_timer_enable(struct sp5100_tco *tco) 165740fbddfSTakahisa Tanaka { 166887d2ec5SGuenter Roeck u32 val; 167887d2ec5SGuenter Roeck 168887d2ec5SGuenter Roeck switch (tco->tco_reg_layout) { 169887d2ec5SGuenter Roeck case sb800: 170740fbddfSTakahisa Tanaka /* For SB800 or later */ 171740fbddfSTakahisa Tanaka /* Set the Watchdog timer resolution to 1 sec */ 1722b750cffSGuenter Roeck sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONFIG, 1732b750cffSGuenter Roeck 0xff, SB800_PM_WATCHDOG_SECOND_RES); 174740fbddfSTakahisa Tanaka 175740fbddfSTakahisa Tanaka /* Enable watchdog decode bit and watchdog timer */ 1762b750cffSGuenter Roeck sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONTROL, 1772b750cffSGuenter Roeck ~SB800_PM_WATCHDOG_DISABLE, 1782b750cffSGuenter Roeck SB800_PCI_WATCHDOG_DECODE_EN); 179887d2ec5SGuenter Roeck break; 180887d2ec5SGuenter Roeck case sp5100: 181740fbddfSTakahisa Tanaka /* For SP5100 or SB7x0 */ 182740fbddfSTakahisa Tanaka /* Enable watchdog decode bit */ 183740fbddfSTakahisa Tanaka pci_read_config_dword(sp5100_tco_pci, 184740fbddfSTakahisa Tanaka SP5100_PCI_WATCHDOG_MISC_REG, 185740fbddfSTakahisa Tanaka &val); 186740fbddfSTakahisa Tanaka 187740fbddfSTakahisa Tanaka val |= SP5100_PCI_WATCHDOG_DECODE_EN; 188740fbddfSTakahisa Tanaka 189740fbddfSTakahisa Tanaka pci_write_config_dword(sp5100_tco_pci, 190740fbddfSTakahisa Tanaka SP5100_PCI_WATCHDOG_MISC_REG, 191740fbddfSTakahisa Tanaka val); 192740fbddfSTakahisa Tanaka 193740fbddfSTakahisa Tanaka /* Enable Watchdog timer and set the resolution to 1 sec */ 1942b750cffSGuenter Roeck sp5100_tco_update_pm_reg8(SP5100_PM_WATCHDOG_CONTROL, 1952b750cffSGuenter Roeck ~SP5100_PM_WATCHDOG_DISABLE, 1962b750cffSGuenter Roeck SP5100_PM_WATCHDOG_SECOND_RES); 197887d2ec5SGuenter Roeck break; 198887d2ec5SGuenter Roeck case efch: 199887d2ec5SGuenter Roeck /* Set the Watchdog timer resolution to 1 sec and enable */ 200887d2ec5SGuenter Roeck sp5100_tco_update_pm_reg8(EFCH_PM_DECODEEN3, 201887d2ec5SGuenter Roeck ~EFCH_PM_WATCHDOG_DISABLE, 202887d2ec5SGuenter Roeck EFCH_PM_DECODEEN_SECOND_RES); 203887d2ec5SGuenter Roeck break; 204740fbddfSTakahisa Tanaka } 205740fbddfSTakahisa Tanaka } 206740fbddfSTakahisa Tanaka 2077cd9d5ffSGuenter Roeck static u32 sp5100_tco_read_pm_reg32(u8 index) 2082b750cffSGuenter Roeck { 2092b750cffSGuenter Roeck u32 val = 0; 2102b750cffSGuenter Roeck int i; 2112b750cffSGuenter Roeck 2122b750cffSGuenter Roeck for (i = 3; i >= 0; i--) 2132b750cffSGuenter Roeck val = (val << 8) + sp5100_tco_read_pm_reg8(index + i); 2142b750cffSGuenter Roeck 2152b750cffSGuenter Roeck return val; 2162b750cffSGuenter Roeck } 2172b750cffSGuenter Roeck 2187cd9d5ffSGuenter Roeck static int sp5100_tco_setupdevice(struct device *dev, 2197cd9d5ffSGuenter Roeck struct watchdog_device *wdd) 22015e28bf1SPriyanka Gupta { 2217cd9d5ffSGuenter Roeck struct sp5100_tco *tco = watchdog_get_drvdata(wdd); 2227cd9d5ffSGuenter Roeck const char *dev_name; 223887d2ec5SGuenter Roeck u32 mmio_addr = 0, val; 22423dfe140SGuenter Roeck int ret; 22515e28bf1SPriyanka Gupta 22615e28bf1SPriyanka Gupta /* Request the IO ports used by this driver */ 22716e7730bSGuenter Roeck if (!request_muxed_region(SP5100_IO_PM_INDEX_REG, 228887d2ec5SGuenter Roeck SP5100_PM_IOPORTS_SIZE, "sp5100_tco")) { 229fd8f9093SGuenter Roeck dev_err(dev, "I/O address 0x%04x already in use\n", 2302b750cffSGuenter Roeck SP5100_IO_PM_INDEX_REG); 23123dfe140SGuenter Roeck return -EBUSY; 23215e28bf1SPriyanka Gupta } 23315e28bf1SPriyanka Gupta 234740fbddfSTakahisa Tanaka /* 235887d2ec5SGuenter Roeck * Determine type of southbridge chipset. 236740fbddfSTakahisa Tanaka */ 237887d2ec5SGuenter Roeck switch (tco->tco_reg_layout) { 238887d2ec5SGuenter Roeck case sp5100: 239887d2ec5SGuenter Roeck dev_name = SP5100_DEVNAME; 240887d2ec5SGuenter Roeck mmio_addr = sp5100_tco_read_pm_reg32(SP5100_PM_WATCHDOG_BASE) & 241887d2ec5SGuenter Roeck 0xfffffff8; 242887d2ec5SGuenter Roeck break; 243887d2ec5SGuenter Roeck case sb800: 244887d2ec5SGuenter Roeck dev_name = SB800_DEVNAME; 245887d2ec5SGuenter Roeck mmio_addr = sp5100_tco_read_pm_reg32(SB800_PM_WATCHDOG_BASE) & 246887d2ec5SGuenter Roeck 0xfffffff8; 247887d2ec5SGuenter Roeck break; 248887d2ec5SGuenter Roeck case efch: 249887d2ec5SGuenter Roeck dev_name = SB800_DEVNAME; 250*09da89abSGuenter Roeck /* 251*09da89abSGuenter Roeck * On Family 17h devices, the EFCH_PM_DECODEEN_WDT_TMREN bit of 252*09da89abSGuenter Roeck * EFCH_PM_DECODEEN not only enables the EFCH_PM_WDT_ADDR memory 253*09da89abSGuenter Roeck * region, it also enables the watchdog itself. 254*09da89abSGuenter Roeck */ 255*09da89abSGuenter Roeck if (boot_cpu_data.x86 == 0x17) { 256*09da89abSGuenter Roeck val = sp5100_tco_read_pm_reg8(EFCH_PM_DECODEEN); 257*09da89abSGuenter Roeck if (!(val & EFCH_PM_DECODEEN_WDT_TMREN)) { 258*09da89abSGuenter Roeck sp5100_tco_update_pm_reg8(EFCH_PM_DECODEEN, 0xff, 259*09da89abSGuenter Roeck EFCH_PM_DECODEEN_WDT_TMREN); 260*09da89abSGuenter Roeck } 261*09da89abSGuenter Roeck } 262887d2ec5SGuenter Roeck val = sp5100_tco_read_pm_reg8(EFCH_PM_DECODEEN); 263887d2ec5SGuenter Roeck if (val & EFCH_PM_DECODEEN_WDT_TMREN) 264887d2ec5SGuenter Roeck mmio_addr = EFCH_PM_WDT_ADDR; 265887d2ec5SGuenter Roeck break; 266887d2ec5SGuenter Roeck default: 267887d2ec5SGuenter Roeck return -ENODEV; 268887d2ec5SGuenter Roeck } 269740fbddfSTakahisa Tanaka 270740fbddfSTakahisa Tanaka /* Check MMIO address conflict */ 271887d2ec5SGuenter Roeck if (!mmio_addr || 272887d2ec5SGuenter Roeck !devm_request_mem_region(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE, 273e189410cSGuenter Roeck dev_name)) { 274887d2ec5SGuenter Roeck if (mmio_addr) 275887d2ec5SGuenter Roeck dev_dbg(dev, "MMIO address 0x%08x already in use\n", 276887d2ec5SGuenter Roeck mmio_addr); 277887d2ec5SGuenter Roeck switch (tco->tco_reg_layout) { 278887d2ec5SGuenter Roeck case sp5100: 279740fbddfSTakahisa Tanaka /* 280740fbddfSTakahisa Tanaka * Secondly, Find the watchdog timer MMIO address 281740fbddfSTakahisa Tanaka * from SBResource_MMIO register. 282740fbddfSTakahisa Tanaka */ 283bdecfcdbSHuang Rui /* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */ 284bdecfcdbSHuang Rui pci_read_config_dword(sp5100_tco_pci, 285e189410cSGuenter Roeck SP5100_SB_RESOURCE_MMIO_BASE, 286887d2ec5SGuenter Roeck &mmio_addr); 287887d2ec5SGuenter Roeck if ((mmio_addr & (SB800_ACPI_MMIO_DECODE_EN | 288887d2ec5SGuenter Roeck SB800_ACPI_MMIO_SEL)) != 289740fbddfSTakahisa Tanaka SB800_ACPI_MMIO_DECODE_EN) { 290e189410cSGuenter Roeck ret = -ENODEV; 291e189410cSGuenter Roeck goto unreg_region; 292e189410cSGuenter Roeck } 293887d2ec5SGuenter Roeck mmio_addr &= ~0xFFF; 294887d2ec5SGuenter Roeck mmio_addr += SB800_PM_WDT_MMIO_OFFSET; 295887d2ec5SGuenter Roeck break; 296887d2ec5SGuenter Roeck case sb800: 297887d2ec5SGuenter Roeck /* Read SBResource_MMIO from AcpiMmioEn(PM_Reg: 24h) */ 298887d2ec5SGuenter Roeck mmio_addr = 299887d2ec5SGuenter Roeck sp5100_tco_read_pm_reg32(SB800_PM_ACPI_MMIO_EN); 300887d2ec5SGuenter Roeck if ((mmio_addr & (SB800_ACPI_MMIO_DECODE_EN | 301887d2ec5SGuenter Roeck SB800_ACPI_MMIO_SEL)) != 302887d2ec5SGuenter Roeck SB800_ACPI_MMIO_DECODE_EN) { 303887d2ec5SGuenter Roeck ret = -ENODEV; 304887d2ec5SGuenter Roeck goto unreg_region; 305887d2ec5SGuenter Roeck } 306887d2ec5SGuenter Roeck mmio_addr &= ~0xFFF; 307887d2ec5SGuenter Roeck mmio_addr += SB800_PM_WDT_MMIO_OFFSET; 308887d2ec5SGuenter Roeck break; 309887d2ec5SGuenter Roeck case efch: 310887d2ec5SGuenter Roeck val = sp5100_tco_read_pm_reg8(EFCH_PM_ISACONTROL); 311887d2ec5SGuenter Roeck if (!(val & EFCH_PM_ISACONTROL_MMIOEN)) { 312887d2ec5SGuenter Roeck ret = -ENODEV; 313887d2ec5SGuenter Roeck goto unreg_region; 314887d2ec5SGuenter Roeck } 315887d2ec5SGuenter Roeck mmio_addr = EFCH_PM_ACPI_MMIO_ADDR + 316887d2ec5SGuenter Roeck EFCH_PM_ACPI_MMIO_WDT_OFFSET; 317887d2ec5SGuenter Roeck break; 318887d2ec5SGuenter Roeck } 319887d2ec5SGuenter Roeck dev_dbg(dev, "Got 0x%08x from SBResource_MMIO register\n", 320887d2ec5SGuenter Roeck mmio_addr); 321887d2ec5SGuenter Roeck if (!devm_request_mem_region(dev, mmio_addr, 322887d2ec5SGuenter Roeck SP5100_WDT_MEM_MAP_SIZE, 323740fbddfSTakahisa Tanaka dev_name)) { 324887d2ec5SGuenter Roeck dev_dbg(dev, "MMIO address 0x%08x already in use\n", 325887d2ec5SGuenter Roeck mmio_addr); 326e189410cSGuenter Roeck ret = -EBUSY; 32790d241edSYinghai Lu goto unreg_region; 328e189410cSGuenter Roeck } 329e189410cSGuenter Roeck } 330740fbddfSTakahisa Tanaka 331887d2ec5SGuenter Roeck tco->tcobase = devm_ioremap(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE); 3327cd9d5ffSGuenter Roeck if (!tco->tcobase) { 333fd8f9093SGuenter Roeck dev_err(dev, "failed to get tcobase address\n"); 33423dfe140SGuenter Roeck ret = -ENOMEM; 3357cd9d5ffSGuenter Roeck goto unreg_region; 33615e28bf1SPriyanka Gupta } 33715e28bf1SPriyanka Gupta 338887d2ec5SGuenter Roeck dev_info(dev, "Using 0x%08x for watchdog MMIO address\n", mmio_addr); 33915e28bf1SPriyanka Gupta 340740fbddfSTakahisa Tanaka /* Setup the watchdog timer */ 341887d2ec5SGuenter Roeck tco_timer_enable(tco); 34215e28bf1SPriyanka Gupta 3437cd9d5ffSGuenter Roeck val = readl(SP5100_WDT_CONTROL(tco->tcobase)); 344f7781b06SGuenter Roeck if (val & SP5100_WDT_DISABLED) { 345f7781b06SGuenter Roeck dev_err(dev, "Watchdog hardware is disabled\n"); 346f7781b06SGuenter Roeck ret = -ENODEV; 347f7781b06SGuenter Roeck goto unreg_region; 348f7781b06SGuenter Roeck } 349f7781b06SGuenter Roeck 350740fbddfSTakahisa Tanaka /* 351740fbddfSTakahisa Tanaka * Save WatchDogFired status, because WatchDogFired flag is 352740fbddfSTakahisa Tanaka * cleared here. 353740fbddfSTakahisa Tanaka */ 3547cd9d5ffSGuenter Roeck if (val & SP5100_WDT_FIRED) 3557cd9d5ffSGuenter Roeck wdd->bootstatus = WDIOF_CARDRESET; 356f7781b06SGuenter Roeck /* Set watchdog action to reset the system */ 3575bbecc5dSGuenter Roeck val &= ~SP5100_WDT_ACTION_RESET; 3587cd9d5ffSGuenter Roeck writel(val, SP5100_WDT_CONTROL(tco->tcobase)); 35915e28bf1SPriyanka Gupta 36015e28bf1SPriyanka Gupta /* Set a reasonable heartbeat before we stop the timer */ 3617cd9d5ffSGuenter Roeck tco_timer_set_timeout(wdd, wdd->timeout); 36215e28bf1SPriyanka Gupta 36315e28bf1SPriyanka Gupta /* 36415e28bf1SPriyanka Gupta * Stop the TCO before we change anything so we don't race with 36515e28bf1SPriyanka Gupta * a zeroed timer. 36615e28bf1SPriyanka Gupta */ 3677cd9d5ffSGuenter Roeck tco_timer_stop(wdd); 36815e28bf1SPriyanka Gupta 36916e7730bSGuenter Roeck release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); 370e189410cSGuenter Roeck 37123dfe140SGuenter Roeck return 0; 37215e28bf1SPriyanka Gupta 37315e28bf1SPriyanka Gupta unreg_region: 3742b750cffSGuenter Roeck release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); 37523dfe140SGuenter Roeck return ret; 37615e28bf1SPriyanka Gupta } 37715e28bf1SPriyanka Gupta 3787cd9d5ffSGuenter Roeck static struct watchdog_info sp5100_tco_wdt_info = { 3797cd9d5ffSGuenter Roeck .identity = "SP5100 TCO timer", 3807cd9d5ffSGuenter Roeck .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 3817cd9d5ffSGuenter Roeck }; 3827cd9d5ffSGuenter Roeck 3837cd9d5ffSGuenter Roeck static const struct watchdog_ops sp5100_tco_wdt_ops = { 3847cd9d5ffSGuenter Roeck .owner = THIS_MODULE, 3857cd9d5ffSGuenter Roeck .start = tco_timer_start, 3867cd9d5ffSGuenter Roeck .stop = tco_timer_stop, 3877cd9d5ffSGuenter Roeck .ping = tco_timer_ping, 3887cd9d5ffSGuenter Roeck .set_timeout = tco_timer_set_timeout, 3897cd9d5ffSGuenter Roeck }; 3907cd9d5ffSGuenter Roeck 3915bbecc5dSGuenter Roeck static int sp5100_tco_probe(struct platform_device *pdev) 39215e28bf1SPriyanka Gupta { 393fd8f9093SGuenter Roeck struct device *dev = &pdev->dev; 3947cd9d5ffSGuenter Roeck struct watchdog_device *wdd; 3957cd9d5ffSGuenter Roeck struct sp5100_tco *tco; 39615e28bf1SPriyanka Gupta int ret; 39715e28bf1SPriyanka Gupta 3987cd9d5ffSGuenter Roeck tco = devm_kzalloc(dev, sizeof(*tco), GFP_KERNEL); 3997cd9d5ffSGuenter Roeck if (!tco) 4007cd9d5ffSGuenter Roeck return -ENOMEM; 4017cd9d5ffSGuenter Roeck 402887d2ec5SGuenter Roeck tco->tco_reg_layout = tco_reg_layout(sp5100_tco_pci); 403887d2ec5SGuenter Roeck 4047cd9d5ffSGuenter Roeck wdd = &tco->wdd; 4057cd9d5ffSGuenter Roeck wdd->parent = dev; 4067cd9d5ffSGuenter Roeck wdd->info = &sp5100_tco_wdt_info; 4077cd9d5ffSGuenter Roeck wdd->ops = &sp5100_tco_wdt_ops; 4087cd9d5ffSGuenter Roeck wdd->timeout = WATCHDOG_HEARTBEAT; 4097cd9d5ffSGuenter Roeck wdd->min_timeout = 1; 4107cd9d5ffSGuenter Roeck wdd->max_timeout = 0xffff; 4117cd9d5ffSGuenter Roeck 4122d505e3eSWolfram Sang watchdog_init_timeout(wdd, heartbeat, NULL); 4137cd9d5ffSGuenter Roeck watchdog_set_nowayout(wdd, nowayout); 4147cd9d5ffSGuenter Roeck watchdog_stop_on_reboot(wdd); 4157cd9d5ffSGuenter Roeck watchdog_stop_on_unregister(wdd); 4167cd9d5ffSGuenter Roeck watchdog_set_drvdata(wdd, tco); 4177cd9d5ffSGuenter Roeck 4187cd9d5ffSGuenter Roeck ret = sp5100_tco_setupdevice(dev, wdd); 41923dfe140SGuenter Roeck if (ret) 42023dfe140SGuenter Roeck return ret; 42115e28bf1SPriyanka Gupta 4227cd9d5ffSGuenter Roeck ret = devm_watchdog_register_device(dev, wdd); 423d41e3f4eSWolfram Sang if (ret) 42415e28bf1SPriyanka Gupta return ret; 42515e28bf1SPriyanka Gupta 4267cd9d5ffSGuenter Roeck /* Show module parameters */ 4277cd9d5ffSGuenter Roeck dev_info(dev, "initialized. heartbeat=%d sec (nowayout=%d)\n", 4287cd9d5ffSGuenter Roeck wdd->timeout, nowayout); 42915e28bf1SPriyanka Gupta 43015e28bf1SPriyanka Gupta return 0; 43115e28bf1SPriyanka Gupta } 43215e28bf1SPriyanka Gupta 43315e28bf1SPriyanka Gupta static struct platform_driver sp5100_tco_driver = { 4345bbecc5dSGuenter Roeck .probe = sp5100_tco_probe, 43515e28bf1SPriyanka Gupta .driver = { 4367cd9d5ffSGuenter Roeck .name = TCO_DRIVER_NAME, 43715e28bf1SPriyanka Gupta }, 43815e28bf1SPriyanka Gupta }; 43915e28bf1SPriyanka Gupta 440a3483443SGuenter Roeck /* 441a3483443SGuenter Roeck * Data for PCI driver interface 442a3483443SGuenter Roeck * 443a3483443SGuenter Roeck * This data only exists for exporting the supported 444a3483443SGuenter Roeck * PCI ids via MODULE_DEVICE_TABLE. We do not actually 445a3483443SGuenter Roeck * register a pci_driver, because someone else might 446a3483443SGuenter Roeck * want to register another driver on the same PCI id. 447a3483443SGuenter Roeck */ 448a3483443SGuenter Roeck static const struct pci_device_id sp5100_tco_pci_tbl[] = { 449a3483443SGuenter Roeck { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID, 450a3483443SGuenter Roeck PCI_ANY_ID, }, 451a3483443SGuenter Roeck { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_HUDSON2_SMBUS, PCI_ANY_ID, 452a3483443SGuenter Roeck PCI_ANY_ID, }, 453a3483443SGuenter Roeck { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, PCI_ANY_ID, 454a3483443SGuenter Roeck PCI_ANY_ID, }, 455a3483443SGuenter Roeck { 0, }, /* End of list */ 456a3483443SGuenter Roeck }; 457a3483443SGuenter Roeck MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl); 458a3483443SGuenter Roeck 4595bbecc5dSGuenter Roeck static int __init sp5100_tco_init(void) 46015e28bf1SPriyanka Gupta { 461a3483443SGuenter Roeck struct pci_dev *dev = NULL; 46215e28bf1SPriyanka Gupta int err; 46315e28bf1SPriyanka Gupta 464a3483443SGuenter Roeck /* Match the PCI device */ 465a3483443SGuenter Roeck for_each_pci_dev(dev) { 466a3483443SGuenter Roeck if (pci_match_id(sp5100_tco_pci_tbl, dev) != NULL) { 467a3483443SGuenter Roeck sp5100_tco_pci = dev; 468a3483443SGuenter Roeck break; 469a3483443SGuenter Roeck } 470a3483443SGuenter Roeck } 471a3483443SGuenter Roeck 472a3483443SGuenter Roeck if (!sp5100_tco_pci) 473a3483443SGuenter Roeck return -ENODEV; 474a3483443SGuenter Roeck 4757cd9d5ffSGuenter Roeck pr_info("SP5100/SB800 TCO WatchDog Timer Driver\n"); 47615e28bf1SPriyanka Gupta 47715e28bf1SPriyanka Gupta err = platform_driver_register(&sp5100_tco_driver); 47815e28bf1SPriyanka Gupta if (err) 47915e28bf1SPriyanka Gupta return err; 48015e28bf1SPriyanka Gupta 4817cd9d5ffSGuenter Roeck sp5100_tco_platform_device = 4827cd9d5ffSGuenter Roeck platform_device_register_simple(TCO_DRIVER_NAME, -1, NULL, 0); 48315e28bf1SPriyanka Gupta if (IS_ERR(sp5100_tco_platform_device)) { 48415e28bf1SPriyanka Gupta err = PTR_ERR(sp5100_tco_platform_device); 48515e28bf1SPriyanka Gupta goto unreg_platform_driver; 48615e28bf1SPriyanka Gupta } 48715e28bf1SPriyanka Gupta 48815e28bf1SPriyanka Gupta return 0; 48915e28bf1SPriyanka Gupta 49015e28bf1SPriyanka Gupta unreg_platform_driver: 49115e28bf1SPriyanka Gupta platform_driver_unregister(&sp5100_tco_driver); 49215e28bf1SPriyanka Gupta return err; 49315e28bf1SPriyanka Gupta } 49415e28bf1SPriyanka Gupta 4955bbecc5dSGuenter Roeck static void __exit sp5100_tco_exit(void) 49615e28bf1SPriyanka Gupta { 49715e28bf1SPriyanka Gupta platform_device_unregister(sp5100_tco_platform_device); 49815e28bf1SPriyanka Gupta platform_driver_unregister(&sp5100_tco_driver); 49915e28bf1SPriyanka Gupta } 50015e28bf1SPriyanka Gupta 5015bbecc5dSGuenter Roeck module_init(sp5100_tco_init); 5025bbecc5dSGuenter Roeck module_exit(sp5100_tco_exit); 50315e28bf1SPriyanka Gupta 50415e28bf1SPriyanka Gupta MODULE_AUTHOR("Priyanka Gupta"); 505740fbddfSTakahisa Tanaka MODULE_DESCRIPTION("TCO timer driver for SP5100/SB800 chipset"); 50615e28bf1SPriyanka Gupta MODULE_LICENSE("GPL"); 507