162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Generic heartbeat driver for regular LED banks
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2007 - 2010  Paul Mundt
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Most SH reference boards include a number of individual LEDs that can
862306a36Sopenharmony_ci * be independently controlled (either via a pre-defined hardware
962306a36Sopenharmony_ci * function or via the LED class, if desired -- the hardware tends to
1062306a36Sopenharmony_ci * encapsulate some of the same "triggers" that the LED class supports,
1162306a36Sopenharmony_ci * so there's not too much value in it).
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci * Additionally, most of these boards also have a LED bank that we've
1462306a36Sopenharmony_ci * traditionally used for strobing the load average. This use case is
1562306a36Sopenharmony_ci * handled by this driver, rather than giving each LED bit position its
1662306a36Sopenharmony_ci * own struct device.
1762306a36Sopenharmony_ci */
1862306a36Sopenharmony_ci#include <linux/init.h>
1962306a36Sopenharmony_ci#include <linux/platform_device.h>
2062306a36Sopenharmony_ci#include <linux/sched.h>
2162306a36Sopenharmony_ci#include <linux/sched/loadavg.h>
2262306a36Sopenharmony_ci#include <linux/timer.h>
2362306a36Sopenharmony_ci#include <linux/io.h>
2462306a36Sopenharmony_ci#include <linux/slab.h>
2562306a36Sopenharmony_ci#include <asm/heartbeat.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define DRV_NAME "heartbeat"
2862306a36Sopenharmony_ci#define DRV_VERSION "0.1.2"
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic unsigned char default_bit_pos[] = { 0, 1, 2, 3, 4, 5, 6, 7 };
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic inline void heartbeat_toggle_bit(struct heartbeat_data *hd,
3362306a36Sopenharmony_ci					unsigned bit, unsigned int inverted)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	unsigned int new;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	new = (1 << hd->bit_pos[bit]);
3862306a36Sopenharmony_ci	if (inverted)
3962306a36Sopenharmony_ci		new = ~new;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	new &= hd->mask;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	switch (hd->regsize) {
4462306a36Sopenharmony_ci	case 32:
4562306a36Sopenharmony_ci		new |= ioread32(hd->base) & ~hd->mask;
4662306a36Sopenharmony_ci		iowrite32(new, hd->base);
4762306a36Sopenharmony_ci		break;
4862306a36Sopenharmony_ci	case 16:
4962306a36Sopenharmony_ci		new |= ioread16(hd->base) & ~hd->mask;
5062306a36Sopenharmony_ci		iowrite16(new, hd->base);
5162306a36Sopenharmony_ci		break;
5262306a36Sopenharmony_ci	default:
5362306a36Sopenharmony_ci		new |= ioread8(hd->base) & ~hd->mask;
5462306a36Sopenharmony_ci		iowrite8(new, hd->base);
5562306a36Sopenharmony_ci		break;
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic void heartbeat_timer(struct timer_list *t)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	struct heartbeat_data *hd = from_timer(hd, t, timer);
6262306a36Sopenharmony_ci	static unsigned bit = 0, up = 1;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	heartbeat_toggle_bit(hd, bit, hd->flags & HEARTBEAT_INVERTED);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	bit += up;
6762306a36Sopenharmony_ci	if ((bit == 0) || (bit == (hd->nr_bits)-1))
6862306a36Sopenharmony_ci		up = -up;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	mod_timer(&hd->timer, jiffies + (110 - ((300 << FSHIFT) /
7162306a36Sopenharmony_ci			((avenrun[0] / 5) + (3 << FSHIFT)))));
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic int heartbeat_drv_probe(struct platform_device *pdev)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	struct resource *res;
7762306a36Sopenharmony_ci	struct heartbeat_data *hd;
7862306a36Sopenharmony_ci	int i;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	if (unlikely(pdev->num_resources != 1)) {
8162306a36Sopenharmony_ci		dev_err(&pdev->dev, "invalid number of resources\n");
8262306a36Sopenharmony_ci		return -EINVAL;
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
8662306a36Sopenharmony_ci	if (unlikely(res == NULL)) {
8762306a36Sopenharmony_ci		dev_err(&pdev->dev, "invalid resource\n");
8862306a36Sopenharmony_ci		return -EINVAL;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	if (pdev->dev.platform_data) {
9262306a36Sopenharmony_ci		hd = pdev->dev.platform_data;
9362306a36Sopenharmony_ci	} else {
9462306a36Sopenharmony_ci		hd = kzalloc(sizeof(struct heartbeat_data), GFP_KERNEL);
9562306a36Sopenharmony_ci		if (unlikely(!hd))
9662306a36Sopenharmony_ci			return -ENOMEM;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	hd->base = ioremap(res->start, resource_size(res));
10062306a36Sopenharmony_ci	if (unlikely(!hd->base)) {
10162306a36Sopenharmony_ci		dev_err(&pdev->dev, "ioremap failed\n");
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci		if (!pdev->dev.platform_data)
10462306a36Sopenharmony_ci			kfree(hd);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci		return -ENXIO;
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (!hd->nr_bits) {
11062306a36Sopenharmony_ci		hd->bit_pos = default_bit_pos;
11162306a36Sopenharmony_ci		hd->nr_bits = ARRAY_SIZE(default_bit_pos);
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	hd->mask = 0;
11562306a36Sopenharmony_ci	for (i = 0; i < hd->nr_bits; i++)
11662306a36Sopenharmony_ci		hd->mask |= (1 << hd->bit_pos[i]);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	if (!hd->regsize) {
11962306a36Sopenharmony_ci		switch (res->flags & IORESOURCE_MEM_TYPE_MASK) {
12062306a36Sopenharmony_ci		case IORESOURCE_MEM_32BIT:
12162306a36Sopenharmony_ci			hd->regsize = 32;
12262306a36Sopenharmony_ci			break;
12362306a36Sopenharmony_ci		case IORESOURCE_MEM_16BIT:
12462306a36Sopenharmony_ci			hd->regsize = 16;
12562306a36Sopenharmony_ci			break;
12662306a36Sopenharmony_ci		case IORESOURCE_MEM_8BIT:
12762306a36Sopenharmony_ci		default:
12862306a36Sopenharmony_ci			hd->regsize = 8;
12962306a36Sopenharmony_ci			break;
13062306a36Sopenharmony_ci		}
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	timer_setup(&hd->timer, heartbeat_timer, 0);
13462306a36Sopenharmony_ci	platform_set_drvdata(pdev, hd);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	return mod_timer(&hd->timer, jiffies + 1);
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic struct platform_driver heartbeat_driver = {
14062306a36Sopenharmony_ci	.probe		= heartbeat_drv_probe,
14162306a36Sopenharmony_ci	.driver		= {
14262306a36Sopenharmony_ci		.name			= DRV_NAME,
14362306a36Sopenharmony_ci		.suppress_bind_attrs	= true,
14462306a36Sopenharmony_ci	},
14562306a36Sopenharmony_ci};
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic int __init heartbeat_init(void)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	printk(KERN_NOTICE DRV_NAME ": version %s loaded\n", DRV_VERSION);
15062306a36Sopenharmony_ci	return platform_driver_register(&heartbeat_driver);
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_cidevice_initcall(heartbeat_init);
153