display7seg.c 6.3 KB
Newer Older
1 2
/* display7seg.c - Driver implementation for the 7-segment display
 *                 present on Sun Microsystems CP1400 and CP1500
L
Linus Torvalds 已提交
3 4 5 6 7 8 9 10 11 12 13
 *
 * Copyright (c) 2000 Eric Brower (ebrower@usa.net)
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/major.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
14
#include <linux/ioport.h>		/* request_region */
15
#include <linux/slab.h>
16
#include <linux/mutex.h>
17 18
#include <linux/of.h>
#include <linux/of_device.h>
A
Arun Sharma 已提交
19
#include <linux/atomic.h>
L
Linus Torvalds 已提交
20
#include <asm/uaccess.h>		/* put_/get_user			*/
21
#include <asm/io.h>
L
Linus Torvalds 已提交
22 23 24 25

#include <asm/display7seg.h>

#define D7S_MINOR	193
26 27
#define DRIVER_NAME	"d7s"
#define PFX		DRIVER_NAME ": "
L
Linus Torvalds 已提交
28

29
static DEFINE_MUTEX(d7s_mutex);
L
Linus Torvalds 已提交
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
static int sol_compat = 0;		/* Solaris compatibility mode	*/

/* Solaris compatibility flag -
 * The Solaris implementation omits support for several
 * documented driver features (ref Sun doc 806-0180-03).  
 * By default, this module supports the documented driver 
 * abilities, rather than the Solaris implementation:
 *
 * 	1) Device ALWAYS reverts to OBP-specified FLIPPED mode
 * 	   upon closure of device or module unload.
 * 	2) Device ioctls D7SIOCRD/D7SIOCWR honor toggling of
 * 	   FLIP bit
 *
 * If you wish the device to operate as under Solaris,
 * omitting above features, set this parameter to non-zero.
 */
46 47 48 49 50 51
module_param(sol_compat, int, 0);
MODULE_PARM_DESC(sol_compat, 
		 "Disables documented functionality omitted from Solaris driver");

MODULE_AUTHOR("Eric Brower <ebrower@usa.net>");
MODULE_DESCRIPTION("7-Segment Display driver for Sun Microsystems CP1400/1500");
L
Linus Torvalds 已提交
52
MODULE_LICENSE("GPL");
53 54 55 56 57 58 59
MODULE_SUPPORTED_DEVICE("d7s");

struct d7s {
	void __iomem	*regs;
	bool		flipped;
};
struct d7s *d7s_device;
L
Linus Torvalds 已提交
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

/*
 * Register block address- see header for details
 * -----------------------------------------
 * | DP | ALARM | FLIP | 4 | 3 | 2 | 1 | 0 |
 * -----------------------------------------
 *
 * DP 		- Toggles decimal point on/off 
 * ALARM	- Toggles "Alarm" LED green/red
 * FLIP		- Inverts display for upside-down mounted board
 * bits 0-4	- 7-segment display contents
 */
static atomic_t d7s_users = ATOMIC_INIT(0);

static int d7s_open(struct inode *inode, struct file *f)
{
	if (D7S_MINOR != iminor(inode))
		return -ENODEV;
	atomic_inc(&d7s_users);
	return 0;
}

static int d7s_release(struct inode *inode, struct file *f)
{
	/* Reset flipped state to OBP default only if
	 * no other users have the device open and we
	 * are not operating in solaris-compat mode
	 */
	if (atomic_dec_and_test(&d7s_users) && !sol_compat) {
89 90 91 92 93 94 95 96 97
		struct d7s *p = d7s_device;
		u8 regval = 0;

		regval = readb(p->regs);
		if (p->flipped)
			regval |= D7S_FLIP;
		else
			regval &= ~D7S_FLIP;
		writeb(regval, p->regs);
L
Linus Torvalds 已提交
98 99 100 101 102
	}

	return 0;
}

103
static long d7s_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
104
{
105 106
	struct d7s *p = d7s_device;
	u8 regs = readb(p->regs);
A
Andrew Morton 已提交
107
	int error = 0;
108
	u8 ireg = 0;
L
Linus Torvalds 已提交
109

J
Josef Sipek 已提交
110
	if (D7S_MINOR != iminor(file->f_path.dentry->d_inode))
L
Linus Torvalds 已提交
111 112
		return -ENODEV;

113
	mutex_lock(&d7s_mutex);
L
Linus Torvalds 已提交
114 115
	switch (cmd) {
	case D7SIOCWR:
116 117
		/* assign device register values we mask-out D7S_FLIP
		 * if in sol_compat mode
L
Linus Torvalds 已提交
118
		 */
119 120 121 122
		if (get_user(ireg, (int __user *) arg)) {
			error = -EFAULT;
			break;
		}
123 124 125 126 127
		if (sol_compat) {
			if (regs & D7S_FLIP)
				ireg |= D7S_FLIP;
			else
				ireg &= ~D7S_FLIP;
L
Linus Torvalds 已提交
128
		}
129
		writeb(ireg, p->regs);
L
Linus Torvalds 已提交
130 131 132 133 134 135 136 137 138
		break;

	case D7SIOCRD:
		/* retrieve device register values
		 * NOTE: Solaris implementation returns D7S_FLIP bit
		 * as toggled by user, even though it does not honor it.
		 * This driver will not misinform you about the state
		 * of your hardware while in sol_compat mode
		 */
139 140 141 142
		if (put_user(regs, (int __user *) arg)) {
			error = -EFAULT;
			break;
		}
L
Linus Torvalds 已提交
143 144 145 146
		break;

	case D7SIOCTM:
		/* toggle device mode-- flip display orientation */
147 148 149 150 151
		if (regs & D7S_FLIP)
			regs &= ~D7S_FLIP;
		else
			regs |= D7S_FLIP;
		writeb(regs, p->regs);
L
Linus Torvalds 已提交
152 153
		break;
	};
154
	mutex_unlock(&d7s_mutex);
L
Linus Torvalds 已提交
155

156
	return error;
L
Linus Torvalds 已提交
157 158
}

