linux/drivers/sbus/char/display7seg.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/* display7seg.c - Driver implementation for the 7-segment display
   3 *                 present on Sun Microsystems CP1400 and CP1500
   4 *
   5 * Copyright (c) 2000 Eric Brower (ebrower@usa.net)
   6 */
   7
   8#include <linux/device.h>
   9#include <linux/kernel.h>
  10#include <linux/module.h>
  11#include <linux/fs.h>
  12#include <linux/errno.h>
  13#include <linux/major.h>
  14#include <linux/miscdevice.h>
  15#include <linux/ioport.h>               /* request_region */
  16#include <linux/slab.h>
  17#include <linux/mutex.h>
  18#include <linux/of.h>
  19#include <linux/of_device.h>
  20#include <linux/atomic.h>
  21#include <linux/uaccess.h>              /* put_/get_user                        */
  22#include <asm/io.h>
  23
  24#include <asm/display7seg.h>
  25
  26#define DRIVER_NAME     "d7s"
  27#define PFX             DRIVER_NAME ": "
  28
  29static DEFINE_MUTEX(d7s_mutex);
  30static int sol_compat = 0;              /* Solaris compatibility mode   */
  31
  32/* Solaris compatibility flag -
  33 * The Solaris implementation omits support for several
  34 * documented driver features (ref Sun doc 806-0180-03).  
  35 * By default, this module supports the documented driver 
  36 * abilities, rather than the Solaris implementation:
  37 *
  38 *      1) Device ALWAYS reverts to OBP-specified FLIPPED mode
  39 *         upon closure of device or module unload.
  40 *      2) Device ioctls D7SIOCRD/D7SIOCWR honor toggling of
  41 *         FLIP bit
  42 *
  43 * If you wish the device to operate as under Solaris,
  44 * omitting above features, set this parameter to non-zero.
  45 */
  46module_param(sol_compat, int, 0);
  47MODULE_PARM_DESC(sol_compat, 
  48                 "Disables documented functionality omitted from Solaris driver");
  49
  50MODULE_AUTHOR("Eric Brower <ebrower@usa.net>");
  51MODULE_DESCRIPTION("7-Segment Display driver for Sun Microsystems CP1400/1500");
  52MODULE_LICENSE("GPL");
  53
  54struct d7s {
  55        void __iomem    *regs;
  56        bool            flipped;
  57};
  58struct d7s *d7s_device;
  59
  60/*
  61 * Register block address- see header for details
  62 * -----------------------------------------
  63 * | DP | ALARM | FLIP | 4 | 3 | 2 | 1 | 0 |
  64 * -----------------------------------------
  65 *
  66 * DP           - Toggles decimal point on/off 
  67 * ALARM        - Toggles "Alarm" LED green/red
  68 * FLIP         - Inverts display for upside-down mounted board
  69 * bits 0-4     - 7-segment display contents
  70 */
  71static atomic_t d7s_users = ATOMIC_INIT(0);
  72
  73static int d7s_open(struct inode *inode, struct file *f)
  74{
  75        if (D7S_MINOR != iminor(inode))
  76                return -ENODEV;
  77        atomic_inc(&d7s_users);
  78        return 0;
  79}
  80
  81static int d7s_release(struct inode *inode, struct file *f)
  82{
  83        /* Reset flipped state to OBP default only if
  84         * no other users have the device open and we
  85         * are not operating in solaris-compat mode
  86         */
  87        if (atomic_dec_and_test(&d7s_users) && !sol_compat) {
  88                struct d7s *p = d7s_device;
  89                u8 regval = 0;
  90
  91                regval = readb(p->regs);
  92                if (p->flipped)
  93                        regval |= D7S_FLIP;
  94                else
  95                        regval &= ~D7S_FLIP;
  96                writeb(regval, p->regs);
  97        }
  98
  99        return 0;
 100}
 101
 102static long d7s_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 103{
 104        struct d7s *p = d7s_device;
 105        u8 regs = readb(p->regs);
 106        int error = 0;
 107        u8 ireg = 0;
 108
 109        if (D7S_MINOR != iminor(file_inode(file)))
 110                return -ENODEV;
 111
 112        mutex_lock(&d7s_mutex);
 113        switch (cmd) {
 114        case D7SIOCWR:
 115                /* assign device register values we mask-out D7S_FLIP
 116                 * if in sol_compat mode
 117                 */
 118                if (get_user(ireg, (int __user *) arg)) {
 119                        error = -EFAULT;
 120                        break;
 121                }
 122                if (sol_compat) {
 123                        if (regs & D7S_FLIP)
 124                                ireg |= D7S_FLIP;
 125                        else
 126                                ireg &= ~D7S_FLIP;
 127                }
 128                writeb(ireg, p->regs);
 129                break;
 130
 131        case D7SIOCRD:
 132                /* retrieve device register values
 133                 * NOTE: Solaris implementation returns D7S_FLIP bit
 134                 * as toggled by user, even though it does not honor it.
 135                 * This driver will not misinform you about the state
 136                 * of your hardware while in sol_compat mode
 137                 */
 138                if (put_user(regs, (int __user *) arg)) {
 139                        error = -EFAULT;
 140                        break;
 141                }
 142                break;
 143
 144        case D7SIOCTM:
 145                /* toggle device mode-- flip display orientation */
 146                regs ^= D7S_FLIP;
 147                writeb(regs, p->regs);
 148                break;
 149        }
 150        mutex_unlock(&d7s_mutex);
 151
 152        return error;
 153}
 154
 155static const struct file_operations d7s_fops = {
 156        .owner =                THIS_MODULE,
 157        .unlocked_ioctl =       d7s_ioctl,
 158        .compat_ioctl =         compat_ptr_ioctl,
 159        .open =                 d7s_open,
 160        .release =              d7s_release,
 161        .llseek = noop_llseek,
 162};
 163
 164static struct miscdevice d7s_miscdev = {
 165        .minor          = D7S_MINOR,
 166        .name           = DRIVER_NAME,
 167        .fops           = &d7s_fops
 168};
 169
 170static int d7s_probe(struct platform_device *op)
 171{
 172        struct device_node *opts;
 173        int err = -EINVAL;
 174        struct d7s *p;
 175        u8 regs;
 176
 177        if (d7s_device)
 178                goto out;
 179
 180        p = devm_kzalloc(&op->dev, sizeof(*p), GFP_KERNEL);
 181        err = -ENOMEM;
 182        if (!p)
 183                goto out;
 184
 185        p->regs = of_ioremap(&op->resource[0], 0, sizeof(u8), "d7s");
 186        if (!p->regs) {
 187                printk(KERN_ERR PFX "Cannot map chip registers\n");
 188                goto out;
 189        }
 190
 191        err = misc_register(&d7s_miscdev);
 192        if (err) {
 193                printk(KERN_ERR PFX "Unable to acquire miscdevice minor %i\n",
 194                       D7S_MINOR);
 195                goto out_iounmap;
 196        }
 197
 198        /* OBP option "d7s-flipped?" is honored as default for the
 199         * device, and reset default when detached
 200         */
 201        regs = readb(p->regs);
 202        opts = of_find_node_by_path("/options");
 203        if (opts &&
 204            of_get_property(opts, "d7s-flipped?", NULL))
 205                p->flipped = true;
 206
 207        if (p->flipped)
 208                regs |= D7S_FLIP;
 209        else
 210                regs &= ~D7S_FLIP;
 211
 212        writeb(regs,  p->regs);
 213
 214        printk(KERN_INFO PFX "7-Segment Display%pOF at [%s:0x%llx] %s\n",
 215               op->dev.of_node,
 216               (regs & D7S_FLIP) ? " (FLIPPED)" : "",
 217               op->resource[0].start,
 218               sol_compat ? "in sol_compat mode" : "");
 219
 220        dev_set_drvdata(&op->dev, p);
 221        d7s_device = p;
 222        err = 0;
 223        of_node_put(opts);
 224
 225out:
 226        return err;
 227
 228out_iounmap:
 229        of_iounmap(&op->resource[0], p->regs, sizeof(u8));
 230        goto out;
 231}
 232
 233static int d7s_remove(struct platform_device *op)
 234{
 235        struct d7s *p = dev_get_drvdata(&op->dev);
 236        u8 regs = readb(p->regs);
 237
 238        /* Honor OBP d7s-flipped? unless operating in solaris-compat mode */
 239        if (sol_compat) {
 240                if (p->flipped)
 241                        regs |= D7S_FLIP;
 242                else
 243                        regs &= ~D7S_FLIP;
 244                writeb(regs, p->regs);
 245        }
 246
 247        misc_deregister(&d7s_miscdev);
 248        of_iounmap(&op->resource[0], p->regs, sizeof(u8));
 249
 250        return 0;
 251}
 252
 253static const struct of_device_id d7s_match[] = {
 254        {
 255                .name = "display7seg",
 256        },
 257        {},
 258};
 259MODULE_DEVICE_TABLE(of, d7s_match);
 260
 261static struct platform_driver d7s_driver = {
 262        .driver = {
 263                .name = DRIVER_NAME,
 264                .of_match_table = d7s_match,
 265        },
 266        .probe          = d7s_probe,
 267        .remove         = d7s_remove,
 268};
 269
 270module_platform_driver(d7s_driver);
 271