636 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * sp5100_tco : TCO timer driver for sp5100 chipsets4 *5 * (c) Copyright 2009 Google Inc., All Rights Reserved.6 *7 * Based on i8xx_tco.c:8 * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights9 * Reserved.10 * https://www.kernelconcepts.de11 *12 * See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide",13 * AMD Publication 44413 "AMD SP5100 Register Reference Guide"14 * AMD Publication 45482 "AMD SB800-Series Southbridges Register15 * Reference Guide"16 * AMD Publication 48751 "BIOS and Kernel Developer’s Guide (BKDG)17 * for AMD Family 16h Models 00h-0Fh Processors"18 * AMD Publication 51192 "AMD Bolton FCH Register Reference Guide"19 * AMD Publication 52740 "BIOS and Kernel Developer’s Guide (BKDG)20 * for AMD Family 16h Models 30h-3Fh Processors"21 * AMD Publication 55570-B1-PUB "Processor Programming Reference (PPR)22 * for AMD Family 17h Model 18h, Revision B123 * Processors (PUB)24 * AMD Publication 55772-A1-PUB "Processor Programming Reference (PPR)25 * for AMD Family 17h Model 20h, Revision A126 * Processors (PUB)27 */28 29/*30 * Includes, defines, variables, module parameters, ...31 */32 33#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt34 35#include <linux/init.h>36#include <linux/io.h>37#include <linux/ioport.h>38#include <linux/module.h>39#include <linux/moduleparam.h>40#include <linux/pci.h>41#include <linux/platform_device.h>42#include <linux/types.h>43#include <linux/watchdog.h>44 45#include "sp5100_tco.h"46 47#define TCO_DRIVER_NAME "sp5100-tco"48 49/* internal variables */50 51enum tco_reg_layout {52 sp5100, sb800, efch, efch_mmio53};54 55struct sp5100_tco {56 struct watchdog_device wdd;57 void __iomem *tcobase;58 enum tco_reg_layout tco_reg_layout;59};60 61/* the watchdog platform device */62static struct platform_device *sp5100_tco_platform_device;63/* the associated PCI device */64static struct pci_dev *sp5100_tco_pci;65 66/* module parameters */67 68#define WATCHDOG_ACTION 069static bool action = WATCHDOG_ACTION;70module_param(action, bool, 0);71MODULE_PARM_DESC(action, "Action taken when watchdog expires, 0 to reset, 1 to poweroff (default="72 __MODULE_STRING(WATCHDOG_ACTION) ")");73 74#define WATCHDOG_HEARTBEAT 60 /* 60 sec default heartbeat. */75static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */76module_param(heartbeat, int, 0);77MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="78 __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");79 80static bool nowayout = WATCHDOG_NOWAYOUT;81module_param(nowayout, bool, 0);82MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started."83 " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");84 85/*86 * Some TCO specific functions87 */88 89static enum tco_reg_layout tco_reg_layout(struct pci_dev *dev)90{91 if (dev->vendor == PCI_VENDOR_ID_ATI &&92 dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS &&93 dev->revision < 0x40) {94 return sp5100;95 } else if (dev->vendor == PCI_VENDOR_ID_AMD &&96 sp5100_tco_pci->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS &&97 sp5100_tco_pci->revision >= AMD_ZEN_SMBUS_PCI_REV) {98 return efch_mmio;99 } else if ((dev->vendor == PCI_VENDOR_ID_AMD || dev->vendor == PCI_VENDOR_ID_HYGON) &&100 ((dev->device == PCI_DEVICE_ID_AMD_HUDSON2_SMBUS &&101 dev->revision >= 0x41) ||102 (dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS &&103 dev->revision >= 0x49))) {104 return efch;105 }106 return sb800;107}108 109static int tco_timer_start(struct watchdog_device *wdd)110{111 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);112 u32 val;113 114 val = readl(SP5100_WDT_CONTROL(tco->tcobase));115 val |= SP5100_WDT_START_STOP_BIT;116 writel(val, SP5100_WDT_CONTROL(tco->tcobase));117 118 /* This must be a distinct write. */119 val |= SP5100_WDT_TRIGGER_BIT;120 writel(val, SP5100_WDT_CONTROL(tco->tcobase));121 122 return 0;123}124 125static int tco_timer_stop(struct watchdog_device *wdd)126{127 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);128 u32 val;129 130 val = readl(SP5100_WDT_CONTROL(tco->tcobase));131 val &= ~SP5100_WDT_START_STOP_BIT;132 writel(val, SP5100_WDT_CONTROL(tco->tcobase));133 134 return 0;135}136 137static int tco_timer_ping(struct watchdog_device *wdd)138{139 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);140 u32 val;141 142 val = readl(SP5100_WDT_CONTROL(tco->tcobase));143 val |= SP5100_WDT_TRIGGER_BIT;144 writel(val, SP5100_WDT_CONTROL(tco->tcobase));145 146 return 0;147}148 149static int tco_timer_set_timeout(struct watchdog_device *wdd,150 unsigned int t)151{152 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);153 154 /* Write new heartbeat to watchdog */155 writel(t, SP5100_WDT_COUNT(tco->tcobase));156 157 wdd->timeout = t;158 159 return 0;160}161 162static unsigned int tco_timer_get_timeleft(struct watchdog_device *wdd)163{164 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);165 166 return readl(SP5100_WDT_COUNT(tco->tcobase));167}168 169static u8 sp5100_tco_read_pm_reg8(u8 index)170{171 outb(index, SP5100_IO_PM_INDEX_REG);172 return inb(SP5100_IO_PM_DATA_REG);173}174 175static void sp5100_tco_update_pm_reg8(u8 index, u8 reset, u8 set)176{177 u8 val;178 179 outb(index, SP5100_IO_PM_INDEX_REG);180 val = inb(SP5100_IO_PM_DATA_REG);181 val &= reset;182 val |= set;183 outb(val, SP5100_IO_PM_DATA_REG);184}185 186static void tco_timer_enable(struct sp5100_tco *tco)187{188 u32 val;189 190 switch (tco->tco_reg_layout) {191 case sb800:192 /* For SB800 or later */193 /* Set the Watchdog timer resolution to 1 sec */194 sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONFIG,195 0xff, SB800_PM_WATCHDOG_SECOND_RES);196 197 /* Enable watchdog decode bit and watchdog timer */198 sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONTROL,199 ~SB800_PM_WATCHDOG_DISABLE,200 SB800_PCI_WATCHDOG_DECODE_EN);201 break;202 case sp5100:203 /* For SP5100 or SB7x0 */204 /* Enable watchdog decode bit */205 pci_read_config_dword(sp5100_tco_pci,206 SP5100_PCI_WATCHDOG_MISC_REG,207 &val);208 209 val |= SP5100_PCI_WATCHDOG_DECODE_EN;210 211 pci_write_config_dword(sp5100_tco_pci,212 SP5100_PCI_WATCHDOG_MISC_REG,213 val);214 215 /* Enable Watchdog timer and set the resolution to 1 sec */216 sp5100_tco_update_pm_reg8(SP5100_PM_WATCHDOG_CONTROL,217 ~SP5100_PM_WATCHDOG_DISABLE,218 SP5100_PM_WATCHDOG_SECOND_RES);219 break;220 case efch:221 /* Set the Watchdog timer resolution to 1 sec and enable */222 sp5100_tco_update_pm_reg8(EFCH_PM_DECODEEN3,223 ~EFCH_PM_WATCHDOG_DISABLE,224 EFCH_PM_DECODEEN_SECOND_RES);225 break;226 default:227 break;228 }229}230 231static u32 sp5100_tco_read_pm_reg32(u8 index)232{233 u32 val = 0;234 int i;235 236 for (i = 3; i >= 0; i--)237 val = (val << 8) + sp5100_tco_read_pm_reg8(index + i);238 239 return val;240}241 242static u32 sp5100_tco_request_region(struct device *dev,243 u32 mmio_addr,244 const char *dev_name)245{246 if (!devm_request_mem_region(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE,247 dev_name)) {248 dev_dbg(dev, "MMIO address 0x%08x already in use\n", mmio_addr);249 return 0;250 }251 252 return mmio_addr;253}254 255static u32 sp5100_tco_prepare_base(struct sp5100_tco *tco,256 u32 mmio_addr,257 u32 alt_mmio_addr,258 const char *dev_name)259{260 struct device *dev = tco->wdd.parent;261 262 dev_dbg(dev, "Got 0x%08x from SBResource_MMIO register\n", mmio_addr);263 264 if (!mmio_addr && !alt_mmio_addr)265 return -ENODEV;266 267 /* Check for MMIO address and alternate MMIO address conflicts */268 if (mmio_addr)269 mmio_addr = sp5100_tco_request_region(dev, mmio_addr, dev_name);270 271 if (!mmio_addr && alt_mmio_addr)272 mmio_addr = sp5100_tco_request_region(dev, alt_mmio_addr, dev_name);273 274 if (!mmio_addr) {275 dev_err(dev, "Failed to reserve MMIO or alternate MMIO region\n");276 return -EBUSY;277 }278 279 tco->tcobase = devm_ioremap(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE);280 if (!tco->tcobase) {281 dev_err(dev, "MMIO address 0x%08x failed mapping\n", mmio_addr);282 devm_release_mem_region(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE);283 return -ENOMEM;284 }285 286 dev_info(dev, "Using 0x%08x for watchdog MMIO address\n", mmio_addr);287 288 return 0;289}290 291static int sp5100_tco_timer_init(struct sp5100_tco *tco)292{293 struct watchdog_device *wdd = &tco->wdd;294 struct device *dev = wdd->parent;295 u32 val;296 297 val = readl(SP5100_WDT_CONTROL(tco->tcobase));298 if (val & SP5100_WDT_DISABLED) {299 dev_err(dev, "Watchdog hardware is disabled\n");300 return -ENODEV;301 }302 303 /*304 * Save WatchDogFired status, because WatchDogFired flag is305 * cleared here.306 */307 if (val & SP5100_WDT_FIRED)308 wdd->bootstatus = WDIOF_CARDRESET;309 310 /* Set watchdog action */311 if (action)312 val |= SP5100_WDT_ACTION_RESET;313 else314 val &= ~SP5100_WDT_ACTION_RESET;315 writel(val, SP5100_WDT_CONTROL(tco->tcobase));316 317 /* Set a reasonable heartbeat before we stop the timer */318 tco_timer_set_timeout(wdd, wdd->timeout);319 320 /*321 * Stop the TCO before we change anything so we don't race with322 * a zeroed timer.323 */324 tco_timer_stop(wdd);325 326 return 0;327}328 329static u8 efch_read_pm_reg8(void __iomem *addr, u8 index)330{331 return readb(addr + index);332}333 334static void efch_update_pm_reg8(void __iomem *addr, u8 index, u8 reset, u8 set)335{336 u8 val;337 338 val = readb(addr + index);339 val &= reset;340 val |= set;341 writeb(val, addr + index);342}343 344static void tco_timer_enable_mmio(void __iomem *addr)345{346 efch_update_pm_reg8(addr, EFCH_PM_DECODEEN3,347 ~EFCH_PM_WATCHDOG_DISABLE,348 EFCH_PM_DECODEEN_SECOND_RES);349}350 351static int sp5100_tco_setupdevice_mmio(struct device *dev,352 struct watchdog_device *wdd)353{354 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);355 const char *dev_name = SB800_DEVNAME;356 u32 mmio_addr = 0, alt_mmio_addr = 0;357 struct resource *res;358 void __iomem *addr;359 int ret;360 u32 val;361 362 res = request_mem_region_muxed(EFCH_PM_ACPI_MMIO_PM_ADDR,363 EFCH_PM_ACPI_MMIO_PM_SIZE,364 "sp5100_tco");365 366 if (!res) {367 dev_err(dev,368 "Memory region 0x%08x already in use\n",369 EFCH_PM_ACPI_MMIO_PM_ADDR);370 return -EBUSY;371 }372 373 addr = ioremap(EFCH_PM_ACPI_MMIO_PM_ADDR, EFCH_PM_ACPI_MMIO_PM_SIZE);374 if (!addr) {375 dev_err(dev, "Address mapping failed\n");376 ret = -ENOMEM;377 goto out;378 }379 380 /*381 * EFCH_PM_DECODEEN_WDT_TMREN is dual purpose. This bitfield382 * enables sp5100_tco register MMIO space decoding. The bitfield383 * also starts the timer operation. Enable if not already enabled.384 */385 val = efch_read_pm_reg8(addr, EFCH_PM_DECODEEN);386 if (!(val & EFCH_PM_DECODEEN_WDT_TMREN)) {387 efch_update_pm_reg8(addr, EFCH_PM_DECODEEN, 0xff,388 EFCH_PM_DECODEEN_WDT_TMREN);389 }390 391 /* Error if the timer could not be enabled */392 val = efch_read_pm_reg8(addr, EFCH_PM_DECODEEN);393 if (!(val & EFCH_PM_DECODEEN_WDT_TMREN)) {394 dev_err(dev, "Failed to enable the timer\n");395 ret = -EFAULT;396 goto out;397 }398 399 mmio_addr = EFCH_PM_WDT_ADDR;400 401 /* Determine alternate MMIO base address */402 val = efch_read_pm_reg8(addr, EFCH_PM_ISACONTROL);403 if (val & EFCH_PM_ISACONTROL_MMIOEN)404 alt_mmio_addr = EFCH_PM_ACPI_MMIO_ADDR +405 EFCH_PM_ACPI_MMIO_WDT_OFFSET;406 407 ret = sp5100_tco_prepare_base(tco, mmio_addr, alt_mmio_addr, dev_name);408 if (!ret) {409 tco_timer_enable_mmio(addr);410 ret = sp5100_tco_timer_init(tco);411 }412 413out:414 if (addr)415 iounmap(addr);416 417 release_resource(res);418 kfree(res);419 420 return ret;421}422 423static int sp5100_tco_setupdevice(struct device *dev,424 struct watchdog_device *wdd)425{426 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);427 const char *dev_name;428 u32 mmio_addr = 0, val;429 u32 alt_mmio_addr = 0;430 int ret;431 432 if (tco->tco_reg_layout == efch_mmio)433 return sp5100_tco_setupdevice_mmio(dev, wdd);434 435 /* Request the IO ports used by this driver */436 if (!request_muxed_region(SP5100_IO_PM_INDEX_REG,437 SP5100_PM_IOPORTS_SIZE, "sp5100_tco")) {438 dev_err(dev, "I/O address 0x%04x already in use\n",439 SP5100_IO_PM_INDEX_REG);440 return -EBUSY;441 }442 443 /*444 * Determine type of southbridge chipset.445 */446 switch (tco->tco_reg_layout) {447 case sp5100:448 dev_name = SP5100_DEVNAME;449 mmio_addr = sp5100_tco_read_pm_reg32(SP5100_PM_WATCHDOG_BASE) &450 0xfffffff8;451 452 /*453 * Secondly, find the watchdog timer MMIO address454 * from SBResource_MMIO register.455 */456 457 /* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */458 pci_read_config_dword(sp5100_tco_pci,459 SP5100_SB_RESOURCE_MMIO_BASE,460 &val);461 462 /* Verify MMIO is enabled and using bar0 */463 if ((val & SB800_ACPI_MMIO_MASK) == SB800_ACPI_MMIO_DECODE_EN)464 alt_mmio_addr = (val & ~0xfff) + SB800_PM_WDT_MMIO_OFFSET;465 break;466 case sb800:467 dev_name = SB800_DEVNAME;468 mmio_addr = sp5100_tco_read_pm_reg32(SB800_PM_WATCHDOG_BASE) &469 0xfffffff8;470 471 /* Read SBResource_MMIO from AcpiMmioEn(PM_Reg: 24h) */472 val = sp5100_tco_read_pm_reg32(SB800_PM_ACPI_MMIO_EN);473 474 /* Verify MMIO is enabled and using bar0 */475 if ((val & SB800_ACPI_MMIO_MASK) == SB800_ACPI_MMIO_DECODE_EN)476 alt_mmio_addr = (val & ~0xfff) + SB800_PM_WDT_MMIO_OFFSET;477 break;478 case efch:479 dev_name = SB800_DEVNAME;480 val = sp5100_tco_read_pm_reg8(EFCH_PM_DECODEEN);481 if (val & EFCH_PM_DECODEEN_WDT_TMREN)482 mmio_addr = EFCH_PM_WDT_ADDR;483 484 val = sp5100_tco_read_pm_reg8(EFCH_PM_ISACONTROL);485 if (val & EFCH_PM_ISACONTROL_MMIOEN)486 alt_mmio_addr = EFCH_PM_ACPI_MMIO_ADDR +487 EFCH_PM_ACPI_MMIO_WDT_OFFSET;488 break;489 default:490 return -ENODEV;491 }492 493 ret = sp5100_tco_prepare_base(tco, mmio_addr, alt_mmio_addr, dev_name);494 if (!ret) {495 /* Setup the watchdog timer */496 tco_timer_enable(tco);497 ret = sp5100_tco_timer_init(tco);498 }499 500 release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);501 return ret;502}503 504static struct watchdog_info sp5100_tco_wdt_info = {505 .identity = "SP5100 TCO timer",506 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,507};508 509static const struct watchdog_ops sp5100_tco_wdt_ops = {510 .owner = THIS_MODULE,511 .start = tco_timer_start,512 .stop = tco_timer_stop,513 .ping = tco_timer_ping,514 .set_timeout = tco_timer_set_timeout,515 .get_timeleft = tco_timer_get_timeleft,516};517 518static int sp5100_tco_probe(struct platform_device *pdev)519{520 struct device *dev = &pdev->dev;521 struct watchdog_device *wdd;522 struct sp5100_tco *tco;523 int ret;524 525 tco = devm_kzalloc(dev, sizeof(*tco), GFP_KERNEL);526 if (!tco)527 return -ENOMEM;528 529 tco->tco_reg_layout = tco_reg_layout(sp5100_tco_pci);530 531 wdd = &tco->wdd;532 wdd->parent = dev;533 wdd->info = &sp5100_tco_wdt_info;534 wdd->ops = &sp5100_tco_wdt_ops;535 wdd->timeout = WATCHDOG_HEARTBEAT;536 wdd->min_timeout = 1;537 wdd->max_timeout = 0xffff;538 539 watchdog_init_timeout(wdd, heartbeat, NULL);540 watchdog_set_nowayout(wdd, nowayout);541 watchdog_stop_on_reboot(wdd);542 watchdog_stop_on_unregister(wdd);543 watchdog_set_drvdata(wdd, tco);544 545 ret = sp5100_tco_setupdevice(dev, wdd);546 if (ret)547 return ret;548 549 ret = devm_watchdog_register_device(dev, wdd);550 if (ret)551 return ret;552 553 /* Show module parameters */554 dev_info(dev, "initialized. heartbeat=%d sec (nowayout=%d)\n",555 wdd->timeout, nowayout);556 557 return 0;558}559 560static struct platform_driver sp5100_tco_driver = {561 .probe = sp5100_tco_probe,562 .driver = {563 .name = TCO_DRIVER_NAME,564 },565};566 567/*568 * Data for PCI driver interface569 *570 * This data only exists for exporting the supported571 * PCI ids via MODULE_DEVICE_TABLE. We do not actually572 * register a pci_driver, because someone else might573 * want to register another driver on the same PCI id.574 */575static const struct pci_device_id sp5100_tco_pci_tbl[] = {576 { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID,577 PCI_ANY_ID, },578 { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_HUDSON2_SMBUS, PCI_ANY_ID,579 PCI_ANY_ID, },580 { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, PCI_ANY_ID,581 PCI_ANY_ID, },582 { PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, PCI_ANY_ID,583 PCI_ANY_ID, },584 { 0, }, /* End of list */585};586MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl);587 588static int __init sp5100_tco_init(void)589{590 struct pci_dev *dev = NULL;591 int err;592 593 /* Match the PCI device */594 for_each_pci_dev(dev) {595 if (pci_match_id(sp5100_tco_pci_tbl, dev) != NULL) {596 sp5100_tco_pci = dev;597 break;598 }599 }600 601 if (!sp5100_tco_pci)602 return -ENODEV;603 604 pr_info("SP5100/SB800 TCO WatchDog Timer Driver\n");605 606 err = platform_driver_register(&sp5100_tco_driver);607 if (err)608 return err;609 610 sp5100_tco_platform_device =611 platform_device_register_simple(TCO_DRIVER_NAME, -1, NULL, 0);612 if (IS_ERR(sp5100_tco_platform_device)) {613 err = PTR_ERR(sp5100_tco_platform_device);614 goto unreg_platform_driver;615 }616 617 return 0;618 619unreg_platform_driver:620 platform_driver_unregister(&sp5100_tco_driver);621 return err;622}623 624static void __exit sp5100_tco_exit(void)625{626 platform_device_unregister(sp5100_tco_platform_device);627 platform_driver_unregister(&sp5100_tco_driver);628}629 630module_init(sp5100_tco_init);631module_exit(sp5100_tco_exit);632 633MODULE_AUTHOR("Priyanka Gupta");634MODULE_DESCRIPTION("TCO timer driver for SP5100/SB800 chipset");635MODULE_LICENSE("GPL");636