FILE
loongson1_wdt.c
File preview with syntax highlighting.
linux-6.19.14/drivers/watchdog/loongson1_wdt.c
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (c) 2016 Yang Ling <gnaygnil@gmail.com> 4 * Copyright (C) 2025 Binbin Zhou <zhoubinbin@loongson.cn> 5 */ 6 7#include <linux/clk.h> 8#include <linux/io.h> 9#include <linux/module.h> 10#include <linux/of.h> 11#include <linux/platform_device.h> 12#include <linux/watchdog.h> 13 14/* Loongson Watchdog Register Definitions */ 15#define WDT_EN 0x0 16 17#define DEFAULT_HEARTBEAT 30 18 19static bool nowayout = WATCHDOG_NOWAYOUT; 20module_param(nowayout, bool, 0); 21MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 22 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 23 24static unsigned int heartbeat; 25module_param(heartbeat, uint, 0); 26MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default=" 27 __MODULE_STRING(DEFAULT_HEARTBEAT) ")"); 28 29struct ls1x_wdt_pdata { 30 u32 timer_offset; 31 u32 set_offset; 32 u32 wdt_en_bit; 33}; 34 35static const struct ls1x_wdt_pdata ls1b_wdt_pdata = { 36 .timer_offset = 0x4, 37 .set_offset = 0x8, 38 .wdt_en_bit = BIT(0), 39}; 40 41static const struct ls1x_wdt_pdata ls2k0300_wdt_pdata = { 42 .timer_offset = 0x8, 43 .set_offset = 0x4, 44 .wdt_en_bit = BIT(1), 45}; 46 47struct ls1x_wdt_drvdata { 48 void __iomem *base; 49 struct clk *clk; 50 unsigned long clk_rate; 51 struct watchdog_device wdt; 52 const struct ls1x_wdt_pdata *pdata; 53}; 54 55static int ls1x_wdt_ping(struct watchdog_device *wdt_dev) 56{ 57 struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 58 59 writel(0x1, drvdata->base + drvdata->pdata->set_offset); 60 61 return 0; 62} 63 64static int ls1x_wdt_set_timeout(struct watchdog_device *wdt_dev, 65 unsigned int timeout) 66{ 67 struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 68 unsigned int max_hw_heartbeat = wdt_dev->max_hw_heartbeat_ms / 1000; 69 unsigned int counts; 70 71 wdt_dev->timeout = timeout; 72 73 counts = drvdata->clk_rate * min(timeout, max_hw_heartbeat); 74 writel(counts, drvdata->base + drvdata->pdata->timer_offset); 75 76 return 0; 77} 78 79static int ls1x_wdt_start(struct watchdog_device *wdt_dev) 80{ 81 struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 82 83 writel(drvdata->pdata->wdt_en_bit, drvdata->base + WDT_EN); 84 85 return 0; 86} 87 88static int ls1x_wdt_stop(struct watchdog_device *wdt_dev) 89{ 90 struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 91 u32 val = readl(drvdata->base + WDT_EN); 92 93 val &= ~(drvdata->pdata->wdt_en_bit); 94 writel(val, drvdata->base + WDT_EN); 95 96 return 0; 97} 98 99static int ls1x_wdt_restart(struct watchdog_device *wdt_dev, 100 unsigned long action, void *data) 101{ 102 struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 103 104 writel(drvdata->pdata->wdt_en_bit, drvdata->base + WDT_EN); 105 writel(0x1, drvdata->base + drvdata->pdata->timer_offset); 106 writel(0x1, drvdata->base + drvdata->pdata->set_offset); 107 108 return 0; 109} 110 111static const struct watchdog_info ls1x_wdt_info = { 112 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 113 .identity = "Loongson1 Watchdog", 114}; 115 116static const struct watchdog_ops ls1x_wdt_ops = { 117 .owner = THIS_MODULE, 118 .start = ls1x_wdt_start, 119 .stop = ls1x_wdt_stop, 120 .ping = ls1x_wdt_ping, 121 .set_timeout = ls1x_wdt_set_timeout, 122 .restart = ls1x_wdt_restart, 123}; 124 125static int ls1x_wdt_probe(struct platform_device *pdev) 126{ 127 struct device *dev = &pdev->dev; 128 struct ls1x_wdt_drvdata *drvdata; 129 struct watchdog_device *ls1x_wdt; 130 unsigned long clk_rate; 131 132 drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); 133 if (!drvdata) 134 return -ENOMEM; 135 platform_set_drvdata(pdev, drvdata); 136 137 drvdata->pdata = of_device_get_match_data(dev); 138 139 drvdata->base = devm_platform_ioremap_resource(pdev, 0); 140 if (IS_ERR(drvdata->base)) 141 return PTR_ERR(drvdata->base); 142 143 drvdata->clk = devm_clk_get_enabled(dev, NULL); 144 if (IS_ERR(drvdata->clk)) 145 return PTR_ERR(drvdata->clk); 146 147 clk_rate = clk_get_rate(drvdata->clk); 148 if (!clk_rate) 149 return -EINVAL; 150 drvdata->clk_rate = clk_rate; 151 152 ls1x_wdt = &drvdata->wdt; 153 ls1x_wdt->info = &ls1x_wdt_info; 154 ls1x_wdt->ops = &ls1x_wdt_ops; 155 ls1x_wdt->timeout = DEFAULT_HEARTBEAT; 156 ls1x_wdt->min_timeout = 1; 157 ls1x_wdt->max_hw_heartbeat_ms = U32_MAX / clk_rate * 1000; 158 ls1x_wdt->parent = dev; 159 160 watchdog_init_timeout(ls1x_wdt, heartbeat, dev); 161 watchdog_set_nowayout(ls1x_wdt, nowayout); 162 watchdog_set_drvdata(ls1x_wdt, drvdata); 163 164 return devm_watchdog_register_device(dev, &drvdata->wdt); 165} 166 167static int ls1x_wdt_resume(struct device *dev) 168{ 169 struct ls1x_wdt_drvdata *data = dev_get_drvdata(dev); 170 171 if (watchdog_active(&data->wdt)) 172 ls1x_wdt_start(&data->wdt); 173 174 return 0; 175} 176 177static int ls1x_wdt_suspend(struct device *dev) 178{ 179 struct ls1x_wdt_drvdata *data = dev_get_drvdata(dev); 180 181 if (watchdog_active(&data->wdt)) 182 ls1x_wdt_stop(&data->wdt); 183 184 return 0; 185} 186 187static DEFINE_SIMPLE_DEV_PM_OPS(ls1x_wdt_pm_ops, ls1x_wdt_suspend, ls1x_wdt_resume); 188 189static const struct of_device_id ls1x_wdt_dt_ids[] = { 190 { .compatible = "loongson,ls1b-wdt", .data = &ls1b_wdt_pdata }, 191 { .compatible = "loongson,ls1c-wdt", .data = &ls1b_wdt_pdata }, 192 { .compatible = "loongson,ls2k0300-wdt", .data = &ls2k0300_wdt_pdata }, 193 { /* sentinel */ } 194}; 195MODULE_DEVICE_TABLE(of, ls1x_wdt_dt_ids); 196 197static struct platform_driver ls1x_wdt_driver = { 198 .probe = ls1x_wdt_probe, 199 .driver = { 200 .name = "ls1x-wdt", 201 .of_match_table = ls1x_wdt_dt_ids, 202 .pm = pm_ptr(&ls1x_wdt_pm_ops), 203 }, 204}; 205 206module_platform_driver(ls1x_wdt_driver); 207 208MODULE_AUTHOR("Yang Ling <gnaygnil@gmail.com>"); 209MODULE_AUTHOR("Binbin Zhou <zhoubinbin@loongson.cn>"); 210MODULE_DESCRIPTION("Loongson Watchdog Driver"); 211MODULE_LICENSE("GPL");
~/
Read-only file preview.