162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * /dev/lcd driver for Apple Network Servers.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/types.h>
762306a36Sopenharmony_ci#include <linux/errno.h>
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/miscdevice.h>
1062306a36Sopenharmony_ci#include <linux/fcntl.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/delay.h>
1362306a36Sopenharmony_ci#include <linux/fs.h>
1462306a36Sopenharmony_ci#include <linux/of.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/uaccess.h>
1762306a36Sopenharmony_ci#include <asm/sections.h>
1862306a36Sopenharmony_ci#include <asm/io.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include "ans-lcd.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define ANSLCD_ADDR		0xf301c000
2362306a36Sopenharmony_ci#define ANSLCD_CTRL_IX 0x00
2462306a36Sopenharmony_ci#define ANSLCD_DATA_IX 0x10
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic unsigned long anslcd_short_delay = 80;
2762306a36Sopenharmony_cistatic unsigned long anslcd_long_delay = 3280;
2862306a36Sopenharmony_cistatic volatile unsigned char __iomem *anslcd_ptr;
2962306a36Sopenharmony_cistatic DEFINE_MUTEX(anslcd_mutex);
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#undef DEBUG
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic void
3462306a36Sopenharmony_cianslcd_write_byte_ctrl ( unsigned char c )
3562306a36Sopenharmony_ci{
3662306a36Sopenharmony_ci#ifdef DEBUG
3762306a36Sopenharmony_ci	printk(KERN_DEBUG "LCD: CTRL byte: %02x\n",c);
3862306a36Sopenharmony_ci#endif
3962306a36Sopenharmony_ci	out_8(anslcd_ptr + ANSLCD_CTRL_IX, c);
4062306a36Sopenharmony_ci	switch(c) {
4162306a36Sopenharmony_ci		case 1:
4262306a36Sopenharmony_ci		case 2:
4362306a36Sopenharmony_ci		case 3:
4462306a36Sopenharmony_ci			udelay(anslcd_long_delay); break;
4562306a36Sopenharmony_ci		default: udelay(anslcd_short_delay);
4662306a36Sopenharmony_ci	}
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic void
5062306a36Sopenharmony_cianslcd_write_byte_data ( unsigned char c )
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	out_8(anslcd_ptr + ANSLCD_DATA_IX, c);
5362306a36Sopenharmony_ci	udelay(anslcd_short_delay);
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic ssize_t
5762306a36Sopenharmony_cianslcd_write( struct file * file, const char __user * buf,
5862306a36Sopenharmony_ci				size_t count, loff_t *ppos )
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	const char __user *p = buf;
6162306a36Sopenharmony_ci	int i;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci#ifdef DEBUG
6462306a36Sopenharmony_ci	printk(KERN_DEBUG "LCD: write\n");
6562306a36Sopenharmony_ci#endif
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	if (!access_ok(buf, count))
6862306a36Sopenharmony_ci		return -EFAULT;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	mutex_lock(&anslcd_mutex);
7162306a36Sopenharmony_ci	for ( i = *ppos; count > 0; ++i, ++p, --count )
7262306a36Sopenharmony_ci	{
7362306a36Sopenharmony_ci		char c;
7462306a36Sopenharmony_ci		__get_user(c, p);
7562306a36Sopenharmony_ci		anslcd_write_byte_data( c );
7662306a36Sopenharmony_ci	}
7762306a36Sopenharmony_ci	mutex_unlock(&anslcd_mutex);
7862306a36Sopenharmony_ci	*ppos = i;
7962306a36Sopenharmony_ci	return p - buf;
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic long
8362306a36Sopenharmony_cianslcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	char ch, __user *temp;
8662306a36Sopenharmony_ci	long ret = 0;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci#ifdef DEBUG
8962306a36Sopenharmony_ci	printk(KERN_DEBUG "LCD: ioctl(%d,%d)\n",cmd,arg);
9062306a36Sopenharmony_ci#endif
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	mutex_lock(&anslcd_mutex);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	switch ( cmd )
9562306a36Sopenharmony_ci	{
9662306a36Sopenharmony_ci	case ANSLCD_CLEAR:
9762306a36Sopenharmony_ci		anslcd_write_byte_ctrl ( 0x38 );
9862306a36Sopenharmony_ci		anslcd_write_byte_ctrl ( 0x0f );
9962306a36Sopenharmony_ci		anslcd_write_byte_ctrl ( 0x06 );
10062306a36Sopenharmony_ci		anslcd_write_byte_ctrl ( 0x01 );
10162306a36Sopenharmony_ci		anslcd_write_byte_ctrl ( 0x02 );
10262306a36Sopenharmony_ci		break;
10362306a36Sopenharmony_ci	case ANSLCD_SENDCTRL:
10462306a36Sopenharmony_ci		temp = (char __user *) arg;
10562306a36Sopenharmony_ci		__get_user(ch, temp);
10662306a36Sopenharmony_ci		for (; ch; temp++) { /* FIXME: This is ugly, but should work, as a \0 byte is not a valid command code */
10762306a36Sopenharmony_ci			anslcd_write_byte_ctrl ( ch );
10862306a36Sopenharmony_ci			__get_user(ch, temp);
10962306a36Sopenharmony_ci		}
11062306a36Sopenharmony_ci		break;
11162306a36Sopenharmony_ci	case ANSLCD_SETSHORTDELAY:
11262306a36Sopenharmony_ci		if (!capable(CAP_SYS_ADMIN))
11362306a36Sopenharmony_ci			ret =-EACCES;
11462306a36Sopenharmony_ci		else
11562306a36Sopenharmony_ci			anslcd_short_delay=arg;
11662306a36Sopenharmony_ci		break;
11762306a36Sopenharmony_ci	case ANSLCD_SETLONGDELAY:
11862306a36Sopenharmony_ci		if (!capable(CAP_SYS_ADMIN))
11962306a36Sopenharmony_ci			ret = -EACCES;
12062306a36Sopenharmony_ci		else
12162306a36Sopenharmony_ci			anslcd_long_delay=arg;
12262306a36Sopenharmony_ci		break;
12362306a36Sopenharmony_ci	default:
12462306a36Sopenharmony_ci		ret = -EINVAL;
12562306a36Sopenharmony_ci	}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	mutex_unlock(&anslcd_mutex);
12862306a36Sopenharmony_ci	return ret;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic int
13262306a36Sopenharmony_cianslcd_open( struct inode * inode, struct file * file )
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	return 0;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ciconst struct file_operations anslcd_fops = {
13862306a36Sopenharmony_ci	.write		= anslcd_write,
13962306a36Sopenharmony_ci	.unlocked_ioctl	= anslcd_ioctl,
14062306a36Sopenharmony_ci	.open		= anslcd_open,
14162306a36Sopenharmony_ci	.llseek		= default_llseek,
14262306a36Sopenharmony_ci};
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic struct miscdevice anslcd_dev = {
14562306a36Sopenharmony_ci	LCD_MINOR,
14662306a36Sopenharmony_ci	"anslcd",
14762306a36Sopenharmony_ci	&anslcd_fops
14862306a36Sopenharmony_ci};
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic const char anslcd_logo[] __initconst =
15162306a36Sopenharmony_ci				"********************"  /* Line #1 */
15262306a36Sopenharmony_ci				"*      LINUX!      *"  /* Line #3 */
15362306a36Sopenharmony_ci				"*    Welcome to    *"  /* Line #2 */
15462306a36Sopenharmony_ci				"********************"; /* Line #4 */
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cistatic int __init
15762306a36Sopenharmony_cianslcd_init(void)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	int a;
16062306a36Sopenharmony_ci	int retval;
16162306a36Sopenharmony_ci	struct device_node* node;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	node = of_find_node_by_name(NULL, "lcd");
16462306a36Sopenharmony_ci	if (!node || !of_node_name_eq(node->parent, "gc")) {
16562306a36Sopenharmony_ci		of_node_put(node);
16662306a36Sopenharmony_ci		return -ENODEV;
16762306a36Sopenharmony_ci	}
16862306a36Sopenharmony_ci	of_node_put(node);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	anslcd_ptr = ioremap(ANSLCD_ADDR, 0x20);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	retval = misc_register(&anslcd_dev);
17362306a36Sopenharmony_ci	if(retval < 0){
17462306a36Sopenharmony_ci		printk(KERN_INFO "LCD: misc_register failed\n");
17562306a36Sopenharmony_ci		iounmap(anslcd_ptr);
17662306a36Sopenharmony_ci		return retval;
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci#ifdef DEBUG
18062306a36Sopenharmony_ci	printk(KERN_DEBUG "LCD: init\n");
18162306a36Sopenharmony_ci#endif
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	mutex_lock(&anslcd_mutex);
18462306a36Sopenharmony_ci	anslcd_write_byte_ctrl ( 0x38 );
18562306a36Sopenharmony_ci	anslcd_write_byte_ctrl ( 0x0c );
18662306a36Sopenharmony_ci	anslcd_write_byte_ctrl ( 0x06 );
18762306a36Sopenharmony_ci	anslcd_write_byte_ctrl ( 0x01 );
18862306a36Sopenharmony_ci	anslcd_write_byte_ctrl ( 0x02 );
18962306a36Sopenharmony_ci	for(a=0;a<80;a++) {
19062306a36Sopenharmony_ci		anslcd_write_byte_data(anslcd_logo[a]);
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci	mutex_unlock(&anslcd_mutex);
19362306a36Sopenharmony_ci	return 0;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic void __exit
19762306a36Sopenharmony_cianslcd_exit(void)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	misc_deregister(&anslcd_dev);
20062306a36Sopenharmony_ci	iounmap(anslcd_ptr);
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_cimodule_init(anslcd_init);
20462306a36Sopenharmony_cimodule_exit(anslcd_exit);
20562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
206