162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* uctrl.c: TS102 Microcontroller interface on Tadpole Sparcbook 3
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright 1999 Derrick J Brashear (shadow@dementia.org)
562306a36Sopenharmony_ci * Copyright 2008 David S. Miller (davem@davemloft.net)
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/errno.h>
1062306a36Sopenharmony_ci#include <linux/delay.h>
1162306a36Sopenharmony_ci#include <linux/interrupt.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <linux/mutex.h>
1462306a36Sopenharmony_ci#include <linux/ioport.h>
1562306a36Sopenharmony_ci#include <linux/miscdevice.h>
1662306a36Sopenharmony_ci#include <linux/mm.h>
1762306a36Sopenharmony_ci#include <linux/of.h>
1862306a36Sopenharmony_ci#include <linux/platform_device.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include <asm/openprom.h>
2162306a36Sopenharmony_ci#include <asm/oplib.h>
2262306a36Sopenharmony_ci#include <asm/irq.h>
2362306a36Sopenharmony_ci#include <asm/io.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define DEBUG 1
2662306a36Sopenharmony_ci#ifdef DEBUG
2762306a36Sopenharmony_ci#define dprintk(x) printk x
2862306a36Sopenharmony_ci#else
2962306a36Sopenharmony_ci#define dprintk(x)
3062306a36Sopenharmony_ci#endif
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistruct uctrl_regs {
3362306a36Sopenharmony_ci	u32 uctrl_intr;
3462306a36Sopenharmony_ci	u32 uctrl_data;
3562306a36Sopenharmony_ci	u32 uctrl_stat;
3662306a36Sopenharmony_ci	u32 uctrl_xxx[5];
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistruct ts102_regs {
4062306a36Sopenharmony_ci	u32 card_a_intr;
4162306a36Sopenharmony_ci	u32 card_a_stat;
4262306a36Sopenharmony_ci	u32 card_a_ctrl;
4362306a36Sopenharmony_ci	u32 card_a_xxx;
4462306a36Sopenharmony_ci	u32 card_b_intr;
4562306a36Sopenharmony_ci	u32 card_b_stat;
4662306a36Sopenharmony_ci	u32 card_b_ctrl;
4762306a36Sopenharmony_ci	u32 card_b_xxx;
4862306a36Sopenharmony_ci	u32 uctrl_intr;
4962306a36Sopenharmony_ci	u32 uctrl_data;
5062306a36Sopenharmony_ci	u32 uctrl_stat;
5162306a36Sopenharmony_ci	u32 uctrl_xxx;
5262306a36Sopenharmony_ci	u32 ts102_xxx[4];
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci/* Bits for uctrl_intr register */
5662306a36Sopenharmony_ci#define UCTRL_INTR_TXE_REQ         0x01    /* transmit FIFO empty int req */
5762306a36Sopenharmony_ci#define UCTRL_INTR_TXNF_REQ        0x02    /* transmit FIFO not full int req */
5862306a36Sopenharmony_ci#define UCTRL_INTR_RXNE_REQ        0x04    /* receive FIFO not empty int req */
5962306a36Sopenharmony_ci#define UCTRL_INTR_RXO_REQ         0x08    /* receive FIFO overflow int req */
6062306a36Sopenharmony_ci#define UCTRL_INTR_TXE_MSK         0x10    /* transmit FIFO empty mask */
6162306a36Sopenharmony_ci#define UCTRL_INTR_TXNF_MSK        0x20    /* transmit FIFO not full mask */
6262306a36Sopenharmony_ci#define UCTRL_INTR_RXNE_MSK        0x40    /* receive FIFO not empty mask */
6362306a36Sopenharmony_ci#define UCTRL_INTR_RXO_MSK         0x80    /* receive FIFO overflow mask */
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci/* Bits for uctrl_stat register */
6662306a36Sopenharmony_ci#define UCTRL_STAT_TXE_STA         0x01    /* transmit FIFO empty status */
6762306a36Sopenharmony_ci#define UCTRL_STAT_TXNF_STA        0x02    /* transmit FIFO not full status */
6862306a36Sopenharmony_ci#define UCTRL_STAT_RXNE_STA        0x04    /* receive FIFO not empty status */
6962306a36Sopenharmony_ci#define UCTRL_STAT_RXO_STA         0x08    /* receive FIFO overflow status */
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic DEFINE_MUTEX(uctrl_mutex);
7262306a36Sopenharmony_cistatic const char *uctrl_extstatus[16] = {
7362306a36Sopenharmony_ci        "main power available",
7462306a36Sopenharmony_ci        "internal battery attached",
7562306a36Sopenharmony_ci        "external battery attached",
7662306a36Sopenharmony_ci        "external VGA attached",
7762306a36Sopenharmony_ci        "external keyboard attached",
7862306a36Sopenharmony_ci        "external mouse attached",
7962306a36Sopenharmony_ci        "lid down",
8062306a36Sopenharmony_ci        "internal battery currently charging",
8162306a36Sopenharmony_ci        "external battery currently charging",
8262306a36Sopenharmony_ci        "internal battery currently discharging",
8362306a36Sopenharmony_ci        "external battery currently discharging",
8462306a36Sopenharmony_ci};
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci/* Everything required for one transaction with the uctrl */
8762306a36Sopenharmony_cistruct uctrl_txn {
8862306a36Sopenharmony_ci	u8 opcode;
8962306a36Sopenharmony_ci	u8 inbits;
9062306a36Sopenharmony_ci	u8 outbits;
9162306a36Sopenharmony_ci	u8 *inbuf;
9262306a36Sopenharmony_ci	u8 *outbuf;
9362306a36Sopenharmony_ci};
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistruct uctrl_status {
9662306a36Sopenharmony_ci	u8 current_temp; /* 0x07 */
9762306a36Sopenharmony_ci	u8 reset_status; /* 0x0b */
9862306a36Sopenharmony_ci	u16 event_status; /* 0x0c */
9962306a36Sopenharmony_ci	u16 error_status; /* 0x10 */
10062306a36Sopenharmony_ci	u16 external_status; /* 0x11, 0x1b */
10162306a36Sopenharmony_ci	u8 internal_charge; /* 0x18 */
10262306a36Sopenharmony_ci	u8 external_charge; /* 0x19 */
10362306a36Sopenharmony_ci	u16 control_lcd; /* 0x20 */
10462306a36Sopenharmony_ci	u8 control_bitport; /* 0x21 */
10562306a36Sopenharmony_ci	u8 speaker_volume; /* 0x23 */
10662306a36Sopenharmony_ci	u8 control_tft_brightness; /* 0x24 */
10762306a36Sopenharmony_ci	u8 control_kbd_repeat_delay; /* 0x28 */
10862306a36Sopenharmony_ci	u8 control_kbd_repeat_period; /* 0x29 */
10962306a36Sopenharmony_ci	u8 control_screen_contrast; /* 0x2F */
11062306a36Sopenharmony_ci};
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cienum uctrl_opcode {
11362306a36Sopenharmony_ci  READ_SERIAL_NUMBER=0x1,
11462306a36Sopenharmony_ci  READ_ETHERNET_ADDRESS=0x2,
11562306a36Sopenharmony_ci  READ_HARDWARE_VERSION=0x3,
11662306a36Sopenharmony_ci  READ_MICROCONTROLLER_VERSION=0x4,
11762306a36Sopenharmony_ci  READ_MAX_TEMPERATURE=0x5,
11862306a36Sopenharmony_ci  READ_MIN_TEMPERATURE=0x6,
11962306a36Sopenharmony_ci  READ_CURRENT_TEMPERATURE=0x7,
12062306a36Sopenharmony_ci  READ_SYSTEM_VARIANT=0x8,
12162306a36Sopenharmony_ci  READ_POWERON_CYCLES=0x9,
12262306a36Sopenharmony_ci  READ_POWERON_SECONDS=0xA,
12362306a36Sopenharmony_ci  READ_RESET_STATUS=0xB,
12462306a36Sopenharmony_ci  READ_EVENT_STATUS=0xC,
12562306a36Sopenharmony_ci  READ_REAL_TIME_CLOCK=0xD,
12662306a36Sopenharmony_ci  READ_EXTERNAL_VGA_PORT=0xE,
12762306a36Sopenharmony_ci  READ_MICROCONTROLLER_ROM_CHECKSUM=0xF,
12862306a36Sopenharmony_ci  READ_ERROR_STATUS=0x10,
12962306a36Sopenharmony_ci  READ_EXTERNAL_STATUS=0x11,
13062306a36Sopenharmony_ci  READ_USER_CONFIGURATION_AREA=0x12,
13162306a36Sopenharmony_ci  READ_MICROCONTROLLER_VOLTAGE=0x13,
13262306a36Sopenharmony_ci  READ_INTERNAL_BATTERY_VOLTAGE=0x14,
13362306a36Sopenharmony_ci  READ_DCIN_VOLTAGE=0x15,
13462306a36Sopenharmony_ci  READ_HORIZONTAL_POINTER_VOLTAGE=0x16,
13562306a36Sopenharmony_ci  READ_VERTICAL_POINTER_VOLTAGE=0x17,
13662306a36Sopenharmony_ci  READ_INTERNAL_BATTERY_CHARGE_LEVEL=0x18,
13762306a36Sopenharmony_ci  READ_EXTERNAL_BATTERY_CHARGE_LEVEL=0x19,
13862306a36Sopenharmony_ci  READ_REAL_TIME_CLOCK_ALARM=0x1A,
13962306a36Sopenharmony_ci  READ_EVENT_STATUS_NO_RESET=0x1B,
14062306a36Sopenharmony_ci  READ_INTERNAL_KEYBOARD_LAYOUT=0x1C,
14162306a36Sopenharmony_ci  READ_EXTERNAL_KEYBOARD_LAYOUT=0x1D,
14262306a36Sopenharmony_ci  READ_EEPROM_STATUS=0x1E,
14362306a36Sopenharmony_ci  CONTROL_LCD=0x20,
14462306a36Sopenharmony_ci  CONTROL_BITPORT=0x21,
14562306a36Sopenharmony_ci  SPEAKER_VOLUME=0x23,
14662306a36Sopenharmony_ci  CONTROL_TFT_BRIGHTNESS=0x24,
14762306a36Sopenharmony_ci  CONTROL_WATCHDOG=0x25,
14862306a36Sopenharmony_ci  CONTROL_FACTORY_EEPROM_AREA=0x26,
14962306a36Sopenharmony_ci  CONTROL_KBD_TIME_UNTIL_REPEAT=0x28,
15062306a36Sopenharmony_ci  CONTROL_KBD_TIME_BETWEEN_REPEATS=0x29,
15162306a36Sopenharmony_ci  CONTROL_TIMEZONE=0x2A,
15262306a36Sopenharmony_ci  CONTROL_MARK_SPACE_RATIO=0x2B,
15362306a36Sopenharmony_ci  CONTROL_DIAGNOSTIC_MODE=0x2E,
15462306a36Sopenharmony_ci  CONTROL_SCREEN_CONTRAST=0x2F,
15562306a36Sopenharmony_ci  RING_BELL=0x30,
15662306a36Sopenharmony_ci  SET_DIAGNOSTIC_STATUS=0x32,
15762306a36Sopenharmony_ci  CLEAR_KEY_COMBINATION_TABLE=0x33,
15862306a36Sopenharmony_ci  PERFORM_SOFTWARE_RESET=0x34,
15962306a36Sopenharmony_ci  SET_REAL_TIME_CLOCK=0x35,
16062306a36Sopenharmony_ci  RECALIBRATE_POINTING_STICK=0x36,
16162306a36Sopenharmony_ci  SET_BELL_FREQUENCY=0x37,
16262306a36Sopenharmony_ci  SET_INTERNAL_BATTERY_CHARGE_RATE=0x39,
16362306a36Sopenharmony_ci  SET_EXTERNAL_BATTERY_CHARGE_RATE=0x3A,
16462306a36Sopenharmony_ci  SET_REAL_TIME_CLOCK_ALARM=0x3B,
16562306a36Sopenharmony_ci  READ_EEPROM=0x40,
16662306a36Sopenharmony_ci  WRITE_EEPROM=0x41,
16762306a36Sopenharmony_ci  WRITE_TO_STATUS_DISPLAY=0x42,
16862306a36Sopenharmony_ci  DEFINE_SPECIAL_CHARACTER=0x43,
16962306a36Sopenharmony_ci  DEFINE_KEY_COMBINATION_ENTRY=0x50,
17062306a36Sopenharmony_ci  DEFINE_STRING_TABLE_ENTRY=0x51,
17162306a36Sopenharmony_ci  DEFINE_STATUS_SCREEN_DISPLAY=0x52,
17262306a36Sopenharmony_ci  PERFORM_EMU_COMMANDS=0x64,
17362306a36Sopenharmony_ci  READ_EMU_REGISTER=0x65,
17462306a36Sopenharmony_ci  WRITE_EMU_REGISTER=0x66,
17562306a36Sopenharmony_ci  READ_EMU_RAM=0x67,
17662306a36Sopenharmony_ci  WRITE_EMU_RAM=0x68,
17762306a36Sopenharmony_ci  READ_BQ_REGISTER=0x69,
17862306a36Sopenharmony_ci  WRITE_BQ_REGISTER=0x6A,
17962306a36Sopenharmony_ci  SET_USER_PASSWORD=0x70,
18062306a36Sopenharmony_ci  VERIFY_USER_PASSWORD=0x71,
18162306a36Sopenharmony_ci  GET_SYSTEM_PASSWORD_KEY=0x72,
18262306a36Sopenharmony_ci  VERIFY_SYSTEM_PASSWORD=0x73,
18362306a36Sopenharmony_ci  POWER_OFF=0x82,
18462306a36Sopenharmony_ci  POWER_RESTART=0x83,
18562306a36Sopenharmony_ci};
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cistatic struct uctrl_driver {
18862306a36Sopenharmony_ci	struct uctrl_regs __iomem *regs;
18962306a36Sopenharmony_ci	int irq;
19062306a36Sopenharmony_ci	int pending;
19162306a36Sopenharmony_ci	struct uctrl_status status;
19262306a36Sopenharmony_ci} *global_driver;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_cistatic void uctrl_get_event_status(struct uctrl_driver *);
19562306a36Sopenharmony_cistatic void uctrl_get_external_status(struct uctrl_driver *);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic long
19862306a36Sopenharmony_ciuctrl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	switch (cmd) {
20162306a36Sopenharmony_ci		default:
20262306a36Sopenharmony_ci			return -EINVAL;
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci	return 0;
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_cistatic int
20862306a36Sopenharmony_ciuctrl_open(struct inode *inode, struct file *file)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	mutex_lock(&uctrl_mutex);
21162306a36Sopenharmony_ci	uctrl_get_event_status(global_driver);
21262306a36Sopenharmony_ci	uctrl_get_external_status(global_driver);
21362306a36Sopenharmony_ci	mutex_unlock(&uctrl_mutex);
21462306a36Sopenharmony_ci	return 0;
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic irqreturn_t uctrl_interrupt(int irq, void *dev_id)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	return IRQ_HANDLED;
22062306a36Sopenharmony_ci}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cistatic const struct file_operations uctrl_fops = {
22362306a36Sopenharmony_ci	.owner =	THIS_MODULE,
22462306a36Sopenharmony_ci	.llseek =	no_llseek,
22562306a36Sopenharmony_ci	.unlocked_ioctl =	uctrl_ioctl,
22662306a36Sopenharmony_ci	.open =		uctrl_open,
22762306a36Sopenharmony_ci};
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic struct miscdevice uctrl_dev = {
23062306a36Sopenharmony_ci	UCTRL_MINOR,
23162306a36Sopenharmony_ci	"uctrl",
23262306a36Sopenharmony_ci	&uctrl_fops
23362306a36Sopenharmony_ci};
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci/* Wait for space to write, then write to it */
23662306a36Sopenharmony_ci#define WRITEUCTLDATA(value) \
23762306a36Sopenharmony_ci{ \
23862306a36Sopenharmony_ci  unsigned int i; \
23962306a36Sopenharmony_ci  for (i = 0; i < 10000; i++) { \
24062306a36Sopenharmony_ci      if (UCTRL_STAT_TXNF_STA & sbus_readl(&driver->regs->uctrl_stat)) \
24162306a36Sopenharmony_ci      break; \
24262306a36Sopenharmony_ci  } \
24362306a36Sopenharmony_ci  dprintk(("write data 0x%02x\n", value)); \
24462306a36Sopenharmony_ci  sbus_writel(value, &driver->regs->uctrl_data); \
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci/* Wait for something to read, read it, then clear the bit */
24862306a36Sopenharmony_ci#define READUCTLDATA(value) \
24962306a36Sopenharmony_ci{ \
25062306a36Sopenharmony_ci  unsigned int i; \
25162306a36Sopenharmony_ci  value = 0; \
25262306a36Sopenharmony_ci  for (i = 0; i < 10000; i++) { \
25362306a36Sopenharmony_ci      if ((UCTRL_STAT_RXNE_STA & sbus_readl(&driver->regs->uctrl_stat)) == 0) \
25462306a36Sopenharmony_ci      break; \
25562306a36Sopenharmony_ci    udelay(1); \
25662306a36Sopenharmony_ci  } \
25762306a36Sopenharmony_ci  value = sbus_readl(&driver->regs->uctrl_data); \
25862306a36Sopenharmony_ci  dprintk(("read data 0x%02x\n", value)); \
25962306a36Sopenharmony_ci  sbus_writel(UCTRL_STAT_RXNE_STA, &driver->regs->uctrl_stat); \
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_cistatic void uctrl_do_txn(struct uctrl_driver *driver, struct uctrl_txn *txn)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	int stat, incnt, outcnt, bytecnt, intr;
26562306a36Sopenharmony_ci	u32 byte;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	stat = sbus_readl(&driver->regs->uctrl_stat);
26862306a36Sopenharmony_ci	intr = sbus_readl(&driver->regs->uctrl_intr);
26962306a36Sopenharmony_ci	sbus_writel(stat, &driver->regs->uctrl_stat);
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	dprintk(("interrupt stat 0x%x int 0x%x\n", stat, intr));
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	incnt = txn->inbits;
27462306a36Sopenharmony_ci	outcnt = txn->outbits;
27562306a36Sopenharmony_ci	byte = (txn->opcode << 8);
27662306a36Sopenharmony_ci	WRITEUCTLDATA(byte);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	bytecnt = 0;
27962306a36Sopenharmony_ci	while (incnt > 0) {
28062306a36Sopenharmony_ci		byte = (txn->inbuf[bytecnt] << 8);
28162306a36Sopenharmony_ci		WRITEUCTLDATA(byte);
28262306a36Sopenharmony_ci		incnt--;
28362306a36Sopenharmony_ci		bytecnt++;
28462306a36Sopenharmony_ci	}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	/* Get the ack */
28762306a36Sopenharmony_ci	READUCTLDATA(byte);
28862306a36Sopenharmony_ci	dprintk(("ack was %x\n", (byte >> 8)));
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	bytecnt = 0;
29162306a36Sopenharmony_ci	while (outcnt > 0) {
29262306a36Sopenharmony_ci		READUCTLDATA(byte);
29362306a36Sopenharmony_ci		txn->outbuf[bytecnt] = (byte >> 8);
29462306a36Sopenharmony_ci		dprintk(("set byte to %02x\n", byte));
29562306a36Sopenharmony_ci		outcnt--;
29662306a36Sopenharmony_ci		bytecnt++;
29762306a36Sopenharmony_ci	}
29862306a36Sopenharmony_ci}
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_cistatic void uctrl_get_event_status(struct uctrl_driver *driver)
30162306a36Sopenharmony_ci{
30262306a36Sopenharmony_ci	struct uctrl_txn txn;
30362306a36Sopenharmony_ci	u8 outbits[2];
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	txn.opcode = READ_EVENT_STATUS;
30662306a36Sopenharmony_ci	txn.inbits = 0;
30762306a36Sopenharmony_ci	txn.outbits = 2;
30862306a36Sopenharmony_ci	txn.inbuf = NULL;
30962306a36Sopenharmony_ci	txn.outbuf = outbits;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	uctrl_do_txn(driver, &txn);
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff)));
31462306a36Sopenharmony_ci	driver->status.event_status =
31562306a36Sopenharmony_ci		((outbits[0] & 0xff) << 8) | (outbits[1] & 0xff);
31662306a36Sopenharmony_ci	dprintk(("ev is %x\n", driver->status.event_status));
31762306a36Sopenharmony_ci}
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_cistatic void uctrl_get_external_status(struct uctrl_driver *driver)
32062306a36Sopenharmony_ci{
32162306a36Sopenharmony_ci	struct uctrl_txn txn;
32262306a36Sopenharmony_ci	u8 outbits[2];
32362306a36Sopenharmony_ci	int i, v;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	txn.opcode = READ_EXTERNAL_STATUS;
32662306a36Sopenharmony_ci	txn.inbits = 0;
32762306a36Sopenharmony_ci	txn.outbits = 2;
32862306a36Sopenharmony_ci	txn.inbuf = NULL;
32962306a36Sopenharmony_ci	txn.outbuf = outbits;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	uctrl_do_txn(driver, &txn);
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff)));
33462306a36Sopenharmony_ci	driver->status.external_status =
33562306a36Sopenharmony_ci		((outbits[0] * 256) + (outbits[1]));
33662306a36Sopenharmony_ci	dprintk(("ex is %x\n", driver->status.external_status));
33762306a36Sopenharmony_ci	v = driver->status.external_status;
33862306a36Sopenharmony_ci	for (i = 0; v != 0; i++, v >>= 1) {
33962306a36Sopenharmony_ci		if (v & 1) {
34062306a36Sopenharmony_ci			dprintk(("%s%s", " ", uctrl_extstatus[i]));
34162306a36Sopenharmony_ci		}
34262306a36Sopenharmony_ci	}
34362306a36Sopenharmony_ci	dprintk(("\n"));
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic int uctrl_probe(struct platform_device *op)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci	struct uctrl_driver *p;
35062306a36Sopenharmony_ci	int err = -ENOMEM;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	p = kzalloc(sizeof(*p), GFP_KERNEL);
35362306a36Sopenharmony_ci	if (!p) {
35462306a36Sopenharmony_ci		printk(KERN_ERR "uctrl: Unable to allocate device struct.\n");
35562306a36Sopenharmony_ci		goto out;
35662306a36Sopenharmony_ci	}
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	p->regs = of_ioremap(&op->resource[0], 0,
35962306a36Sopenharmony_ci			     resource_size(&op->resource[0]),
36062306a36Sopenharmony_ci			     "uctrl");
36162306a36Sopenharmony_ci	if (!p->regs) {
36262306a36Sopenharmony_ci		printk(KERN_ERR "uctrl: Unable to map registers.\n");
36362306a36Sopenharmony_ci		goto out_free;
36462306a36Sopenharmony_ci	}
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	p->irq = op->archdata.irqs[0];
36762306a36Sopenharmony_ci	err = request_irq(p->irq, uctrl_interrupt, 0, "uctrl", p);
36862306a36Sopenharmony_ci	if (err) {
36962306a36Sopenharmony_ci		printk(KERN_ERR "uctrl: Unable to register irq.\n");
37062306a36Sopenharmony_ci		goto out_iounmap;
37162306a36Sopenharmony_ci	}
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	err = misc_register(&uctrl_dev);
37462306a36Sopenharmony_ci	if (err) {
37562306a36Sopenharmony_ci		printk(KERN_ERR "uctrl: Unable to register misc device.\n");
37662306a36Sopenharmony_ci		goto out_free_irq;
37762306a36Sopenharmony_ci	}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	sbus_writel(UCTRL_INTR_RXNE_REQ|UCTRL_INTR_RXNE_MSK, &p->regs->uctrl_intr);
38062306a36Sopenharmony_ci	printk(KERN_INFO "%pOF: uctrl regs[0x%p] (irq %d)\n",
38162306a36Sopenharmony_ci	       op->dev.of_node, p->regs, p->irq);
38262306a36Sopenharmony_ci	uctrl_get_event_status(p);
38362306a36Sopenharmony_ci	uctrl_get_external_status(p);
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	dev_set_drvdata(&op->dev, p);
38662306a36Sopenharmony_ci	global_driver = p;
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ciout:
38962306a36Sopenharmony_ci	return err;
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ciout_free_irq:
39262306a36Sopenharmony_ci	free_irq(p->irq, p);
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ciout_iounmap:
39562306a36Sopenharmony_ci	of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0]));
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ciout_free:
39862306a36Sopenharmony_ci	kfree(p);
39962306a36Sopenharmony_ci	goto out;
40062306a36Sopenharmony_ci}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_cistatic int uctrl_remove(struct platform_device *op)
40362306a36Sopenharmony_ci{
40462306a36Sopenharmony_ci	struct uctrl_driver *p = dev_get_drvdata(&op->dev);
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	if (p) {
40762306a36Sopenharmony_ci		misc_deregister(&uctrl_dev);
40862306a36Sopenharmony_ci		free_irq(p->irq, p);
40962306a36Sopenharmony_ci		of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0]));
41062306a36Sopenharmony_ci		kfree(p);
41162306a36Sopenharmony_ci	}
41262306a36Sopenharmony_ci	return 0;
41362306a36Sopenharmony_ci}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cistatic const struct of_device_id uctrl_match[] = {
41662306a36Sopenharmony_ci	{
41762306a36Sopenharmony_ci		.name = "uctrl",
41862306a36Sopenharmony_ci	},
41962306a36Sopenharmony_ci	{},
42062306a36Sopenharmony_ci};
42162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, uctrl_match);
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_cistatic struct platform_driver uctrl_driver = {
42462306a36Sopenharmony_ci	.driver = {
42562306a36Sopenharmony_ci		.name = "uctrl",
42662306a36Sopenharmony_ci		.of_match_table = uctrl_match,
42762306a36Sopenharmony_ci	},
42862306a36Sopenharmony_ci	.probe		= uctrl_probe,
42962306a36Sopenharmony_ci	.remove		= uctrl_remove,
43062306a36Sopenharmony_ci};
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_cimodule_platform_driver(uctrl_driver);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
436