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