159
static const struct file_operations d7s_fops = {
160 161 162 163 164
	.owner =		THIS_MODULE,
	.unlocked_ioctl =	d7s_ioctl,
	.compat_ioctl =		d7s_ioctl,
	.open =			d7s_open,
	.release =		d7s_release,
165
	.llseek = noop_llseek,
L
Linus Torvalds 已提交
166 167
};

168 169 170 171 172
static struct miscdevice d7s_miscdev = {
	.minor		= D7S_MINOR,
	.name		= DRIVER_NAME,
	.fops		= &d7s_fops
};
L
Linus Torvalds 已提交
173

174
static int __devinit d7s_probe(struct platform_device *op)
L
Linus Torvalds 已提交
175
{
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
	struct device_node *opts;
	int err = -EINVAL;
	struct d7s *p;
	u8 regs;

	if (d7s_device)
		goto out;

	p = kzalloc(sizeof(*p), GFP_KERNEL);
	err = -ENOMEM;
	if (!p)
		goto out;

	p->regs = of_ioremap(&op->resource[0], 0, sizeof(u8), "d7s");
	if (!p->regs) {
		printk(KERN_ERR PFX "Cannot map chip registers\n");
		goto out_free;
L
Linus Torvalds 已提交
193 194
	}

195 196 197 198 199
	err = misc_register(&d7s_miscdev);
	if (err) {
		printk(KERN_ERR PFX "Unable to acquire miscdevice minor %i\n",
		       D7S_MINOR);
		goto out_iounmap;
L
Linus Torvalds 已提交
200 201
	}

202 203
	/* OBP option "d7s-flipped?" is honored as default for the
	 * device, and reset default when detached
L
Linus Torvalds 已提交
204
	 */
205 206 207 208 209 210 211 212 213 214 215 216 217
	regs = readb(p->regs);
	opts = of_find_node_by_path("/options");
	if (opts &&
	    of_get_property(opts, "d7s-flipped?", NULL))
		p->flipped = true;

	if (p->flipped)
		regs |= D7S_FLIP;
	else
		regs &= ~D7S_FLIP;

	writeb(regs,  p->regs);

218
	printk(KERN_INFO PFX "7-Segment Display%s at [%s:0x%llx] %s\n",
219
	       op->dev.of_node->full_name,
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
	       (regs & D7S_FLIP) ? " (FLIPPED)" : "",
	       op->resource[0].start,
	       sol_compat ? "in sol_compat mode" : "");

	dev_set_drvdata(&op->dev, p);
	d7s_device = p;
	err = 0;

out:
	return err;

out_iounmap:
	of_iounmap(&op->resource[0], p->regs, sizeof(u8));

out_free:
	kfree(p);
	goto out;
L
Linus Torvalds 已提交
237 238
}

239
static int __devexit d7s_remove(struct platform_device *op)
L
Linus Torvalds 已提交
240
{
241 242
	struct d7s *p = dev_get_drvdata(&op->dev);
	u8 regs = readb(p->regs);
L
Linus Torvalds 已提交
243 244

	/* Honor OBP d7s-flipped? unless operating in solaris-compat mode */
245 246 247 248 249 250
	if (sol_compat) {
		if (p->flipped)
			regs |= D7S_FLIP;
		else
			regs &= ~D7S_FLIP;
		writeb(regs, p->regs);
L
Linus Torvalds 已提交
251 252 253
	}

	misc_deregister(&d7s_miscdev);
254 255 256 257 258 259
	of_iounmap(&op->resource[0], p->regs, sizeof(u8));
	kfree(p);

	return 0;
}

260
static const struct of_device_id d7s_match[] = {
261 262 263 264 265 266 267
	{
		.name = "display7seg",
	},
	{},
};
MODULE_DEVICE_TABLE(of, d7s_match);

268
static struct platform_driver d7s_driver = {
269 270 271 272 273
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = d7s_match,
	},
274 275 276 277
	.probe		= d7s_probe,
	.remove		= __devexit_p(d7s_remove),
};

278
module_platform_driver(d7s_driver);