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/smp_lock.h>
  16#include <linux/of.h>
  17#include <linux/of_device.h>
  18#include <asm/atomic.h>
  19#include <asm/uaccess.h>                /* put_/get_user                        */
  20#include <asm/io.h>
  21
  22#include <asm/display7seg.h>
  23
  24#define D7S_MINOR       193
  25#define DRIVER_NAME     "d7s"
  26#define PFX             DRIVER_NAME ": "
  27
  28static int sol_compat = 0;              /* Solaris compatibility mode   */
  29
  30/* Solaris compatibility flag -
  31 * The Solaris implementation omits support for several
  32 * documented driver features (ref Sun doc 806-0180-03).  
  33 * By default, this module supports the documented driver 
  34 * abilities, rather than the Solaris implementation:
  35 *
  36 *      1) Device ALWAYS reverts to OBP-specified FLIPPED mode
  37 *         upon closure of device or module unload.
  38 *      2) Device ioctls D7SIOCRD/D7SIOCWR honor toggling of
  39 *         FLIP bit
  40 *
  41 * If you wish the device to operate as under Solaris,
  42 * omitting above features, set this parameter to non-zero.
  43 */
  44module_param(sol_compat, int, 0);
  45MODULE_PARM_DESC(sol_compat, 
  46                 "Disables documented functionality omitted from Solaris driver");
  47
  48MODULE_AUTHOR("Eric Brower <ebrower@usa.net>");
  49MODULE_DESCRIPTION("7-Segment Display driver for Sun Microsystems CP1400/1500");
  50MODULE_LICENSE("GPL");
  51MODULE_SUPPORTED_DEVICE("d7s");
  52
  53struct d7s {
  54        void __iomem    *regs;
  55        bool            flipped;
  56};
  57struct d7s *d7s_device;
  58
  59/*
  60 * Register block address- see header for details
  61 * -----------------------------------------
  62 * | DP | ALARM | FLIP | 4 | 3 | 2 | 1 | 0 |
  63 * -----------------------------------------
  64 *
  65 * DP           - Toggles decimal point on/off 
  66 * ALARM        - Toggles "Alarm" LED green/red
  67 * FLIP         - Inverts display for upside-down mounted board
  68 * bits 0-4     - 7-segment display contents
  69 */
  70static atomic_t d7s_users = ATOMIC_INIT(0);
  71
  72static int d7s_open(struct inode *inode, struct file *f)
  73{
  74        if (D7S_MINOR != iminor(inode))
  75                return -ENODEV;
  76        cycle_kernel_lock();
  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->f_path.dentry->d_inode))
 110                return -ENODEV;
 111
 112        lock_kernel();
 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                if (regs & D7S_FLIP)
 147                        regs &= ~D7S_FLIP;
 148                else
 149                        regs |= D7S_FLIP;
 150                writeb(regs, p->regs);
 151                break;
 152        };
 153        unlock_kernel();
 154
 155        return error;
 156}
 157
 158static const struct file_operations d7s_fops = {
 159        .owner =                THIS_MODULE,
 160        .unlocked_ioctl =       d7s_ioctl,
 161        .compat_ioctl =         d7s_ioctl,
 162        .open =                 d7s_open,
 163        .release =              d7s_release,
 164};
 165
 166static struct miscdevice d7s_miscdev = {
 167        .minor          = D7S_MINOR,
 168        .name           = DRIVER_NAME,
 169        .fops           = &d7s_fops
 170};
 171
 172static int __devinit d7s_probe(struct of_device *op,
 173                               const struct of_device_id *match)
 174{
 175        struct device_node *opts;
 176        int err = -EINVAL;
 177        struct d7s *p;
 178        u8 regs;
 179
 180        if (d7s_device)
 181                goto out;
 182
 183        p = kzalloc(sizeof(*p), GFP_KERNEL);
 184        err = -ENOMEM;
 185        if (!p)
 186                goto out;
 187
 188        p->regs = of_ioremap(&op->resource[0], 0, sizeof(u8), "d7s");
 189        if (!p->regs) {
 190                printk(KERN_ERR PFX "Cannot map chip registers\n");
 191                goto out_free;
 192        }
 193
 194        err = misc_register(&d7s_miscdev);
 195        if (err) {
 196                printk(KERN_ERR PFX "Unable to acquire miscdevice minor %i\n",
 197                       D7S_MINOR);
 198                goto out_iounmap;
 199        }
 200
 201        /* OBP option "d7s-flipped?" is honored as default for the
 202         * device, and reset default when detached
 203         */
 204        regs = readb(p->regs);
 205        opts = of_find_node_by_path("/options");
 206        if (opts &&
 207            of_get_property(opts, "d7s-flipped?", NULL))
 208                p->flipped = true;
 209
 210        if (p->flipped)
 211                regs |= D7S_FLIP;
 212        else
 213                regs &= ~D7S_FLIP;
 214
 215        writeb(regs,  p->regs);
 216
 217        printk(KERN_INFO PFX "7-Segment Display%s at [%s:0x%llx] %s\n",
 218               op->node->full_name,
 219               (regs & D7S_FLIP) ? " (FLIPPED)" : "",
 220               op->resource[0].start,
 221               sol_compat ? "in sol_compat mode" : "");
 222
 223        dev_set_drvdata(&op->dev, p);
 224        d7s_device = p;
 225        err = 0;
 226
 227out:
 228        return err;
 229
 230out_iounmap:
 231        of_iounmap(&op->resource[0], p->regs, sizeof(u8));
 232
 233out_free:
 234        kfree(p);
 235        goto out;
 236}
 237
 238static int __devexit d7s_remove(struct of_device *op)
 239{
 240        struct d7s *p = dev_get_drvdata(&op->dev);
 241        u8 regs = readb(p->regs);
 242
 243        /* Honor OBP d7s-flipped? unless operating in solaris-compat mode */
 244        if (sol_compat) {
 245                if (p->flipped)
 246                        regs |= D7S_FLIP;
 247                else
 248                        regs &= ~D7S_FLIP;
 249                writeb(regs, p->regs);
 250        }
 251
 252        misc_deregister(&d7s_miscdev);
 253        of_iounmap(&op->resource[0], p->regs, sizeof(u8));
 254        kfree(p);
 255
 256        return 0;
 257}
 258
 259static const struct of_device_id d7s_match[] = {
 260        {
 261                .name = "display7seg",
 262        },
 263        {},
 264};
 265MODULE_DEVICE_TABLE(of, d7s_match);
 266
 267static struct of_platform_driver d7s_driver = {
 268        .name           = DRIVER_NAME,
 269        .match_table    = d7s_match,
 270        .probe          = d7s_probe,
 271        .remove         = __devexit_p(d7s_remove),
 272};
 273
 274static int __init d7s_init(void)
 275{
 276        return of_register_driver(&d7s_driver, &of_bus_type);
 277}
 278
 279static void __exit d7s_exit(void)
 280{
 281        of_unregister_driver(&d7s_driver);
 282}
 283
 284module_init(d7s_init);
 285module_exit(d7s_exit);
 286