1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Watchdog driver for Alphascale ASM9260.
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>
6*4882a593Smuzhiyun */
7*4882a593Smuzhiyun
8*4882a593Smuzhiyun #include <linux/bitops.h>
9*4882a593Smuzhiyun #include <linux/clk.h>
10*4882a593Smuzhiyun #include <linux/delay.h>
11*4882a593Smuzhiyun #include <linux/interrupt.h>
12*4882a593Smuzhiyun #include <linux/io.h>
13*4882a593Smuzhiyun #include <linux/module.h>
14*4882a593Smuzhiyun #include <linux/of.h>
15*4882a593Smuzhiyun #include <linux/platform_device.h>
16*4882a593Smuzhiyun #include <linux/reset.h>
17*4882a593Smuzhiyun #include <linux/watchdog.h>
18*4882a593Smuzhiyun
19*4882a593Smuzhiyun #define CLOCK_FREQ 1000000
20*4882a593Smuzhiyun
21*4882a593Smuzhiyun /* Watchdog Mode register */
22*4882a593Smuzhiyun #define HW_WDMOD 0x00
23*4882a593Smuzhiyun /* Wake interrupt. Set by HW, can't be cleared. */
24*4882a593Smuzhiyun #define BM_MOD_WDINT BIT(3)
25*4882a593Smuzhiyun /* This bit set if timeout reached. Cleared by SW. */
26*4882a593Smuzhiyun #define BM_MOD_WDTOF BIT(2)
27*4882a593Smuzhiyun /* HW Reset on timeout */
28*4882a593Smuzhiyun #define BM_MOD_WDRESET BIT(1)
29*4882a593Smuzhiyun /* WD enable */
30*4882a593Smuzhiyun #define BM_MOD_WDEN BIT(0)
31*4882a593Smuzhiyun
32*4882a593Smuzhiyun /*
33*4882a593Smuzhiyun * Watchdog Timer Constant register
34*4882a593Smuzhiyun * Minimal value is 0xff, the meaning of this value
35*4882a593Smuzhiyun * depends on used clock: T = WDCLK * (0xff + 1) * 4
36*4882a593Smuzhiyun */
37*4882a593Smuzhiyun #define HW_WDTC 0x04
38*4882a593Smuzhiyun #define BM_WDTC_MAX(freq) (0x7fffffff / (freq))
39*4882a593Smuzhiyun
40*4882a593Smuzhiyun /* Watchdog Feed register */
41*4882a593Smuzhiyun #define HW_WDFEED 0x08
42*4882a593Smuzhiyun
43*4882a593Smuzhiyun /* Watchdog Timer Value register */
44*4882a593Smuzhiyun #define HW_WDTV 0x0c
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun #define ASM9260_WDT_DEFAULT_TIMEOUT 30
47*4882a593Smuzhiyun
48*4882a593Smuzhiyun enum asm9260_wdt_mode {
49*4882a593Smuzhiyun HW_RESET,
50*4882a593Smuzhiyun SW_RESET,
51*4882a593Smuzhiyun DEBUG,
52*4882a593Smuzhiyun };
53*4882a593Smuzhiyun
54*4882a593Smuzhiyun struct asm9260_wdt_priv {
55*4882a593Smuzhiyun struct device *dev;
56*4882a593Smuzhiyun struct watchdog_device wdd;
57*4882a593Smuzhiyun struct clk *clk;
58*4882a593Smuzhiyun struct clk *clk_ahb;
59*4882a593Smuzhiyun struct reset_control *rst;
60*4882a593Smuzhiyun
61*4882a593Smuzhiyun void __iomem *iobase;
62*4882a593Smuzhiyun int irq;
63*4882a593Smuzhiyun unsigned long wdt_freq;
64*4882a593Smuzhiyun enum asm9260_wdt_mode mode;
65*4882a593Smuzhiyun };
66*4882a593Smuzhiyun
asm9260_wdt_feed(struct watchdog_device * wdd)67*4882a593Smuzhiyun static int asm9260_wdt_feed(struct watchdog_device *wdd)
68*4882a593Smuzhiyun {
69*4882a593Smuzhiyun struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun iowrite32(0xaa, priv->iobase + HW_WDFEED);
72*4882a593Smuzhiyun iowrite32(0x55, priv->iobase + HW_WDFEED);
73*4882a593Smuzhiyun
74*4882a593Smuzhiyun return 0;
75*4882a593Smuzhiyun }
76*4882a593Smuzhiyun
asm9260_wdt_gettimeleft(struct watchdog_device * wdd)77*4882a593Smuzhiyun static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)
78*4882a593Smuzhiyun {
79*4882a593Smuzhiyun struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
80*4882a593Smuzhiyun u32 counter;
81*4882a593Smuzhiyun
82*4882a593Smuzhiyun counter = ioread32(priv->iobase + HW_WDTV);
83*4882a593Smuzhiyun
84*4882a593Smuzhiyun return counter / priv->wdt_freq;
85*4882a593Smuzhiyun }
86*4882a593Smuzhiyun
asm9260_wdt_updatetimeout(struct watchdog_device * wdd)87*4882a593Smuzhiyun static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)
88*4882a593Smuzhiyun {
89*4882a593Smuzhiyun struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
90*4882a593Smuzhiyun u32 counter;
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun counter = wdd->timeout * priv->wdt_freq;
93*4882a593Smuzhiyun
94*4882a593Smuzhiyun iowrite32(counter, priv->iobase + HW_WDTC);
95*4882a593Smuzhiyun
96*4882a593Smuzhiyun return 0;
97*4882a593Smuzhiyun }
98*4882a593Smuzhiyun
asm9260_wdt_enable(struct watchdog_device * wdd)99*4882a593Smuzhiyun static int asm9260_wdt_enable(struct watchdog_device *wdd)
100*4882a593Smuzhiyun {
101*4882a593Smuzhiyun struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
102*4882a593Smuzhiyun u32 mode = 0;
103*4882a593Smuzhiyun
104*4882a593Smuzhiyun if (priv->mode == HW_RESET)
105*4882a593Smuzhiyun mode = BM_MOD_WDRESET;
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);
108*4882a593Smuzhiyun
109*4882a593Smuzhiyun asm9260_wdt_updatetimeout(wdd);
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun asm9260_wdt_feed(wdd);
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun return 0;
114*4882a593Smuzhiyun }
115*4882a593Smuzhiyun
asm9260_wdt_disable(struct watchdog_device * wdd)116*4882a593Smuzhiyun static int asm9260_wdt_disable(struct watchdog_device *wdd)
117*4882a593Smuzhiyun {
118*4882a593Smuzhiyun struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun /* The only way to disable WD is to reset it. */
121*4882a593Smuzhiyun reset_control_assert(priv->rst);
122*4882a593Smuzhiyun reset_control_deassert(priv->rst);
123*4882a593Smuzhiyun
124*4882a593Smuzhiyun return 0;
125*4882a593Smuzhiyun }
126*4882a593Smuzhiyun
asm9260_wdt_settimeout(struct watchdog_device * wdd,unsigned int to)127*4882a593Smuzhiyun static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
128*4882a593Smuzhiyun {
129*4882a593Smuzhiyun wdd->timeout = to;
130*4882a593Smuzhiyun asm9260_wdt_updatetimeout(wdd);
131*4882a593Smuzhiyun
132*4882a593Smuzhiyun return 0;
133*4882a593Smuzhiyun }
134*4882a593Smuzhiyun
asm9260_wdt_sys_reset(struct asm9260_wdt_priv * priv)135*4882a593Smuzhiyun static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)
136*4882a593Smuzhiyun {
137*4882a593Smuzhiyun /* init WD if it was not started */
138*4882a593Smuzhiyun
139*4882a593Smuzhiyun iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);
140*4882a593Smuzhiyun
141*4882a593Smuzhiyun iowrite32(0xff, priv->iobase + HW_WDTC);
142*4882a593Smuzhiyun /* first pass correct sequence */
143*4882a593Smuzhiyun asm9260_wdt_feed(&priv->wdd);
144*4882a593Smuzhiyun /*
145*4882a593Smuzhiyun * Then write wrong pattern to the feed to trigger reset
146*4882a593Smuzhiyun * ASAP.
147*4882a593Smuzhiyun */
148*4882a593Smuzhiyun iowrite32(0xff, priv->iobase + HW_WDFEED);
149*4882a593Smuzhiyun
150*4882a593Smuzhiyun mdelay(1000);
151*4882a593Smuzhiyun }
152*4882a593Smuzhiyun
asm9260_wdt_irq(int irq,void * devid)153*4882a593Smuzhiyun static irqreturn_t asm9260_wdt_irq(int irq, void *devid)
154*4882a593Smuzhiyun {
155*4882a593Smuzhiyun struct asm9260_wdt_priv *priv = devid;
156*4882a593Smuzhiyun u32 stat;
157*4882a593Smuzhiyun
158*4882a593Smuzhiyun stat = ioread32(priv->iobase + HW_WDMOD);
159*4882a593Smuzhiyun if (!(stat & BM_MOD_WDINT))
160*4882a593Smuzhiyun return IRQ_NONE;
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun if (priv->mode == DEBUG) {
163*4882a593Smuzhiyun dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");
164*4882a593Smuzhiyun } else {
165*4882a593Smuzhiyun dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");
166*4882a593Smuzhiyun asm9260_wdt_sys_reset(priv);
167*4882a593Smuzhiyun }
168*4882a593Smuzhiyun
169*4882a593Smuzhiyun return IRQ_HANDLED;
170*4882a593Smuzhiyun }
171*4882a593Smuzhiyun
asm9260_restart(struct watchdog_device * wdd,unsigned long action,void * data)172*4882a593Smuzhiyun static int asm9260_restart(struct watchdog_device *wdd, unsigned long action,
173*4882a593Smuzhiyun void *data)
174*4882a593Smuzhiyun {
175*4882a593Smuzhiyun struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
176*4882a593Smuzhiyun
177*4882a593Smuzhiyun asm9260_wdt_sys_reset(priv);
178*4882a593Smuzhiyun
179*4882a593Smuzhiyun return 0;
180*4882a593Smuzhiyun }
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun static const struct watchdog_info asm9260_wdt_ident = {
183*4882a593Smuzhiyun .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
184*4882a593Smuzhiyun | WDIOF_MAGICCLOSE,
185*4882a593Smuzhiyun .identity = "Alphascale asm9260 Watchdog",
186*4882a593Smuzhiyun };
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun static const struct watchdog_ops asm9260_wdt_ops = {
189*4882a593Smuzhiyun .owner = THIS_MODULE,
190*4882a593Smuzhiyun .start = asm9260_wdt_enable,
191*4882a593Smuzhiyun .stop = asm9260_wdt_disable,
192*4882a593Smuzhiyun .get_timeleft = asm9260_wdt_gettimeleft,
193*4882a593Smuzhiyun .ping = asm9260_wdt_feed,
194*4882a593Smuzhiyun .set_timeout = asm9260_wdt_settimeout,
195*4882a593Smuzhiyun .restart = asm9260_restart,
196*4882a593Smuzhiyun };
197*4882a593Smuzhiyun
asm9260_clk_disable_unprepare(void * data)198*4882a593Smuzhiyun static void asm9260_clk_disable_unprepare(void *data)
199*4882a593Smuzhiyun {
200*4882a593Smuzhiyun clk_disable_unprepare(data);
201*4882a593Smuzhiyun }
202*4882a593Smuzhiyun
asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv * priv)203*4882a593Smuzhiyun static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)
204*4882a593Smuzhiyun {
205*4882a593Smuzhiyun int err;
206*4882a593Smuzhiyun unsigned long clk;
207*4882a593Smuzhiyun
208*4882a593Smuzhiyun priv->clk = devm_clk_get(priv->dev, "mod");
209*4882a593Smuzhiyun if (IS_ERR(priv->clk)) {
210*4882a593Smuzhiyun dev_err(priv->dev, "Failed to get \"mod\" clk\n");
211*4882a593Smuzhiyun return PTR_ERR(priv->clk);
212*4882a593Smuzhiyun }
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun /* configure AHB clock */
215*4882a593Smuzhiyun priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
216*4882a593Smuzhiyun if (IS_ERR(priv->clk_ahb)) {
217*4882a593Smuzhiyun dev_err(priv->dev, "Failed to get \"ahb\" clk\n");
218*4882a593Smuzhiyun return PTR_ERR(priv->clk_ahb);
219*4882a593Smuzhiyun }
220*4882a593Smuzhiyun
221*4882a593Smuzhiyun err = clk_prepare_enable(priv->clk_ahb);
222*4882a593Smuzhiyun if (err) {
223*4882a593Smuzhiyun dev_err(priv->dev, "Failed to enable ahb_clk!\n");
224*4882a593Smuzhiyun return err;
225*4882a593Smuzhiyun }
226*4882a593Smuzhiyun err = devm_add_action_or_reset(priv->dev,
227*4882a593Smuzhiyun asm9260_clk_disable_unprepare,
228*4882a593Smuzhiyun priv->clk_ahb);
229*4882a593Smuzhiyun if (err)
230*4882a593Smuzhiyun return err;
231*4882a593Smuzhiyun
232*4882a593Smuzhiyun err = clk_set_rate(priv->clk, CLOCK_FREQ);
233*4882a593Smuzhiyun if (err) {
234*4882a593Smuzhiyun dev_err(priv->dev, "Failed to set rate!\n");
235*4882a593Smuzhiyun return err;
236*4882a593Smuzhiyun }
237*4882a593Smuzhiyun
238*4882a593Smuzhiyun err = clk_prepare_enable(priv->clk);
239*4882a593Smuzhiyun if (err) {
240*4882a593Smuzhiyun dev_err(priv->dev, "Failed to enable clk!\n");
241*4882a593Smuzhiyun return err;
242*4882a593Smuzhiyun }
243*4882a593Smuzhiyun err = devm_add_action_or_reset(priv->dev,
244*4882a593Smuzhiyun asm9260_clk_disable_unprepare,
245*4882a593Smuzhiyun priv->clk);
246*4882a593Smuzhiyun if (err)
247*4882a593Smuzhiyun return err;
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun /* wdt has internal divider */
250*4882a593Smuzhiyun clk = clk_get_rate(priv->clk);
251*4882a593Smuzhiyun if (!clk) {
252*4882a593Smuzhiyun dev_err(priv->dev, "Failed, clk is 0!\n");
253*4882a593Smuzhiyun return -EINVAL;
254*4882a593Smuzhiyun }
255*4882a593Smuzhiyun
256*4882a593Smuzhiyun priv->wdt_freq = clk / 2;
257*4882a593Smuzhiyun
258*4882a593Smuzhiyun return 0;
259*4882a593Smuzhiyun }
260*4882a593Smuzhiyun
asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv * priv)261*4882a593Smuzhiyun static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)
262*4882a593Smuzhiyun {
263*4882a593Smuzhiyun const char *tmp;
264*4882a593Smuzhiyun int ret;
265*4882a593Smuzhiyun
266*4882a593Smuzhiyun /* default mode */
267*4882a593Smuzhiyun priv->mode = HW_RESET;
268*4882a593Smuzhiyun
269*4882a593Smuzhiyun ret = of_property_read_string(priv->dev->of_node,
270*4882a593Smuzhiyun "alphascale,mode", &tmp);
271*4882a593Smuzhiyun if (ret < 0)
272*4882a593Smuzhiyun return;
273*4882a593Smuzhiyun
274*4882a593Smuzhiyun if (!strcmp(tmp, "hw"))
275*4882a593Smuzhiyun priv->mode = HW_RESET;
276*4882a593Smuzhiyun else if (!strcmp(tmp, "sw"))
277*4882a593Smuzhiyun priv->mode = SW_RESET;
278*4882a593Smuzhiyun else if (!strcmp(tmp, "debug"))
279*4882a593Smuzhiyun priv->mode = DEBUG;
280*4882a593Smuzhiyun else
281*4882a593Smuzhiyun dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",
282*4882a593Smuzhiyun tmp);
283*4882a593Smuzhiyun }
284*4882a593Smuzhiyun
asm9260_wdt_probe(struct platform_device * pdev)285*4882a593Smuzhiyun static int asm9260_wdt_probe(struct platform_device *pdev)
286*4882a593Smuzhiyun {
287*4882a593Smuzhiyun struct device *dev = &pdev->dev;
288*4882a593Smuzhiyun struct asm9260_wdt_priv *priv;
289*4882a593Smuzhiyun struct watchdog_device *wdd;
290*4882a593Smuzhiyun int ret;
291*4882a593Smuzhiyun static const char * const mode_name[] = { "hw", "sw", "debug", };
292*4882a593Smuzhiyun
293*4882a593Smuzhiyun priv = devm_kzalloc(dev, sizeof(struct asm9260_wdt_priv), GFP_KERNEL);
294*4882a593Smuzhiyun if (!priv)
295*4882a593Smuzhiyun return -ENOMEM;
296*4882a593Smuzhiyun
297*4882a593Smuzhiyun priv->dev = dev;
298*4882a593Smuzhiyun
299*4882a593Smuzhiyun priv->iobase = devm_platform_ioremap_resource(pdev, 0);
300*4882a593Smuzhiyun if (IS_ERR(priv->iobase))
301*4882a593Smuzhiyun return PTR_ERR(priv->iobase);
302*4882a593Smuzhiyun
303*4882a593Smuzhiyun priv->rst = devm_reset_control_get_exclusive(dev, "wdt_rst");
304*4882a593Smuzhiyun if (IS_ERR(priv->rst))
305*4882a593Smuzhiyun return PTR_ERR(priv->rst);
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun ret = asm9260_wdt_get_dt_clks(priv);
308*4882a593Smuzhiyun if (ret)
309*4882a593Smuzhiyun return ret;
310*4882a593Smuzhiyun
311*4882a593Smuzhiyun wdd = &priv->wdd;
312*4882a593Smuzhiyun wdd->info = &asm9260_wdt_ident;
313*4882a593Smuzhiyun wdd->ops = &asm9260_wdt_ops;
314*4882a593Smuzhiyun wdd->min_timeout = 1;
315*4882a593Smuzhiyun wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);
316*4882a593Smuzhiyun wdd->parent = dev;
317*4882a593Smuzhiyun
318*4882a593Smuzhiyun watchdog_set_drvdata(wdd, priv);
319*4882a593Smuzhiyun
320*4882a593Smuzhiyun /*
321*4882a593Smuzhiyun * If 'timeout-sec' unspecified in devicetree, assume a 30 second
322*4882a593Smuzhiyun * default, unless the max timeout is less than 30 seconds, then use
323*4882a593Smuzhiyun * the max instead.
324*4882a593Smuzhiyun */
325*4882a593Smuzhiyun wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
326*4882a593Smuzhiyun watchdog_init_timeout(wdd, 0, dev);
327*4882a593Smuzhiyun
328*4882a593Smuzhiyun asm9260_wdt_get_dt_mode(priv);
329*4882a593Smuzhiyun
330*4882a593Smuzhiyun if (priv->mode != HW_RESET)
331*4882a593Smuzhiyun priv->irq = platform_get_irq(pdev, 0);
332*4882a593Smuzhiyun
333*4882a593Smuzhiyun if (priv->irq > 0) {
334*4882a593Smuzhiyun /*
335*4882a593Smuzhiyun * Not all supported platforms specify an interrupt for the
336*4882a593Smuzhiyun * watchdog, so let's make it optional.
337*4882a593Smuzhiyun */
338*4882a593Smuzhiyun ret = devm_request_irq(dev, priv->irq, asm9260_wdt_irq, 0,
339*4882a593Smuzhiyun pdev->name, priv);
340*4882a593Smuzhiyun if (ret < 0)
341*4882a593Smuzhiyun dev_warn(dev, "failed to request IRQ\n");
342*4882a593Smuzhiyun }
343*4882a593Smuzhiyun
344*4882a593Smuzhiyun watchdog_set_restart_priority(wdd, 128);
345*4882a593Smuzhiyun
346*4882a593Smuzhiyun watchdog_stop_on_reboot(wdd);
347*4882a593Smuzhiyun watchdog_stop_on_unregister(wdd);
348*4882a593Smuzhiyun ret = devm_watchdog_register_device(dev, wdd);
349*4882a593Smuzhiyun if (ret)
350*4882a593Smuzhiyun return ret;
351*4882a593Smuzhiyun
352*4882a593Smuzhiyun platform_set_drvdata(pdev, priv);
353*4882a593Smuzhiyun
354*4882a593Smuzhiyun dev_info(dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
355*4882a593Smuzhiyun wdd->timeout, mode_name[priv->mode]);
356*4882a593Smuzhiyun return 0;
357*4882a593Smuzhiyun }
358*4882a593Smuzhiyun
359*4882a593Smuzhiyun static const struct of_device_id asm9260_wdt_of_match[] = {
360*4882a593Smuzhiyun { .compatible = "alphascale,asm9260-wdt"},
361*4882a593Smuzhiyun {},
362*4882a593Smuzhiyun };
363*4882a593Smuzhiyun MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
364*4882a593Smuzhiyun
365*4882a593Smuzhiyun static struct platform_driver asm9260_wdt_driver = {
366*4882a593Smuzhiyun .driver = {
367*4882a593Smuzhiyun .name = "asm9260-wdt",
368*4882a593Smuzhiyun .of_match_table = asm9260_wdt_of_match,
369*4882a593Smuzhiyun },
370*4882a593Smuzhiyun .probe = asm9260_wdt_probe,
371*4882a593Smuzhiyun };
372*4882a593Smuzhiyun module_platform_driver(asm9260_wdt_driver);
373*4882a593Smuzhiyun
374*4882a593Smuzhiyun MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
375*4882a593Smuzhiyun MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
376*4882a593Smuzhiyun MODULE_LICENSE("GPL");
377