// SPDX-License-Identifier: GPL-2.0-or-later /* * MPC5200 General Purpose Timer device driver * * Copyright (c) 2009 Secret Lab Technologies Ltd. * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix * * This file is a driver for the General Purpose Timer (gpt) devices * found on the MPC5200 SoC. Each timer has an IO pin which can be used * for GPIO or can be used to raise interrupts. The timer function can * be used independently from the IO pin, or it can be used to control * output signals or measure input signals. * * This driver supports the GPIO and IRQ controller functions of the GPT * device. Timer functions are not yet supported. * * The timer gpt0 can be used as watchdog (wdt). If the wdt mode is used, * this prevents the use of any gpt0 gpt function (i.e. they will fail with * -EBUSY). Thus, the safety wdt function always has precedence over the gpt * function. If the kernel has been compiled with CONFIG_WATCHDOG_NOWAYOUT, * this means that gpt0 is locked in wdt mode until the next reboot - this * may be a requirement in safety applications. * * To use the GPIO function, the following two properties must be added * to the device tree node for the gpt device (typically in the .dts file * for the board): * gpio-controller; * #gpio-cells = < 2 >; * This driver will register the GPIO pin if it finds the gpio-controller * property in the device tree. * * To use the IRQ controller function, the following two properties must * be added to the device tree node for the gpt device: * interrupt-controller; * #interrupt-cells = < 1 >; * The IRQ controller binding only uses one cell to specify the interrupt, * and the IRQ flags are encoded in the cell. A cell is not used to encode * the IRQ number because the GPT only has a single IRQ source. For flags, * a value of '1' means rising edge sensitive and '2' means falling edge. * * The GPIO and the IRQ controller functions can be used at the same time, * but in this use case the IO line will only work as an input. Trying to * use it as a GPIO output will not work. * * When using the GPIO line as an output, it can either be driven as normal * IO, or it can be an Open Collector (OC) output. At the moment it is the * responsibility of either the bootloader or the platform setup code to set * the output mode. This driver does not change the output mode setting.
*/
MODULE_DESCRIPTION("Freescale MPC52xx gpt driver");
MODULE_AUTHOR("Sascha Hauer, Grant Likely, Albrecht Dreß");
MODULE_LICENSE("GPL");
/** * struct mpc52xx_gpt - Private data structure for MPC52xx GPT driver * @dev: pointer to device structure * @regs: virtual address of GPT registers * @lock: spinlock to coordinate between different functions. * @gc: gpio_chip instance structure; used when GPIO is enabled * @irqhost: Pointer to irq_domain instance; used when IRQ mode is supported * @wdt_mode: only relevant for gpt0: bit 0 (MPC52xx_GPT_CAN_WDT) indicates * if the gpt may be used as wdt, bit 1 (MPC52xx_GPT_IS_WDT) indicates * if the timer is actively used as wdt which blocks gpt functions
*/ struct mpc52xx_gpt_priv { struct list_head list; /* List of all GPT devices */ struct device *dev; struct mpc52xx_gpt __iomem *regs;
raw_spinlock_t lock; struct irq_domain *irqhost;
u32 ipb_freq;
u8 wdt_mode;
/* If the GPT is currently disabled, then change it to be in Input * Capture mode. If the mode is non-zero, then the pin could be
* already in use for something. */
raw_spin_lock_irqsave(&gpt->lock, flags);
mode = in_be32(&gpt->regs->mode); if ((mode & MPC52xx_GPT_MODE_MS_MASK) == 0)
out_be32(&gpt->regs->mode, mode | MPC52xx_GPT_MODE_MS_IC);
raw_spin_unlock_irqrestore(&gpt->lock, flags);
staticint mpc52xx_gpt_do_start(struct mpc52xx_gpt_priv *gpt, u64 period, int continuous, int as_wdt)
{
u32 clear, set;
u64 clocks;
u32 prescale; unsignedlong flags;
clear = MPC52xx_GPT_MODE_MS_MASK | MPC52xx_GPT_MODE_CONTINUOUS;
set = MPC52xx_GPT_MODE_MS_GPIO | MPC52xx_GPT_MODE_COUNTER_ENABLE; if (as_wdt) {
clear |= MPC52xx_GPT_MODE_IRQ_EN;
set |= MPC52xx_GPT_MODE_WDT_EN;
} elseif (continuous)
set |= MPC52xx_GPT_MODE_CONTINUOUS;
/* Determine the number of clocks in the requested period. 64 bit * arithmetic is done here to preserve the precision until the value * is scaled back down into the u32 range. Period is in 'ns', bus
* frequency is in Hz. */
clocks = period * (u64)gpt->ipb_freq;
do_div(clocks, 1000000000); /* Scale it down to ns range */
/* This device cannot handle a clock count greater than 32 bits */ if (clocks > 0xffffffff) return -EINVAL;
/* Calculate the prescaler and count values from the clocks value. * 'clocks' is the number of clock ticks in the period. The timer * has 16 bit precision and a 16 bit prescaler. Prescaler is * calculated by integer dividing the clocks by 0x10000 (shifting * down 16 bits) to obtain the smallest possible divisor for clocks * to get a 16 bit count value. * * Note: the prescale register is '1' based, not '0' based. ie. a * value of '1' means divide the clock by one. 0xffff divides the * clock by 0xffff. '0x0000' does not divide by zero, but wraps * around and divides by 0x10000. That is why prescale must be
* a u32 variable, not a u16, for this calculation. */
prescale = (clocks >> 16) + 1;
do_div(clocks, prescale); if (clocks > 0xffff) {
pr_err("calculation error; prescale:%x clocks:%llx\n",
prescale, clocks); return -EINVAL;
}
/* Set and enable the timer, reject an attempt to use a wdt as gpt */
raw_spin_lock_irqsave(&gpt->lock, flags); if (as_wdt)
gpt->wdt_mode |= MPC52xx_GPT_IS_WDT; elseif ((gpt->wdt_mode & MPC52xx_GPT_IS_WDT) != 0) {
raw_spin_unlock_irqrestore(&gpt->lock, flags); return -EBUSY;
}
out_be32(&gpt->regs->count, prescale << 16 | clocks);
clrsetbits_be32(&gpt->regs->mode, clear, set);
raw_spin_unlock_irqrestore(&gpt->lock, flags);
return 0;
}
/** * mpc52xx_gpt_start_timer - Set and enable the GPT timer * @gpt: Pointer to gpt private data structure * @period: period of timer in ns; max. ~130s @ 33MHz IPB clock * @continuous: set to 1 to make timer continuous free running * * An interrupt will be generated every time the timer fires
*/ int mpc52xx_gpt_start_timer(struct mpc52xx_gpt_priv *gpt, u64 period, int continuous)
{ return mpc52xx_gpt_do_start(gpt, period, continuous, 0);
}
EXPORT_SYMBOL(mpc52xx_gpt_start_timer);
/** * mpc52xx_gpt_stop_timer - Stop a gpt * @gpt: Pointer to gpt private data structure * * Returns an error if attempting to stop a wdt
*/ int mpc52xx_gpt_stop_timer(struct mpc52xx_gpt_priv *gpt)
{ unsignedlong flags;
/* reject the operation if the timer is used as watchdog (gpt 0 only) */
raw_spin_lock_irqsave(&gpt->lock, flags); if ((gpt->wdt_mode & MPC52xx_GPT_IS_WDT) != 0) {
raw_spin_unlock_irqrestore(&gpt->lock, flags); return -EBUSY;
}
staticlong mpc52xx_wdt_ioctl(struct file *file, unsignedint cmd, unsignedlong arg)
{ struct mpc52xx_gpt_priv *gpt_wdt = file->private_data; int __user *data = (int __user *)arg; int timeout;
u64 real_timeout; int ret = 0;
switch (cmd) { case WDIOC_GETSUPPORT:
ret = copy_to_user(data, &mpc5200_wdt_info, sizeof(mpc5200_wdt_info)); if (ret)
ret = -EFAULT; break;
case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS:
ret = put_user(0, data); break;
case WDIOC_KEEPALIVE:
mpc52xx_gpt_wdt_ping(gpt_wdt); break;
case WDIOC_SETTIMEOUT:
ret = get_user(timeout, data); if (ret) break;
real_timeout = (u64) timeout * 1000000000ULL;
ret = mpc52xx_gpt_do_start(gpt_wdt, real_timeout, 0, 1); if (ret) break; /* fall through and return the timeout */
fallthrough;
case WDIOC_GETTIMEOUT: /* we need to round here as to avoid e.g. the following * situation: * - timeout requested is 1 second; * - real timeout @33MHz is 999997090ns * - the int divide by 10^9 will return 0.
*/
real_timeout =
mpc52xx_gpt_timer_period(gpt_wdt) + 500000000ULL;
do_div(real_timeout, 1000000000ULL);
timeout = (int) real_timeout;
ret = put_user(timeout, data); break;
default:
ret = -ENOTTY;
} return ret;
}
staticint mpc52xx_wdt_open(struct inode *inode, struct file *file)
{ int ret;
/* sanity check */ if (!mpc52xx_gpt_wdt) return -ENODEV;
/* /dev/watchdog can only be opened once */ if (test_and_set_bit(0, &wdt_is_active)) return -EBUSY;
/* Set and activate the watchdog with 30 seconds timeout */
ret = mpc52xx_gpt_do_start(mpc52xx_gpt_wdt, 30ULL * 1000000000ULL,
0, 1); if (ret) {
clear_bit(0, &wdt_is_active); return ret;
}
/* check if this device could be a watchdog */ if (of_property_read_bool(ofdev->dev.of_node, "fsl,has-wdt") ||
of_property_read_bool(ofdev->dev.of_node, "has-wdt")) { const u32 *on_boot_wdt;
gpt->wdt_mode = MPC52xx_GPT_CAN_WDT;
on_boot_wdt = of_get_property(ofdev->dev.of_node, "fsl,wdt-on-boot", NULL); if (on_boot_wdt) {
dev_info(gpt->dev, "used as watchdog\n");
gpt->wdt_mode |= MPC52xx_GPT_IS_WDT;
} else
dev_info(gpt->dev, "can function as watchdog\n");
mpc52xx_gpt_wdt_setup(gpt, on_boot_wdt);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.