18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Generic heartbeat driver for regular LED banks
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2007 - 2010  Paul Mundt
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Most SH reference boards include a number of individual LEDs that can
88c2ecf20Sopenharmony_ci * be independently controlled (either via a pre-defined hardware
98c2ecf20Sopenharmony_ci * function or via the LED class, if desired -- the hardware tends to
108c2ecf20Sopenharmony_ci * encapsulate some of the same "triggers" that the LED class supports,
118c2ecf20Sopenharmony_ci * so there's not too much value in it).
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci * Additionally, most of these boards also have a LED bank that we've
148c2ecf20Sopenharmony_ci * traditionally used for strobing the load average. This use case is
158c2ecf20Sopenharmony_ci * handled by this driver, rather than giving each LED bit position its
168c2ecf20Sopenharmony_ci * own struct device.
178c2ecf20Sopenharmony_ci */
188c2ecf20Sopenharmony_ci#include <linux/init.h>
198c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
208c2ecf20Sopenharmony_ci#include <linux/sched.h>
218c2ecf20Sopenharmony_ci#include <linux/sched/loadavg.h>
228c2ecf20Sopenharmony_ci#include <linux/timer.h>
238c2ecf20Sopenharmony_ci#include <linux/io.h>
248c2ecf20Sopenharmony_ci#include <linux/slab.h>
258c2ecf20Sopenharmony_ci#include <asm/heartbeat.h>
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define DRV_NAME "heartbeat"
288c2ecf20Sopenharmony_ci#define DRV_VERSION "0.1.2"
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistatic unsigned char default_bit_pos[] = { 0, 1, 2, 3, 4, 5, 6, 7 };
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistatic inline void heartbeat_toggle_bit(struct heartbeat_data *hd,
338c2ecf20Sopenharmony_ci					unsigned bit, unsigned int inverted)
348c2ecf20Sopenharmony_ci{
358c2ecf20Sopenharmony_ci	unsigned int new;
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci	new = (1 << hd->bit_pos[bit]);
388c2ecf20Sopenharmony_ci	if (inverted)
398c2ecf20Sopenharmony_ci		new = ~new;
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	new &= hd->mask;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	switch (hd->regsize) {
448c2ecf20Sopenharmony_ci	case 32:
458c2ecf20Sopenharmony_ci		new |= ioread32(hd->base) & ~hd->mask;
468c2ecf20Sopenharmony_ci		iowrite32(new, hd->base);
478c2ecf20Sopenharmony_ci		break;
488c2ecf20Sopenharmony_ci	case 16:
498c2ecf20Sopenharmony_ci		new |= ioread16(hd->base) & ~hd->mask;
508c2ecf20Sopenharmony_ci		iowrite16(new, hd->base);
518c2ecf20Sopenharmony_ci		break;
528c2ecf20Sopenharmony_ci	default:
538c2ecf20Sopenharmony_ci		new |= ioread8(hd->base) & ~hd->mask;
548c2ecf20Sopenharmony_ci		iowrite8(new, hd->base);
558c2ecf20Sopenharmony_ci		break;
568c2ecf20Sopenharmony_ci	}
578c2ecf20Sopenharmony_ci}
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic void heartbeat_timer(struct timer_list *t)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	struct heartbeat_data *hd = from_timer(hd, t, timer);
628c2ecf20Sopenharmony_ci	static unsigned bit = 0, up = 1;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	heartbeat_toggle_bit(hd, bit, hd->flags & HEARTBEAT_INVERTED);
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	bit += up;
678c2ecf20Sopenharmony_ci	if ((bit == 0) || (bit == (hd->nr_bits)-1))
688c2ecf20Sopenharmony_ci		up = -up;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	mod_timer(&hd->timer, jiffies + (110 - ((300 << FSHIFT) /
718c2ecf20Sopenharmony_ci			((avenrun[0] / 5) + (3 << FSHIFT)))));
728c2ecf20Sopenharmony_ci}
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic int heartbeat_drv_probe(struct platform_device *pdev)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	struct resource *res;
778c2ecf20Sopenharmony_ci	struct heartbeat_data *hd;
788c2ecf20Sopenharmony_ci	int i;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	if (unlikely(pdev->num_resources != 1)) {
818c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "invalid number of resources\n");
828c2ecf20Sopenharmony_ci		return -EINVAL;
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
868c2ecf20Sopenharmony_ci	if (unlikely(res == NULL)) {
878c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "invalid resource\n");
888c2ecf20Sopenharmony_ci		return -EINVAL;
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	if (pdev->dev.platform_data) {
928c2ecf20Sopenharmony_ci		hd = pdev->dev.platform_data;
938c2ecf20Sopenharmony_ci	} else {
948c2ecf20Sopenharmony_ci		hd = kzalloc(sizeof(struct heartbeat_data), GFP_KERNEL);
958c2ecf20Sopenharmony_ci		if (unlikely(!hd))
968c2ecf20Sopenharmony_ci			return -ENOMEM;
978c2ecf20Sopenharmony_ci	}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	hd->base = ioremap(res->start, resource_size(res));
1008c2ecf20Sopenharmony_ci	if (unlikely(!hd->base)) {
1018c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "ioremap failed\n");
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci		if (!pdev->dev.platform_data)
1048c2ecf20Sopenharmony_ci			kfree(hd);
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci		return -ENXIO;
1078c2ecf20Sopenharmony_ci	}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	if (!hd->nr_bits) {
1108c2ecf20Sopenharmony_ci		hd->bit_pos = default_bit_pos;
1118c2ecf20Sopenharmony_ci		hd->nr_bits = ARRAY_SIZE(default_bit_pos);
1128c2ecf20Sopenharmony_ci	}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	hd->mask = 0;
1158c2ecf20Sopenharmony_ci	for (i = 0; i < hd->nr_bits; i++)
1168c2ecf20Sopenharmony_ci		hd->mask |= (1 << hd->bit_pos[i]);
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	if (!hd->regsize) {
1198c2ecf20Sopenharmony_ci		switch (res->flags & IORESOURCE_MEM_TYPE_MASK) {
1208c2ecf20Sopenharmony_ci		case IORESOURCE_MEM_32BIT:
1218c2ecf20Sopenharmony_ci			hd->regsize = 32;
1228c2ecf20Sopenharmony_ci			break;
1238c2ecf20Sopenharmony_ci		case IORESOURCE_MEM_16BIT:
1248c2ecf20Sopenharmony_ci			hd->regsize = 16;
1258c2ecf20Sopenharmony_ci			break;
1268c2ecf20Sopenharmony_ci		case IORESOURCE_MEM_8BIT:
1278c2ecf20Sopenharmony_ci		default:
1288c2ecf20Sopenharmony_ci			hd->regsize = 8;
1298c2ecf20Sopenharmony_ci			break;
1308c2ecf20Sopenharmony_ci		}
1318c2ecf20Sopenharmony_ci	}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	timer_setup(&hd->timer, heartbeat_timer, 0);
1348c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, hd);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	return mod_timer(&hd->timer, jiffies + 1);
1378c2ecf20Sopenharmony_ci}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_cistatic struct platform_driver heartbeat_driver = {
1408c2ecf20Sopenharmony_ci	.probe		= heartbeat_drv_probe,
1418c2ecf20Sopenharmony_ci	.driver		= {
1428c2ecf20Sopenharmony_ci		.name			= DRV_NAME,
1438c2ecf20Sopenharmony_ci		.suppress_bind_attrs	= true,
1448c2ecf20Sopenharmony_ci	},
1458c2ecf20Sopenharmony_ci};
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_cistatic int __init heartbeat_init(void)
1488c2ecf20Sopenharmony_ci{
1498c2ecf20Sopenharmony_ci	printk(KERN_NOTICE DRV_NAME ": version %s loaded\n", DRV_VERSION);
1508c2ecf20Sopenharmony_ci	return platform_driver_register(&heartbeat_driver);
1518c2ecf20Sopenharmony_ci}
1528c2ecf20Sopenharmony_cidevice_initcall(heartbeat_init);
153