162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* display7seg.c - Driver implementation for the 7-segment display 362306a36Sopenharmony_ci * present on Sun Microsystems CP1400 and CP1500 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2000 Eric Brower (ebrower@usa.net) 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/device.h> 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/fs.h> 1262306a36Sopenharmony_ci#include <linux/errno.h> 1362306a36Sopenharmony_ci#include <linux/major.h> 1462306a36Sopenharmony_ci#include <linux/miscdevice.h> 1562306a36Sopenharmony_ci#include <linux/ioport.h> /* request_region */ 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <linux/mutex.h> 1862306a36Sopenharmony_ci#include <linux/of.h> 1962306a36Sopenharmony_ci#include <linux/platform_device.h> 2062306a36Sopenharmony_ci#include <linux/atomic.h> 2162306a36Sopenharmony_ci#include <linux/uaccess.h> /* put_/get_user */ 2262306a36Sopenharmony_ci#include <asm/io.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include <asm/display7seg.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define DRIVER_NAME "d7s" 2762306a36Sopenharmony_ci#define PFX DRIVER_NAME ": " 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic DEFINE_MUTEX(d7s_mutex); 3062306a36Sopenharmony_cistatic int sol_compat = 0; /* Solaris compatibility mode */ 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* Solaris compatibility flag - 3362306a36Sopenharmony_ci * The Solaris implementation omits support for several 3462306a36Sopenharmony_ci * documented driver features (ref Sun doc 806-0180-03). 3562306a36Sopenharmony_ci * By default, this module supports the documented driver 3662306a36Sopenharmony_ci * abilities, rather than the Solaris implementation: 3762306a36Sopenharmony_ci * 3862306a36Sopenharmony_ci * 1) Device ALWAYS reverts to OBP-specified FLIPPED mode 3962306a36Sopenharmony_ci * upon closure of device or module unload. 4062306a36Sopenharmony_ci * 2) Device ioctls D7SIOCRD/D7SIOCWR honor toggling of 4162306a36Sopenharmony_ci * FLIP bit 4262306a36Sopenharmony_ci * 4362306a36Sopenharmony_ci * If you wish the device to operate as under Solaris, 4462306a36Sopenharmony_ci * omitting above features, set this parameter to non-zero. 4562306a36Sopenharmony_ci */ 4662306a36Sopenharmony_cimodule_param(sol_compat, int, 0); 4762306a36Sopenharmony_ciMODULE_PARM_DESC(sol_compat, 4862306a36Sopenharmony_ci "Disables documented functionality omitted from Solaris driver"); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ciMODULE_AUTHOR("Eric Brower <ebrower@usa.net>"); 5162306a36Sopenharmony_ciMODULE_DESCRIPTION("7-Segment Display driver for Sun Microsystems CP1400/1500"); 5262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistruct d7s { 5562306a36Sopenharmony_ci void __iomem *regs; 5662306a36Sopenharmony_ci bool flipped; 5762306a36Sopenharmony_ci}; 5862306a36Sopenharmony_cistruct d7s *d7s_device; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci/* 6162306a36Sopenharmony_ci * Register block address- see header for details 6262306a36Sopenharmony_ci * ----------------------------------------- 6362306a36Sopenharmony_ci * | DP | ALARM | FLIP | 4 | 3 | 2 | 1 | 0 | 6462306a36Sopenharmony_ci * ----------------------------------------- 6562306a36Sopenharmony_ci * 6662306a36Sopenharmony_ci * DP - Toggles decimal point on/off 6762306a36Sopenharmony_ci * ALARM - Toggles "Alarm" LED green/red 6862306a36Sopenharmony_ci * FLIP - Inverts display for upside-down mounted board 6962306a36Sopenharmony_ci * bits 0-4 - 7-segment display contents 7062306a36Sopenharmony_ci */ 7162306a36Sopenharmony_cistatic atomic_t d7s_users = ATOMIC_INIT(0); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic int d7s_open(struct inode *inode, struct file *f) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci if (D7S_MINOR != iminor(inode)) 7662306a36Sopenharmony_ci return -ENODEV; 7762306a36Sopenharmony_ci atomic_inc(&d7s_users); 7862306a36Sopenharmony_ci return 0; 7962306a36Sopenharmony_ci} 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistatic int d7s_release(struct inode *inode, struct file *f) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci /* Reset flipped state to OBP default only if 8462306a36Sopenharmony_ci * no other users have the device open and we 8562306a36Sopenharmony_ci * are not operating in solaris-compat mode 8662306a36Sopenharmony_ci */ 8762306a36Sopenharmony_ci if (atomic_dec_and_test(&d7s_users) && !sol_compat) { 8862306a36Sopenharmony_ci struct d7s *p = d7s_device; 8962306a36Sopenharmony_ci u8 regval = 0; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci regval = readb(p->regs); 9262306a36Sopenharmony_ci if (p->flipped) 9362306a36Sopenharmony_ci regval |= D7S_FLIP; 9462306a36Sopenharmony_ci else 9562306a36Sopenharmony_ci regval &= ~D7S_FLIP; 9662306a36Sopenharmony_ci writeb(regval, p->regs); 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci return 0; 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic long d7s_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci struct d7s *p = d7s_device; 10562306a36Sopenharmony_ci u8 regs = readb(p->regs); 10662306a36Sopenharmony_ci int error = 0; 10762306a36Sopenharmony_ci u8 ireg = 0; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci if (D7S_MINOR != iminor(file_inode(file))) 11062306a36Sopenharmony_ci return -ENODEV; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci mutex_lock(&d7s_mutex); 11362306a36Sopenharmony_ci switch (cmd) { 11462306a36Sopenharmony_ci case D7SIOCWR: 11562306a36Sopenharmony_ci /* assign device register values we mask-out D7S_FLIP 11662306a36Sopenharmony_ci * if in sol_compat mode 11762306a36Sopenharmony_ci */ 11862306a36Sopenharmony_ci if (get_user(ireg, (int __user *) arg)) { 11962306a36Sopenharmony_ci error = -EFAULT; 12062306a36Sopenharmony_ci break; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci if (sol_compat) { 12362306a36Sopenharmony_ci if (regs & D7S_FLIP) 12462306a36Sopenharmony_ci ireg |= D7S_FLIP; 12562306a36Sopenharmony_ci else 12662306a36Sopenharmony_ci ireg &= ~D7S_FLIP; 12762306a36Sopenharmony_ci } 12862306a36Sopenharmony_ci writeb(ireg, p->regs); 12962306a36Sopenharmony_ci break; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci case D7SIOCRD: 13262306a36Sopenharmony_ci /* retrieve device register values 13362306a36Sopenharmony_ci * NOTE: Solaris implementation returns D7S_FLIP bit 13462306a36Sopenharmony_ci * as toggled by user, even though it does not honor it. 13562306a36Sopenharmony_ci * This driver will not misinform you about the state 13662306a36Sopenharmony_ci * of your hardware while in sol_compat mode 13762306a36Sopenharmony_ci */ 13862306a36Sopenharmony_ci if (put_user(regs, (int __user *) arg)) { 13962306a36Sopenharmony_ci error = -EFAULT; 14062306a36Sopenharmony_ci break; 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci break; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci case D7SIOCTM: 14562306a36Sopenharmony_ci /* toggle device mode-- flip display orientation */ 14662306a36Sopenharmony_ci regs ^= D7S_FLIP; 14762306a36Sopenharmony_ci writeb(regs, p->regs); 14862306a36Sopenharmony_ci break; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci mutex_unlock(&d7s_mutex); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci return error; 15362306a36Sopenharmony_ci} 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_cistatic const struct file_operations d7s_fops = { 15662306a36Sopenharmony_ci .owner = THIS_MODULE, 15762306a36Sopenharmony_ci .unlocked_ioctl = d7s_ioctl, 15862306a36Sopenharmony_ci .compat_ioctl = compat_ptr_ioctl, 15962306a36Sopenharmony_ci .open = d7s_open, 16062306a36Sopenharmony_ci .release = d7s_release, 16162306a36Sopenharmony_ci .llseek = noop_llseek, 16262306a36Sopenharmony_ci}; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic struct miscdevice d7s_miscdev = { 16562306a36Sopenharmony_ci .minor = D7S_MINOR, 16662306a36Sopenharmony_ci .name = DRIVER_NAME, 16762306a36Sopenharmony_ci .fops = &d7s_fops 16862306a36Sopenharmony_ci}; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic int d7s_probe(struct platform_device *op) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci struct device_node *opts; 17362306a36Sopenharmony_ci int err = -EINVAL; 17462306a36Sopenharmony_ci struct d7s *p; 17562306a36Sopenharmony_ci u8 regs; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (d7s_device) 17862306a36Sopenharmony_ci goto out; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci p = devm_kzalloc(&op->dev, sizeof(*p), GFP_KERNEL); 18162306a36Sopenharmony_ci err = -ENOMEM; 18262306a36Sopenharmony_ci if (!p) 18362306a36Sopenharmony_ci goto out; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci p->regs = of_ioremap(&op->resource[0], 0, sizeof(u8), "d7s"); 18662306a36Sopenharmony_ci if (!p->regs) { 18762306a36Sopenharmony_ci printk(KERN_ERR PFX "Cannot map chip registers\n"); 18862306a36Sopenharmony_ci goto out; 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci err = misc_register(&d7s_miscdev); 19262306a36Sopenharmony_ci if (err) { 19362306a36Sopenharmony_ci printk(KERN_ERR PFX "Unable to acquire miscdevice minor %i\n", 19462306a36Sopenharmony_ci D7S_MINOR); 19562306a36Sopenharmony_ci goto out_iounmap; 19662306a36Sopenharmony_ci } 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci /* OBP option "d7s-flipped?" is honored as default for the 19962306a36Sopenharmony_ci * device, and reset default when detached 20062306a36Sopenharmony_ci */ 20162306a36Sopenharmony_ci regs = readb(p->regs); 20262306a36Sopenharmony_ci opts = of_find_node_by_path("/options"); 20362306a36Sopenharmony_ci if (opts) 20462306a36Sopenharmony_ci p->flipped = of_property_read_bool(opts, "d7s-flipped?"); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci if (p->flipped) 20762306a36Sopenharmony_ci regs |= D7S_FLIP; 20862306a36Sopenharmony_ci else 20962306a36Sopenharmony_ci regs &= ~D7S_FLIP; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci writeb(regs, p->regs); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci printk(KERN_INFO PFX "7-Segment Display%pOF at [%s:0x%llx] %s\n", 21462306a36Sopenharmony_ci op->dev.of_node, 21562306a36Sopenharmony_ci (regs & D7S_FLIP) ? " (FLIPPED)" : "", 21662306a36Sopenharmony_ci op->resource[0].start, 21762306a36Sopenharmony_ci sol_compat ? "in sol_compat mode" : ""); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci dev_set_drvdata(&op->dev, p); 22062306a36Sopenharmony_ci d7s_device = p; 22162306a36Sopenharmony_ci err = 0; 22262306a36Sopenharmony_ci of_node_put(opts); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ciout: 22562306a36Sopenharmony_ci return err; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ciout_iounmap: 22862306a36Sopenharmony_ci of_iounmap(&op->resource[0], p->regs, sizeof(u8)); 22962306a36Sopenharmony_ci goto out; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic int d7s_remove(struct platform_device *op) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci struct d7s *p = dev_get_drvdata(&op->dev); 23562306a36Sopenharmony_ci u8 regs = readb(p->regs); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci /* Honor OBP d7s-flipped? unless operating in solaris-compat mode */ 23862306a36Sopenharmony_ci if (sol_compat) { 23962306a36Sopenharmony_ci if (p->flipped) 24062306a36Sopenharmony_ci regs |= D7S_FLIP; 24162306a36Sopenharmony_ci else 24262306a36Sopenharmony_ci regs &= ~D7S_FLIP; 24362306a36Sopenharmony_ci writeb(regs, p->regs); 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci misc_deregister(&d7s_miscdev); 24762306a36Sopenharmony_ci of_iounmap(&op->resource[0], p->regs, sizeof(u8)); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci return 0; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cistatic const struct of_device_id d7s_match[] = { 25362306a36Sopenharmony_ci { 25462306a36Sopenharmony_ci .name = "display7seg", 25562306a36Sopenharmony_ci }, 25662306a36Sopenharmony_ci {}, 25762306a36Sopenharmony_ci}; 25862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, d7s_match); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_cistatic struct platform_driver d7s_driver = { 26162306a36Sopenharmony_ci .driver = { 26262306a36Sopenharmony_ci .name = DRIVER_NAME, 26362306a36Sopenharmony_ci .of_match_table = d7s_match, 26462306a36Sopenharmony_ci }, 26562306a36Sopenharmony_ci .probe = d7s_probe, 26662306a36Sopenharmony_ci .remove = d7s_remove, 26762306a36Sopenharmony_ci}; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_cimodule_platform_driver(d7s_driver); 270