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