18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* display7seg.c - Driver implementation for the 7-segment display 38c2ecf20Sopenharmony_ci * present on Sun Microsystems CP1400 and CP1500 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2000 Eric Brower (ebrower@usa.net) 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/device.h> 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/fs.h> 128c2ecf20Sopenharmony_ci#include <linux/errno.h> 138c2ecf20Sopenharmony_ci#include <linux/major.h> 148c2ecf20Sopenharmony_ci#include <linux/miscdevice.h> 158c2ecf20Sopenharmony_ci#include <linux/ioport.h> /* request_region */ 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci#include <linux/mutex.h> 188c2ecf20Sopenharmony_ci#include <linux/of.h> 198c2ecf20Sopenharmony_ci#include <linux/of_device.h> 208c2ecf20Sopenharmony_ci#include <linux/atomic.h> 218c2ecf20Sopenharmony_ci#include <linux/uaccess.h> /* put_/get_user */ 228c2ecf20Sopenharmony_ci#include <asm/io.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#include <asm/display7seg.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define DRIVER_NAME "d7s" 278c2ecf20Sopenharmony_ci#define PFX DRIVER_NAME ": " 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(d7s_mutex); 308c2ecf20Sopenharmony_cistatic int sol_compat = 0; /* Solaris compatibility mode */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci/* Solaris compatibility flag - 338c2ecf20Sopenharmony_ci * The Solaris implementation omits support for several 348c2ecf20Sopenharmony_ci * documented driver features (ref Sun doc 806-0180-03). 358c2ecf20Sopenharmony_ci * By default, this module supports the documented driver 368c2ecf20Sopenharmony_ci * abilities, rather than the Solaris implementation: 378c2ecf20Sopenharmony_ci * 388c2ecf20Sopenharmony_ci * 1) Device ALWAYS reverts to OBP-specified FLIPPED mode 398c2ecf20Sopenharmony_ci * upon closure of device or module unload. 408c2ecf20Sopenharmony_ci * 2) Device ioctls D7SIOCRD/D7SIOCWR honor toggling of 418c2ecf20Sopenharmony_ci * FLIP bit 428c2ecf20Sopenharmony_ci * 438c2ecf20Sopenharmony_ci * If you wish the device to operate as under Solaris, 448c2ecf20Sopenharmony_ci * omitting above features, set this parameter to non-zero. 458c2ecf20Sopenharmony_ci */ 468c2ecf20Sopenharmony_cimodule_param(sol_compat, int, 0); 478c2ecf20Sopenharmony_ciMODULE_PARM_DESC(sol_compat, 488c2ecf20Sopenharmony_ci "Disables documented functionality omitted from Solaris driver"); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ciMODULE_AUTHOR("Eric Brower <ebrower@usa.net>"); 518c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("7-Segment Display driver for Sun Microsystems CP1400/1500"); 528c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 538c2ecf20Sopenharmony_ciMODULE_SUPPORTED_DEVICE("d7s"); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistruct d7s { 568c2ecf20Sopenharmony_ci void __iomem *regs; 578c2ecf20Sopenharmony_ci bool flipped; 588c2ecf20Sopenharmony_ci}; 598c2ecf20Sopenharmony_cistruct d7s *d7s_device; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci/* 628c2ecf20Sopenharmony_ci * Register block address- see header for details 638c2ecf20Sopenharmony_ci * ----------------------------------------- 648c2ecf20Sopenharmony_ci * | DP | ALARM | FLIP | 4 | 3 | 2 | 1 | 0 | 658c2ecf20Sopenharmony_ci * ----------------------------------------- 668c2ecf20Sopenharmony_ci * 678c2ecf20Sopenharmony_ci * DP - Toggles decimal point on/off 688c2ecf20Sopenharmony_ci * ALARM - Toggles "Alarm" LED green/red 698c2ecf20Sopenharmony_ci * FLIP - Inverts display for upside-down mounted board 708c2ecf20Sopenharmony_ci * bits 0-4 - 7-segment display contents 718c2ecf20Sopenharmony_ci */ 728c2ecf20Sopenharmony_cistatic atomic_t d7s_users = ATOMIC_INIT(0); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic int d7s_open(struct inode *inode, struct file *f) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci if (D7S_MINOR != iminor(inode)) 778c2ecf20Sopenharmony_ci return -ENODEV; 788c2ecf20Sopenharmony_ci atomic_inc(&d7s_users); 798c2ecf20Sopenharmony_ci return 0; 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic int d7s_release(struct inode *inode, struct file *f) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci /* Reset flipped state to OBP default only if 858c2ecf20Sopenharmony_ci * no other users have the device open and we 868c2ecf20Sopenharmony_ci * are not operating in solaris-compat mode 878c2ecf20Sopenharmony_ci */ 888c2ecf20Sopenharmony_ci if (atomic_dec_and_test(&d7s_users) && !sol_compat) { 898c2ecf20Sopenharmony_ci struct d7s *p = d7s_device; 908c2ecf20Sopenharmony_ci u8 regval = 0; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci regval = readb(p->regs); 938c2ecf20Sopenharmony_ci if (p->flipped) 948c2ecf20Sopenharmony_ci regval |= D7S_FLIP; 958c2ecf20Sopenharmony_ci else 968c2ecf20Sopenharmony_ci regval &= ~D7S_FLIP; 978c2ecf20Sopenharmony_ci writeb(regval, p->regs); 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci return 0; 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic long d7s_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci struct d7s *p = d7s_device; 1068c2ecf20Sopenharmony_ci u8 regs = readb(p->regs); 1078c2ecf20Sopenharmony_ci int error = 0; 1088c2ecf20Sopenharmony_ci u8 ireg = 0; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci if (D7S_MINOR != iminor(file_inode(file))) 1118c2ecf20Sopenharmony_ci return -ENODEV; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci mutex_lock(&d7s_mutex); 1148c2ecf20Sopenharmony_ci switch (cmd) { 1158c2ecf20Sopenharmony_ci case D7SIOCWR: 1168c2ecf20Sopenharmony_ci /* assign device register values we mask-out D7S_FLIP 1178c2ecf20Sopenharmony_ci * if in sol_compat mode 1188c2ecf20Sopenharmony_ci */ 1198c2ecf20Sopenharmony_ci if (get_user(ireg, (int __user *) arg)) { 1208c2ecf20Sopenharmony_ci error = -EFAULT; 1218c2ecf20Sopenharmony_ci break; 1228c2ecf20Sopenharmony_ci } 1238c2ecf20Sopenharmony_ci if (sol_compat) { 1248c2ecf20Sopenharmony_ci if (regs & D7S_FLIP) 1258c2ecf20Sopenharmony_ci ireg |= D7S_FLIP; 1268c2ecf20Sopenharmony_ci else 1278c2ecf20Sopenharmony_ci ireg &= ~D7S_FLIP; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci writeb(ireg, p->regs); 1308c2ecf20Sopenharmony_ci break; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci case D7SIOCRD: 1338c2ecf20Sopenharmony_ci /* retrieve device register values 1348c2ecf20Sopenharmony_ci * NOTE: Solaris implementation returns D7S_FLIP bit 1358c2ecf20Sopenharmony_ci * as toggled by user, even though it does not honor it. 1368c2ecf20Sopenharmony_ci * This driver will not misinform you about the state 1378c2ecf20Sopenharmony_ci * of your hardware while in sol_compat mode 1388c2ecf20Sopenharmony_ci */ 1398c2ecf20Sopenharmony_ci if (put_user(regs, (int __user *) arg)) { 1408c2ecf20Sopenharmony_ci error = -EFAULT; 1418c2ecf20Sopenharmony_ci break; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci break; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci case D7SIOCTM: 1468c2ecf20Sopenharmony_ci /* toggle device mode-- flip display orientation */ 1478c2ecf20Sopenharmony_ci regs ^= D7S_FLIP; 1488c2ecf20Sopenharmony_ci writeb(regs, p->regs); 1498c2ecf20Sopenharmony_ci break; 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci mutex_unlock(&d7s_mutex); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci return error; 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_cistatic const struct file_operations d7s_fops = { 1578c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1588c2ecf20Sopenharmony_ci .unlocked_ioctl = d7s_ioctl, 1598c2ecf20Sopenharmony_ci .compat_ioctl = compat_ptr_ioctl, 1608c2ecf20Sopenharmony_ci .open = d7s_open, 1618c2ecf20Sopenharmony_ci .release = d7s_release, 1628c2ecf20Sopenharmony_ci .llseek = noop_llseek, 1638c2ecf20Sopenharmony_ci}; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic struct miscdevice d7s_miscdev = { 1668c2ecf20Sopenharmony_ci .minor = D7S_MINOR, 1678c2ecf20Sopenharmony_ci .name = DRIVER_NAME, 1688c2ecf20Sopenharmony_ci .fops = &d7s_fops 1698c2ecf20Sopenharmony_ci}; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_cistatic int d7s_probe(struct platform_device *op) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci struct device_node *opts; 1748c2ecf20Sopenharmony_ci int err = -EINVAL; 1758c2ecf20Sopenharmony_ci struct d7s *p; 1768c2ecf20Sopenharmony_ci u8 regs; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci if (d7s_device) 1798c2ecf20Sopenharmony_ci goto out; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci p = devm_kzalloc(&op->dev, sizeof(*p), GFP_KERNEL); 1828c2ecf20Sopenharmony_ci err = -ENOMEM; 1838c2ecf20Sopenharmony_ci if (!p) 1848c2ecf20Sopenharmony_ci goto out; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci p->regs = of_ioremap(&op->resource[0], 0, sizeof(u8), "d7s"); 1878c2ecf20Sopenharmony_ci if (!p->regs) { 1888c2ecf20Sopenharmony_ci printk(KERN_ERR PFX "Cannot map chip registers\n"); 1898c2ecf20Sopenharmony_ci goto out_free; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci err = misc_register(&d7s_miscdev); 1938c2ecf20Sopenharmony_ci if (err) { 1948c2ecf20Sopenharmony_ci printk(KERN_ERR PFX "Unable to acquire miscdevice minor %i\n", 1958c2ecf20Sopenharmony_ci D7S_MINOR); 1968c2ecf20Sopenharmony_ci goto out_iounmap; 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci /* OBP option "d7s-flipped?" is honored as default for the 2008c2ecf20Sopenharmony_ci * device, and reset default when detached 2018c2ecf20Sopenharmony_ci */ 2028c2ecf20Sopenharmony_ci regs = readb(p->regs); 2038c2ecf20Sopenharmony_ci opts = of_find_node_by_path("/options"); 2048c2ecf20Sopenharmony_ci if (opts && 2058c2ecf20Sopenharmony_ci of_get_property(opts, "d7s-flipped?", NULL)) 2068c2ecf20Sopenharmony_ci p->flipped = true; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci if (p->flipped) 2098c2ecf20Sopenharmony_ci regs |= D7S_FLIP; 2108c2ecf20Sopenharmony_ci else 2118c2ecf20Sopenharmony_ci regs &= ~D7S_FLIP; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci writeb(regs, p->regs); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci printk(KERN_INFO PFX "7-Segment Display%pOF at [%s:0x%llx] %s\n", 2168c2ecf20Sopenharmony_ci op->dev.of_node, 2178c2ecf20Sopenharmony_ci (regs & D7S_FLIP) ? " (FLIPPED)" : "", 2188c2ecf20Sopenharmony_ci op->resource[0].start, 2198c2ecf20Sopenharmony_ci sol_compat ? "in sol_compat mode" : ""); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci dev_set_drvdata(&op->dev, p); 2228c2ecf20Sopenharmony_ci d7s_device = p; 2238c2ecf20Sopenharmony_ci err = 0; 2248c2ecf20Sopenharmony_ci of_node_put(opts); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ciout: 2278c2ecf20Sopenharmony_ci return err; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ciout_iounmap: 2308c2ecf20Sopenharmony_ci of_iounmap(&op->resource[0], p->regs, sizeof(u8)); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ciout_free: 2338c2ecf20Sopenharmony_ci goto out; 2348c2ecf20Sopenharmony_ci} 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_cistatic int d7s_remove(struct platform_device *op) 2378c2ecf20Sopenharmony_ci{ 2388c2ecf20Sopenharmony_ci struct d7s *p = dev_get_drvdata(&op->dev); 2398c2ecf20Sopenharmony_ci u8 regs = readb(p->regs); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci /* Honor OBP d7s-flipped? unless operating in solaris-compat mode */ 2428c2ecf20Sopenharmony_ci if (sol_compat) { 2438c2ecf20Sopenharmony_ci if (p->flipped) 2448c2ecf20Sopenharmony_ci regs |= D7S_FLIP; 2458c2ecf20Sopenharmony_ci else 2468c2ecf20Sopenharmony_ci regs &= ~D7S_FLIP; 2478c2ecf20Sopenharmony_ci writeb(regs, p->regs); 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci misc_deregister(&d7s_miscdev); 2518c2ecf20Sopenharmony_ci of_iounmap(&op->resource[0], p->regs, sizeof(u8)); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci return 0; 2548c2ecf20Sopenharmony_ci} 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_cistatic const struct of_device_id d7s_match[] = { 2578c2ecf20Sopenharmony_ci { 2588c2ecf20Sopenharmony_ci .name = "display7seg", 2598c2ecf20Sopenharmony_ci }, 2608c2ecf20Sopenharmony_ci {}, 2618c2ecf20Sopenharmony_ci}; 2628c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, d7s_match); 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_cistatic struct platform_driver d7s_driver = { 2658c2ecf20Sopenharmony_ci .driver = { 2668c2ecf20Sopenharmony_ci .name = DRIVER_NAME, 2678c2ecf20Sopenharmony_ci .of_match_table = d7s_match, 2688c2ecf20Sopenharmony_ci }, 2698c2ecf20Sopenharmony_ci .probe = d7s_probe, 2708c2ecf20Sopenharmony_ci .remove = d7s_remove, 2718c2ecf20Sopenharmony_ci}; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_cimodule_platform_driver(d7s_driver); 274