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