xref: /kernel/linux/linux-6.6/drivers/macintosh/smu.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * PowerMac G5 SMU driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2004 J. Mayer <l_indien@magic.fr>
662306a36Sopenharmony_ci * Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci/*
1062306a36Sopenharmony_ci * TODO:
1162306a36Sopenharmony_ci *  - maybe add timeout to commands ?
1262306a36Sopenharmony_ci *  - blocking version of time functions
1362306a36Sopenharmony_ci *  - polling version of i2c commands (including timer that works with
1462306a36Sopenharmony_ci *    interrupts off)
1562306a36Sopenharmony_ci *  - maybe avoid some data copies with i2c by directly using the smu cmd
1662306a36Sopenharmony_ci *    buffer and a lower level internal interface
1762306a36Sopenharmony_ci *  - understand SMU -> CPU events and implement reception of them via
1862306a36Sopenharmony_ci *    the userland interface
1962306a36Sopenharmony_ci */
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <linux/types.h>
2262306a36Sopenharmony_ci#include <linux/kernel.h>
2362306a36Sopenharmony_ci#include <linux/device.h>
2462306a36Sopenharmony_ci#include <linux/dmapool.h>
2562306a36Sopenharmony_ci#include <linux/memblock.h>
2662306a36Sopenharmony_ci#include <linux/vmalloc.h>
2762306a36Sopenharmony_ci#include <linux/highmem.h>
2862306a36Sopenharmony_ci#include <linux/jiffies.h>
2962306a36Sopenharmony_ci#include <linux/interrupt.h>
3062306a36Sopenharmony_ci#include <linux/rtc.h>
3162306a36Sopenharmony_ci#include <linux/completion.h>
3262306a36Sopenharmony_ci#include <linux/miscdevice.h>
3362306a36Sopenharmony_ci#include <linux/delay.h>
3462306a36Sopenharmony_ci#include <linux/poll.h>
3562306a36Sopenharmony_ci#include <linux/mutex.h>
3662306a36Sopenharmony_ci#include <linux/of.h>
3762306a36Sopenharmony_ci#include <linux/of_address.h>
3862306a36Sopenharmony_ci#include <linux/of_irq.h>
3962306a36Sopenharmony_ci#include <linux/of_platform.h>
4062306a36Sopenharmony_ci#include <linux/platform_device.h>
4162306a36Sopenharmony_ci#include <linux/slab.h>
4262306a36Sopenharmony_ci#include <linux/sched/signal.h>
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci#include <asm/byteorder.h>
4562306a36Sopenharmony_ci#include <asm/io.h>
4662306a36Sopenharmony_ci#include <asm/machdep.h>
4762306a36Sopenharmony_ci#include <asm/pmac_feature.h>
4862306a36Sopenharmony_ci#include <asm/smu.h>
4962306a36Sopenharmony_ci#include <asm/sections.h>
5062306a36Sopenharmony_ci#include <linux/uaccess.h>
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci#define VERSION "0.7"
5362306a36Sopenharmony_ci#define AUTHOR  "(c) 2005 Benjamin Herrenschmidt, IBM Corp."
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci#undef DEBUG_SMU
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci#ifdef DEBUG_SMU
5862306a36Sopenharmony_ci#define DPRINTK(fmt, args...) do { printk(KERN_DEBUG fmt , ##args); } while (0)
5962306a36Sopenharmony_ci#else
6062306a36Sopenharmony_ci#define DPRINTK(fmt, args...) do { } while (0)
6162306a36Sopenharmony_ci#endif
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci/*
6462306a36Sopenharmony_ci * This is the command buffer passed to the SMU hardware
6562306a36Sopenharmony_ci */
6662306a36Sopenharmony_ci#define SMU_MAX_DATA	254
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistruct smu_cmd_buf {
6962306a36Sopenharmony_ci	u8 cmd;
7062306a36Sopenharmony_ci	u8 length;
7162306a36Sopenharmony_ci	u8 data[SMU_MAX_DATA];
7262306a36Sopenharmony_ci};
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistruct smu_device {
7562306a36Sopenharmony_ci	spinlock_t		lock;
7662306a36Sopenharmony_ci	struct device_node	*of_node;
7762306a36Sopenharmony_ci	struct platform_device	*of_dev;
7862306a36Sopenharmony_ci	int			doorbell;	/* doorbell gpio */
7962306a36Sopenharmony_ci	u32 __iomem		*db_buf;	/* doorbell buffer */
8062306a36Sopenharmony_ci	struct device_node	*db_node;
8162306a36Sopenharmony_ci	unsigned int		db_irq;
8262306a36Sopenharmony_ci	int			msg;
8362306a36Sopenharmony_ci	struct device_node	*msg_node;
8462306a36Sopenharmony_ci	unsigned int		msg_irq;
8562306a36Sopenharmony_ci	struct smu_cmd_buf	*cmd_buf;	/* command buffer virtual */
8662306a36Sopenharmony_ci	u32			cmd_buf_abs;	/* command buffer absolute */
8762306a36Sopenharmony_ci	struct list_head	cmd_list;
8862306a36Sopenharmony_ci	struct smu_cmd		*cmd_cur;	/* pending command */
8962306a36Sopenharmony_ci	int			broken_nap;
9062306a36Sopenharmony_ci	struct list_head	cmd_i2c_list;
9162306a36Sopenharmony_ci	struct smu_i2c_cmd	*cmd_i2c_cur;	/* pending i2c command */
9262306a36Sopenharmony_ci	struct timer_list	i2c_timer;
9362306a36Sopenharmony_ci};
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci/*
9662306a36Sopenharmony_ci * I don't think there will ever be more than one SMU, so
9762306a36Sopenharmony_ci * for now, just hard code that
9862306a36Sopenharmony_ci */
9962306a36Sopenharmony_cistatic DEFINE_MUTEX(smu_mutex);
10062306a36Sopenharmony_cistatic struct smu_device	*smu;
10162306a36Sopenharmony_cistatic DEFINE_MUTEX(smu_part_access);
10262306a36Sopenharmony_cistatic int smu_irq_inited;
10362306a36Sopenharmony_cistatic unsigned long smu_cmdbuf_abs;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic void smu_i2c_retry(struct timer_list *t);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci/*
10862306a36Sopenharmony_ci * SMU driver low level stuff
10962306a36Sopenharmony_ci */
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic void smu_start_cmd(void)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	unsigned long faddr, fend;
11462306a36Sopenharmony_ci	struct smu_cmd *cmd;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	if (list_empty(&smu->cmd_list))
11762306a36Sopenharmony_ci		return;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	/* Fetch first command in queue */
12062306a36Sopenharmony_ci	cmd = list_entry(smu->cmd_list.next, struct smu_cmd, link);
12162306a36Sopenharmony_ci	smu->cmd_cur = cmd;
12262306a36Sopenharmony_ci	list_del(&cmd->link);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	DPRINTK("SMU: starting cmd %x, %d bytes data\n", cmd->cmd,
12562306a36Sopenharmony_ci		cmd->data_len);
12662306a36Sopenharmony_ci	DPRINTK("SMU: data buffer: %8ph\n", cmd->data_buf);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/* Fill the SMU command buffer */
12962306a36Sopenharmony_ci	smu->cmd_buf->cmd = cmd->cmd;
13062306a36Sopenharmony_ci	smu->cmd_buf->length = cmd->data_len;
13162306a36Sopenharmony_ci	memcpy(smu->cmd_buf->data, cmd->data_buf, cmd->data_len);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	/* Flush command and data to RAM */
13462306a36Sopenharmony_ci	faddr = (unsigned long)smu->cmd_buf;
13562306a36Sopenharmony_ci	fend = faddr + smu->cmd_buf->length + 2;
13662306a36Sopenharmony_ci	flush_dcache_range(faddr, fend);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	/* We also disable NAP mode for the duration of the command
14062306a36Sopenharmony_ci	 * on U3 based machines.
14162306a36Sopenharmony_ci	 * This is slightly racy as it can be written back to 1 by a sysctl
14262306a36Sopenharmony_ci	 * but that never happens in practice. There seem to be an issue with
14362306a36Sopenharmony_ci	 * U3 based machines such as the iMac G5 where napping for the
14462306a36Sopenharmony_ci	 * whole duration of the command prevents the SMU from fetching it
14562306a36Sopenharmony_ci	 * from memory. This might be related to the strange i2c based
14662306a36Sopenharmony_ci	 * mechanism the SMU uses to access memory.
14762306a36Sopenharmony_ci	 */
14862306a36Sopenharmony_ci	if (smu->broken_nap)
14962306a36Sopenharmony_ci		powersave_nap = 0;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	/* This isn't exactly a DMA mapping here, I suspect
15262306a36Sopenharmony_ci	 * the SMU is actually communicating with us via i2c to the
15362306a36Sopenharmony_ci	 * northbridge or the CPU to access RAM.
15462306a36Sopenharmony_ci	 */
15562306a36Sopenharmony_ci	writel(smu->cmd_buf_abs, smu->db_buf);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	/* Ring the SMU doorbell */
15862306a36Sopenharmony_ci	pmac_do_feature_call(PMAC_FTR_WRITE_GPIO, NULL, smu->doorbell, 4);
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic irqreturn_t smu_db_intr(int irq, void *arg)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	unsigned long flags;
16562306a36Sopenharmony_ci	struct smu_cmd *cmd;
16662306a36Sopenharmony_ci	void (*done)(struct smu_cmd *cmd, void *misc) = NULL;
16762306a36Sopenharmony_ci	void *misc = NULL;
16862306a36Sopenharmony_ci	u8 gpio;
16962306a36Sopenharmony_ci	int rc = 0;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	/* SMU completed the command, well, we hope, let's make sure
17262306a36Sopenharmony_ci	 * of it
17362306a36Sopenharmony_ci	 */
17462306a36Sopenharmony_ci	spin_lock_irqsave(&smu->lock, flags);
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, smu->doorbell);
17762306a36Sopenharmony_ci	if ((gpio & 7) != 7) {
17862306a36Sopenharmony_ci		spin_unlock_irqrestore(&smu->lock, flags);
17962306a36Sopenharmony_ci		return IRQ_HANDLED;
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	cmd = smu->cmd_cur;
18362306a36Sopenharmony_ci	smu->cmd_cur = NULL;
18462306a36Sopenharmony_ci	if (cmd == NULL)
18562306a36Sopenharmony_ci		goto bail;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	if (rc == 0) {
18862306a36Sopenharmony_ci		unsigned long faddr;
18962306a36Sopenharmony_ci		int reply_len;
19062306a36Sopenharmony_ci		u8 ack;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci		/* CPU might have brought back the cache line, so we need
19362306a36Sopenharmony_ci		 * to flush again before peeking at the SMU response. We
19462306a36Sopenharmony_ci		 * flush the entire buffer for now as we haven't read the
19562306a36Sopenharmony_ci		 * reply length (it's only 2 cache lines anyway)
19662306a36Sopenharmony_ci		 */
19762306a36Sopenharmony_ci		faddr = (unsigned long)smu->cmd_buf;
19862306a36Sopenharmony_ci		flush_dcache_range(faddr, faddr + 256);
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci		/* Now check ack */
20162306a36Sopenharmony_ci		ack = (~cmd->cmd) & 0xff;
20262306a36Sopenharmony_ci		if (ack != smu->cmd_buf->cmd) {
20362306a36Sopenharmony_ci			DPRINTK("SMU: incorrect ack, want %x got %x\n",
20462306a36Sopenharmony_ci				ack, smu->cmd_buf->cmd);
20562306a36Sopenharmony_ci			rc = -EIO;
20662306a36Sopenharmony_ci		}
20762306a36Sopenharmony_ci		reply_len = rc == 0 ? smu->cmd_buf->length : 0;
20862306a36Sopenharmony_ci		DPRINTK("SMU: reply len: %d\n", reply_len);
20962306a36Sopenharmony_ci		if (reply_len > cmd->reply_len) {
21062306a36Sopenharmony_ci			printk(KERN_WARNING "SMU: reply buffer too small,"
21162306a36Sopenharmony_ci			       "got %d bytes for a %d bytes buffer\n",
21262306a36Sopenharmony_ci			       reply_len, cmd->reply_len);
21362306a36Sopenharmony_ci			reply_len = cmd->reply_len;
21462306a36Sopenharmony_ci		}
21562306a36Sopenharmony_ci		cmd->reply_len = reply_len;
21662306a36Sopenharmony_ci		if (cmd->reply_buf && reply_len)
21762306a36Sopenharmony_ci			memcpy(cmd->reply_buf, smu->cmd_buf->data, reply_len);
21862306a36Sopenharmony_ci	}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	/* Now complete the command. Write status last in order as we lost
22162306a36Sopenharmony_ci	 * ownership of the command structure as soon as it's no longer -1
22262306a36Sopenharmony_ci	 */
22362306a36Sopenharmony_ci	done = cmd->done;
22462306a36Sopenharmony_ci	misc = cmd->misc;
22562306a36Sopenharmony_ci	mb();
22662306a36Sopenharmony_ci	cmd->status = rc;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	/* Re-enable NAP mode */
22962306a36Sopenharmony_ci	if (smu->broken_nap)
23062306a36Sopenharmony_ci		powersave_nap = 1;
23162306a36Sopenharmony_ci bail:
23262306a36Sopenharmony_ci	/* Start next command if any */
23362306a36Sopenharmony_ci	smu_start_cmd();
23462306a36Sopenharmony_ci	spin_unlock_irqrestore(&smu->lock, flags);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	/* Call command completion handler if any */
23762306a36Sopenharmony_ci	if (done)
23862306a36Sopenharmony_ci		done(cmd, misc);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	/* It's an edge interrupt, nothing to do */
24162306a36Sopenharmony_ci	return IRQ_HANDLED;
24262306a36Sopenharmony_ci}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cistatic irqreturn_t smu_msg_intr(int irq, void *arg)
24662306a36Sopenharmony_ci{
24762306a36Sopenharmony_ci	/* I don't quite know what to do with this one, we seem to never
24862306a36Sopenharmony_ci	 * receive it, so I suspect we have to arm it someway in the SMU
24962306a36Sopenharmony_ci	 * to start getting events that way.
25062306a36Sopenharmony_ci	 */
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	printk(KERN_INFO "SMU: message interrupt !\n");
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	/* It's an edge interrupt, nothing to do */
25562306a36Sopenharmony_ci	return IRQ_HANDLED;
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci/*
26062306a36Sopenharmony_ci * Queued command management.
26162306a36Sopenharmony_ci *
26262306a36Sopenharmony_ci */
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ciint smu_queue_cmd(struct smu_cmd *cmd)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	unsigned long flags;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	if (smu == NULL)
26962306a36Sopenharmony_ci		return -ENODEV;
27062306a36Sopenharmony_ci	if (cmd->data_len > SMU_MAX_DATA ||
27162306a36Sopenharmony_ci	    cmd->reply_len > SMU_MAX_DATA)
27262306a36Sopenharmony_ci		return -EINVAL;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	cmd->status = 1;
27562306a36Sopenharmony_ci	spin_lock_irqsave(&smu->lock, flags);
27662306a36Sopenharmony_ci	list_add_tail(&cmd->link, &smu->cmd_list);
27762306a36Sopenharmony_ci	if (smu->cmd_cur == NULL)
27862306a36Sopenharmony_ci		smu_start_cmd();
27962306a36Sopenharmony_ci	spin_unlock_irqrestore(&smu->lock, flags);
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	/* Workaround for early calls when irq isn't available */
28262306a36Sopenharmony_ci	if (!smu_irq_inited || !smu->db_irq)
28362306a36Sopenharmony_ci		smu_spinwait_cmd(cmd);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	return 0;
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ciEXPORT_SYMBOL(smu_queue_cmd);
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ciint smu_queue_simple(struct smu_simple_cmd *scmd, u8 command,
29162306a36Sopenharmony_ci		     unsigned int data_len,
29262306a36Sopenharmony_ci		     void (*done)(struct smu_cmd *cmd, void *misc),
29362306a36Sopenharmony_ci		     void *misc, ...)
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	struct smu_cmd *cmd = &scmd->cmd;
29662306a36Sopenharmony_ci	va_list list;
29762306a36Sopenharmony_ci	int i;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	if (data_len > sizeof(scmd->buffer))
30062306a36Sopenharmony_ci		return -EINVAL;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	memset(scmd, 0, sizeof(*scmd));
30362306a36Sopenharmony_ci	cmd->cmd = command;
30462306a36Sopenharmony_ci	cmd->data_len = data_len;
30562306a36Sopenharmony_ci	cmd->data_buf = scmd->buffer;
30662306a36Sopenharmony_ci	cmd->reply_len = sizeof(scmd->buffer);
30762306a36Sopenharmony_ci	cmd->reply_buf = scmd->buffer;
30862306a36Sopenharmony_ci	cmd->done = done;
30962306a36Sopenharmony_ci	cmd->misc = misc;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	va_start(list, misc);
31262306a36Sopenharmony_ci	for (i = 0; i < data_len; ++i)
31362306a36Sopenharmony_ci		scmd->buffer[i] = (u8)va_arg(list, int);
31462306a36Sopenharmony_ci	va_end(list);
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	return smu_queue_cmd(cmd);
31762306a36Sopenharmony_ci}
31862306a36Sopenharmony_ciEXPORT_SYMBOL(smu_queue_simple);
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_civoid smu_poll(void)
32262306a36Sopenharmony_ci{
32362306a36Sopenharmony_ci	u8 gpio;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	if (smu == NULL)
32662306a36Sopenharmony_ci		return;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, smu->doorbell);
32962306a36Sopenharmony_ci	if ((gpio & 7) == 7)
33062306a36Sopenharmony_ci		smu_db_intr(smu->db_irq, smu);
33162306a36Sopenharmony_ci}
33262306a36Sopenharmony_ciEXPORT_SYMBOL(smu_poll);
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_civoid smu_done_complete(struct smu_cmd *cmd, void *misc)
33662306a36Sopenharmony_ci{
33762306a36Sopenharmony_ci	struct completion *comp = misc;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	complete(comp);
34062306a36Sopenharmony_ci}
34162306a36Sopenharmony_ciEXPORT_SYMBOL(smu_done_complete);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_civoid smu_spinwait_cmd(struct smu_cmd *cmd)
34562306a36Sopenharmony_ci{
34662306a36Sopenharmony_ci	while(cmd->status == 1)
34762306a36Sopenharmony_ci		smu_poll();
34862306a36Sopenharmony_ci}
34962306a36Sopenharmony_ciEXPORT_SYMBOL(smu_spinwait_cmd);
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci/* RTC low level commands */
35362306a36Sopenharmony_cistatic inline int bcd2hex (int n)
35462306a36Sopenharmony_ci{
35562306a36Sopenharmony_ci	return (((n & 0xf0) >> 4) * 10) + (n & 0xf);
35662306a36Sopenharmony_ci}
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_cistatic inline int hex2bcd (int n)
36062306a36Sopenharmony_ci{
36162306a36Sopenharmony_ci	return ((n / 10) << 4) + (n % 10);
36262306a36Sopenharmony_ci}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_cistatic inline void smu_fill_set_rtc_cmd(struct smu_cmd_buf *cmd_buf,
36662306a36Sopenharmony_ci					struct rtc_time *time)
36762306a36Sopenharmony_ci{
36862306a36Sopenharmony_ci	cmd_buf->cmd = 0x8e;
36962306a36Sopenharmony_ci	cmd_buf->length = 8;
37062306a36Sopenharmony_ci	cmd_buf->data[0] = 0x80;
37162306a36Sopenharmony_ci	cmd_buf->data[1] = hex2bcd(time->tm_sec);
37262306a36Sopenharmony_ci	cmd_buf->data[2] = hex2bcd(time->tm_min);
37362306a36Sopenharmony_ci	cmd_buf->data[3] = hex2bcd(time->tm_hour);
37462306a36Sopenharmony_ci	cmd_buf->data[4] = time->tm_wday;
37562306a36Sopenharmony_ci	cmd_buf->data[5] = hex2bcd(time->tm_mday);
37662306a36Sopenharmony_ci	cmd_buf->data[6] = hex2bcd(time->tm_mon) + 1;
37762306a36Sopenharmony_ci	cmd_buf->data[7] = hex2bcd(time->tm_year - 100);
37862306a36Sopenharmony_ci}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ciint smu_get_rtc_time(struct rtc_time *time, int spinwait)
38262306a36Sopenharmony_ci{
38362306a36Sopenharmony_ci	struct smu_simple_cmd cmd;
38462306a36Sopenharmony_ci	int rc;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	if (smu == NULL)
38762306a36Sopenharmony_ci		return -ENODEV;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	memset(time, 0, sizeof(struct rtc_time));
39062306a36Sopenharmony_ci	rc = smu_queue_simple(&cmd, SMU_CMD_RTC_COMMAND, 1, NULL, NULL,
39162306a36Sopenharmony_ci			      SMU_CMD_RTC_GET_DATETIME);
39262306a36Sopenharmony_ci	if (rc)
39362306a36Sopenharmony_ci		return rc;
39462306a36Sopenharmony_ci	smu_spinwait_simple(&cmd);
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	time->tm_sec = bcd2hex(cmd.buffer[0]);
39762306a36Sopenharmony_ci	time->tm_min = bcd2hex(cmd.buffer[1]);
39862306a36Sopenharmony_ci	time->tm_hour = bcd2hex(cmd.buffer[2]);
39962306a36Sopenharmony_ci	time->tm_wday = bcd2hex(cmd.buffer[3]);
40062306a36Sopenharmony_ci	time->tm_mday = bcd2hex(cmd.buffer[4]);
40162306a36Sopenharmony_ci	time->tm_mon = bcd2hex(cmd.buffer[5]) - 1;
40262306a36Sopenharmony_ci	time->tm_year = bcd2hex(cmd.buffer[6]) + 100;
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	return 0;
40562306a36Sopenharmony_ci}
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ciint smu_set_rtc_time(struct rtc_time *time, int spinwait)
40962306a36Sopenharmony_ci{
41062306a36Sopenharmony_ci	struct smu_simple_cmd cmd;
41162306a36Sopenharmony_ci	int rc;
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	if (smu == NULL)
41462306a36Sopenharmony_ci		return -ENODEV;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	rc = smu_queue_simple(&cmd, SMU_CMD_RTC_COMMAND, 8, NULL, NULL,
41762306a36Sopenharmony_ci			      SMU_CMD_RTC_SET_DATETIME,
41862306a36Sopenharmony_ci			      hex2bcd(time->tm_sec),
41962306a36Sopenharmony_ci			      hex2bcd(time->tm_min),
42062306a36Sopenharmony_ci			      hex2bcd(time->tm_hour),
42162306a36Sopenharmony_ci			      time->tm_wday,
42262306a36Sopenharmony_ci			      hex2bcd(time->tm_mday),
42362306a36Sopenharmony_ci			      hex2bcd(time->tm_mon) + 1,
42462306a36Sopenharmony_ci			      hex2bcd(time->tm_year - 100));
42562306a36Sopenharmony_ci	if (rc)
42662306a36Sopenharmony_ci		return rc;
42762306a36Sopenharmony_ci	smu_spinwait_simple(&cmd);
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	return 0;
43062306a36Sopenharmony_ci}
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_civoid smu_shutdown(void)
43462306a36Sopenharmony_ci{
43562306a36Sopenharmony_ci	struct smu_simple_cmd cmd;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	if (smu == NULL)
43862306a36Sopenharmony_ci		return;
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	if (smu_queue_simple(&cmd, SMU_CMD_POWER_COMMAND, 9, NULL, NULL,
44162306a36Sopenharmony_ci			     'S', 'H', 'U', 'T', 'D', 'O', 'W', 'N', 0))
44262306a36Sopenharmony_ci		return;
44362306a36Sopenharmony_ci	smu_spinwait_simple(&cmd);
44462306a36Sopenharmony_ci	for (;;)
44562306a36Sopenharmony_ci		;
44662306a36Sopenharmony_ci}
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_civoid smu_restart(void)
45062306a36Sopenharmony_ci{
45162306a36Sopenharmony_ci	struct smu_simple_cmd cmd;
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	if (smu == NULL)
45462306a36Sopenharmony_ci		return;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	if (smu_queue_simple(&cmd, SMU_CMD_POWER_COMMAND, 8, NULL, NULL,
45762306a36Sopenharmony_ci			     'R', 'E', 'S', 'T', 'A', 'R', 'T', 0))
45862306a36Sopenharmony_ci		return;
45962306a36Sopenharmony_ci	smu_spinwait_simple(&cmd);
46062306a36Sopenharmony_ci	for (;;)
46162306a36Sopenharmony_ci		;
46262306a36Sopenharmony_ci}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ciint smu_present(void)
46662306a36Sopenharmony_ci{
46762306a36Sopenharmony_ci	return smu != NULL;
46862306a36Sopenharmony_ci}
46962306a36Sopenharmony_ciEXPORT_SYMBOL(smu_present);
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ciint __init smu_init (void)
47362306a36Sopenharmony_ci{
47462306a36Sopenharmony_ci	struct device_node *np;
47562306a36Sopenharmony_ci	u64 data;
47662306a36Sopenharmony_ci	int ret = 0;
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci        np = of_find_node_by_type(NULL, "smu");
47962306a36Sopenharmony_ci        if (np == NULL)
48062306a36Sopenharmony_ci		return -ENODEV;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	printk(KERN_INFO "SMU: Driver %s %s\n", VERSION, AUTHOR);
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	/*
48562306a36Sopenharmony_ci	 * SMU based G5s need some memory below 2Gb. Thankfully this is
48662306a36Sopenharmony_ci	 * called at a time where memblock is still available.
48762306a36Sopenharmony_ci	 */
48862306a36Sopenharmony_ci	smu_cmdbuf_abs = memblock_phys_alloc_range(4096, 4096, 0, 0x80000000UL);
48962306a36Sopenharmony_ci	if (smu_cmdbuf_abs == 0) {
49062306a36Sopenharmony_ci		printk(KERN_ERR "SMU: Command buffer allocation failed !\n");
49162306a36Sopenharmony_ci		ret = -EINVAL;
49262306a36Sopenharmony_ci		goto fail_np;
49362306a36Sopenharmony_ci	}
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	smu = memblock_alloc(sizeof(struct smu_device), SMP_CACHE_BYTES);
49662306a36Sopenharmony_ci	if (!smu)
49762306a36Sopenharmony_ci		panic("%s: Failed to allocate %zu bytes\n", __func__,
49862306a36Sopenharmony_ci		      sizeof(struct smu_device));
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	spin_lock_init(&smu->lock);
50162306a36Sopenharmony_ci	INIT_LIST_HEAD(&smu->cmd_list);
50262306a36Sopenharmony_ci	INIT_LIST_HEAD(&smu->cmd_i2c_list);
50362306a36Sopenharmony_ci	smu->of_node = np;
50462306a36Sopenharmony_ci	smu->db_irq = 0;
50562306a36Sopenharmony_ci	smu->msg_irq = 0;
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_ci	/* smu_cmdbuf_abs is in the low 2G of RAM, can be converted to a
50862306a36Sopenharmony_ci	 * 32 bits value safely
50962306a36Sopenharmony_ci	 */
51062306a36Sopenharmony_ci	smu->cmd_buf_abs = (u32)smu_cmdbuf_abs;
51162306a36Sopenharmony_ci	smu->cmd_buf = __va(smu_cmdbuf_abs);
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	smu->db_node = of_find_node_by_name(NULL, "smu-doorbell");
51462306a36Sopenharmony_ci	if (smu->db_node == NULL) {
51562306a36Sopenharmony_ci		printk(KERN_ERR "SMU: Can't find doorbell GPIO !\n");
51662306a36Sopenharmony_ci		ret = -ENXIO;
51762306a36Sopenharmony_ci		goto fail_bootmem;
51862306a36Sopenharmony_ci	}
51962306a36Sopenharmony_ci	if (of_property_read_reg(smu->db_node, 0, &data, NULL)) {
52062306a36Sopenharmony_ci		printk(KERN_ERR "SMU: Can't find doorbell GPIO address !\n");
52162306a36Sopenharmony_ci		ret = -ENXIO;
52262306a36Sopenharmony_ci		goto fail_db_node;
52362306a36Sopenharmony_ci	}
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	/* Current setup has one doorbell GPIO that does both doorbell
52662306a36Sopenharmony_ci	 * and ack. GPIOs are at 0x50, best would be to find that out
52762306a36Sopenharmony_ci	 * in the device-tree though.
52862306a36Sopenharmony_ci	 */
52962306a36Sopenharmony_ci	smu->doorbell = data;
53062306a36Sopenharmony_ci	if (smu->doorbell < 0x50)
53162306a36Sopenharmony_ci		smu->doorbell += 0x50;
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	/* Now look for the smu-interrupt GPIO */
53462306a36Sopenharmony_ci	do {
53562306a36Sopenharmony_ci		smu->msg_node = of_find_node_by_name(NULL, "smu-interrupt");
53662306a36Sopenharmony_ci		if (smu->msg_node == NULL)
53762306a36Sopenharmony_ci			break;
53862306a36Sopenharmony_ci		if (of_property_read_reg(smu->msg_node, 0, &data, NULL)) {
53962306a36Sopenharmony_ci			of_node_put(smu->msg_node);
54062306a36Sopenharmony_ci			smu->msg_node = NULL;
54162306a36Sopenharmony_ci			break;
54262306a36Sopenharmony_ci		}
54362306a36Sopenharmony_ci		smu->msg = data;
54462306a36Sopenharmony_ci		if (smu->msg < 0x50)
54562306a36Sopenharmony_ci			smu->msg += 0x50;
54662306a36Sopenharmony_ci	} while(0);
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	/* Doorbell buffer is currently hard-coded, I didn't find a proper
54962306a36Sopenharmony_ci	 * device-tree entry giving the address. Best would probably to use
55062306a36Sopenharmony_ci	 * an offset for K2 base though, but let's do it that way for now.
55162306a36Sopenharmony_ci	 */
55262306a36Sopenharmony_ci	smu->db_buf = ioremap(0x8000860c, 0x1000);
55362306a36Sopenharmony_ci	if (smu->db_buf == NULL) {
55462306a36Sopenharmony_ci		printk(KERN_ERR "SMU: Can't map doorbell buffer pointer !\n");
55562306a36Sopenharmony_ci		ret = -ENXIO;
55662306a36Sopenharmony_ci		goto fail_msg_node;
55762306a36Sopenharmony_ci	}
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	/* U3 has an issue with NAP mode when issuing SMU commands */
56062306a36Sopenharmony_ci	smu->broken_nap = pmac_get_uninorth_variant() < 4;
56162306a36Sopenharmony_ci	if (smu->broken_nap)
56262306a36Sopenharmony_ci		printk(KERN_INFO "SMU: using NAP mode workaround\n");
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	sys_ctrler = SYS_CTRLER_SMU;
56562306a36Sopenharmony_ci	return 0;
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_cifail_msg_node:
56862306a36Sopenharmony_ci	of_node_put(smu->msg_node);
56962306a36Sopenharmony_cifail_db_node:
57062306a36Sopenharmony_ci	of_node_put(smu->db_node);
57162306a36Sopenharmony_cifail_bootmem:
57262306a36Sopenharmony_ci	memblock_free(smu, sizeof(struct smu_device));
57362306a36Sopenharmony_ci	smu = NULL;
57462306a36Sopenharmony_cifail_np:
57562306a36Sopenharmony_ci	of_node_put(np);
57662306a36Sopenharmony_ci	return ret;
57762306a36Sopenharmony_ci}
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_cistatic int smu_late_init(void)
58162306a36Sopenharmony_ci{
58262306a36Sopenharmony_ci	if (!smu)
58362306a36Sopenharmony_ci		return 0;
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci	timer_setup(&smu->i2c_timer, smu_i2c_retry, 0);
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci	if (smu->db_node) {
58862306a36Sopenharmony_ci		smu->db_irq = irq_of_parse_and_map(smu->db_node, 0);
58962306a36Sopenharmony_ci		if (!smu->db_irq)
59062306a36Sopenharmony_ci			printk(KERN_ERR "smu: failed to map irq for node %pOF\n",
59162306a36Sopenharmony_ci			       smu->db_node);
59262306a36Sopenharmony_ci	}
59362306a36Sopenharmony_ci	if (smu->msg_node) {
59462306a36Sopenharmony_ci		smu->msg_irq = irq_of_parse_and_map(smu->msg_node, 0);
59562306a36Sopenharmony_ci		if (!smu->msg_irq)
59662306a36Sopenharmony_ci			printk(KERN_ERR "smu: failed to map irq for node %pOF\n",
59762306a36Sopenharmony_ci			       smu->msg_node);
59862306a36Sopenharmony_ci	}
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	/*
60162306a36Sopenharmony_ci	 * Try to request the interrupts
60262306a36Sopenharmony_ci	 */
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci	if (smu->db_irq) {
60562306a36Sopenharmony_ci		if (request_irq(smu->db_irq, smu_db_intr,
60662306a36Sopenharmony_ci				IRQF_SHARED, "SMU doorbell", smu) < 0) {
60762306a36Sopenharmony_ci			printk(KERN_WARNING "SMU: can't "
60862306a36Sopenharmony_ci			       "request interrupt %d\n",
60962306a36Sopenharmony_ci			       smu->db_irq);
61062306a36Sopenharmony_ci			smu->db_irq = 0;
61162306a36Sopenharmony_ci		}
61262306a36Sopenharmony_ci	}
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	if (smu->msg_irq) {
61562306a36Sopenharmony_ci		if (request_irq(smu->msg_irq, smu_msg_intr,
61662306a36Sopenharmony_ci				IRQF_SHARED, "SMU message", smu) < 0) {
61762306a36Sopenharmony_ci			printk(KERN_WARNING "SMU: can't "
61862306a36Sopenharmony_ci			       "request interrupt %d\n",
61962306a36Sopenharmony_ci			       smu->msg_irq);
62062306a36Sopenharmony_ci			smu->msg_irq = 0;
62162306a36Sopenharmony_ci		}
62262306a36Sopenharmony_ci	}
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	smu_irq_inited = 1;
62562306a36Sopenharmony_ci	return 0;
62662306a36Sopenharmony_ci}
62762306a36Sopenharmony_ci/* This has to be before arch_initcall as the low i2c stuff relies on the
62862306a36Sopenharmony_ci * above having been done before we reach arch_initcalls
62962306a36Sopenharmony_ci */
63062306a36Sopenharmony_cicore_initcall(smu_late_init);
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci/*
63362306a36Sopenharmony_ci * sysfs visibility
63462306a36Sopenharmony_ci */
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_cistatic void smu_expose_childs(struct work_struct *unused)
63762306a36Sopenharmony_ci{
63862306a36Sopenharmony_ci	struct device_node *np;
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_ci	for_each_child_of_node(smu->of_node, np)
64162306a36Sopenharmony_ci		if (of_device_is_compatible(np, "smu-sensors"))
64262306a36Sopenharmony_ci			of_platform_device_create(np, "smu-sensors",
64362306a36Sopenharmony_ci						  &smu->of_dev->dev);
64462306a36Sopenharmony_ci}
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_cistatic DECLARE_WORK(smu_expose_childs_work, smu_expose_childs);
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_cistatic int smu_platform_probe(struct platform_device* dev)
64962306a36Sopenharmony_ci{
65062306a36Sopenharmony_ci	if (!smu)
65162306a36Sopenharmony_ci		return -ENODEV;
65262306a36Sopenharmony_ci	smu->of_dev = dev;
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci	/*
65562306a36Sopenharmony_ci	 * Ok, we are matched, now expose all i2c busses. We have to defer
65662306a36Sopenharmony_ci	 * that unfortunately or it would deadlock inside the device model
65762306a36Sopenharmony_ci	 */
65862306a36Sopenharmony_ci	schedule_work(&smu_expose_childs_work);
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	return 0;
66162306a36Sopenharmony_ci}
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_cistatic const struct of_device_id smu_platform_match[] =
66462306a36Sopenharmony_ci{
66562306a36Sopenharmony_ci	{
66662306a36Sopenharmony_ci		.type		= "smu",
66762306a36Sopenharmony_ci	},
66862306a36Sopenharmony_ci	{},
66962306a36Sopenharmony_ci};
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_cistatic struct platform_driver smu_of_platform_driver =
67262306a36Sopenharmony_ci{
67362306a36Sopenharmony_ci	.driver = {
67462306a36Sopenharmony_ci		.name = "smu",
67562306a36Sopenharmony_ci		.of_match_table = smu_platform_match,
67662306a36Sopenharmony_ci	},
67762306a36Sopenharmony_ci	.probe		= smu_platform_probe,
67862306a36Sopenharmony_ci};
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_cistatic int __init smu_init_sysfs(void)
68162306a36Sopenharmony_ci{
68262306a36Sopenharmony_ci	/*
68362306a36Sopenharmony_ci	 * For now, we don't power manage machines with an SMU chip,
68462306a36Sopenharmony_ci	 * I'm a bit too far from figuring out how that works with those
68562306a36Sopenharmony_ci	 * new chipsets, but that will come back and bite us
68662306a36Sopenharmony_ci	 */
68762306a36Sopenharmony_ci	platform_driver_register(&smu_of_platform_driver);
68862306a36Sopenharmony_ci	return 0;
68962306a36Sopenharmony_ci}
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_cidevice_initcall(smu_init_sysfs);
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_cistruct platform_device *smu_get_ofdev(void)
69462306a36Sopenharmony_ci{
69562306a36Sopenharmony_ci	if (!smu)
69662306a36Sopenharmony_ci		return NULL;
69762306a36Sopenharmony_ci	return smu->of_dev;
69862306a36Sopenharmony_ci}
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(smu_get_ofdev);
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci/*
70362306a36Sopenharmony_ci * i2c interface
70462306a36Sopenharmony_ci */
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_cistatic void smu_i2c_complete_command(struct smu_i2c_cmd *cmd, int fail)
70762306a36Sopenharmony_ci{
70862306a36Sopenharmony_ci	void (*done)(struct smu_i2c_cmd *cmd, void *misc) = cmd->done;
70962306a36Sopenharmony_ci	void *misc = cmd->misc;
71062306a36Sopenharmony_ci	unsigned long flags;
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci	/* Check for read case */
71362306a36Sopenharmony_ci	if (!fail && cmd->read) {
71462306a36Sopenharmony_ci		if (cmd->pdata[0] < 1)
71562306a36Sopenharmony_ci			fail = 1;
71662306a36Sopenharmony_ci		else
71762306a36Sopenharmony_ci			memcpy(cmd->info.data, &cmd->pdata[1],
71862306a36Sopenharmony_ci			       cmd->info.datalen);
71962306a36Sopenharmony_ci	}
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_ci	DPRINTK("SMU: completing, success: %d\n", !fail);
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci	/* Update status and mark no pending i2c command with lock
72462306a36Sopenharmony_ci	 * held so nobody comes in while we dequeue an eventual
72562306a36Sopenharmony_ci	 * pending next i2c command
72662306a36Sopenharmony_ci	 */
72762306a36Sopenharmony_ci	spin_lock_irqsave(&smu->lock, flags);
72862306a36Sopenharmony_ci	smu->cmd_i2c_cur = NULL;
72962306a36Sopenharmony_ci	wmb();
73062306a36Sopenharmony_ci	cmd->status = fail ? -EIO : 0;
73162306a36Sopenharmony_ci
73262306a36Sopenharmony_ci	/* Is there another i2c command waiting ? */
73362306a36Sopenharmony_ci	if (!list_empty(&smu->cmd_i2c_list)) {
73462306a36Sopenharmony_ci		struct smu_i2c_cmd *newcmd;
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_ci		/* Fetch it, new current, remove from list */
73762306a36Sopenharmony_ci		newcmd = list_entry(smu->cmd_i2c_list.next,
73862306a36Sopenharmony_ci				    struct smu_i2c_cmd, link);
73962306a36Sopenharmony_ci		smu->cmd_i2c_cur = newcmd;
74062306a36Sopenharmony_ci		list_del(&cmd->link);
74162306a36Sopenharmony_ci
74262306a36Sopenharmony_ci		/* Queue with low level smu */
74362306a36Sopenharmony_ci		list_add_tail(&cmd->scmd.link, &smu->cmd_list);
74462306a36Sopenharmony_ci		if (smu->cmd_cur == NULL)
74562306a36Sopenharmony_ci			smu_start_cmd();
74662306a36Sopenharmony_ci	}
74762306a36Sopenharmony_ci	spin_unlock_irqrestore(&smu->lock, flags);
74862306a36Sopenharmony_ci
74962306a36Sopenharmony_ci	/* Call command completion handler if any */
75062306a36Sopenharmony_ci	if (done)
75162306a36Sopenharmony_ci		done(cmd, misc);
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci}
75462306a36Sopenharmony_ci
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_cistatic void smu_i2c_retry(struct timer_list *unused)
75762306a36Sopenharmony_ci{
75862306a36Sopenharmony_ci	struct smu_i2c_cmd	*cmd = smu->cmd_i2c_cur;
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_ci	DPRINTK("SMU: i2c failure, requeuing...\n");
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci	/* requeue command simply by resetting reply_len */
76362306a36Sopenharmony_ci	cmd->pdata[0] = 0xff;
76462306a36Sopenharmony_ci	cmd->scmd.reply_len = sizeof(cmd->pdata);
76562306a36Sopenharmony_ci	smu_queue_cmd(&cmd->scmd);
76662306a36Sopenharmony_ci}
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_cistatic void smu_i2c_low_completion(struct smu_cmd *scmd, void *misc)
77062306a36Sopenharmony_ci{
77162306a36Sopenharmony_ci	struct smu_i2c_cmd	*cmd = misc;
77262306a36Sopenharmony_ci	int			fail = 0;
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_ci	DPRINTK("SMU: i2c compl. stage=%d status=%x pdata[0]=%x rlen: %x\n",
77562306a36Sopenharmony_ci		cmd->stage, scmd->status, cmd->pdata[0], scmd->reply_len);
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	/* Check for possible status */
77862306a36Sopenharmony_ci	if (scmd->status < 0)
77962306a36Sopenharmony_ci		fail = 1;
78062306a36Sopenharmony_ci	else if (cmd->read) {
78162306a36Sopenharmony_ci		if (cmd->stage == 0)
78262306a36Sopenharmony_ci			fail = cmd->pdata[0] != 0;
78362306a36Sopenharmony_ci		else
78462306a36Sopenharmony_ci			fail = cmd->pdata[0] >= 0x80;
78562306a36Sopenharmony_ci	} else {
78662306a36Sopenharmony_ci		fail = cmd->pdata[0] != 0;
78762306a36Sopenharmony_ci	}
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	/* Handle failures by requeuing command, after 5ms interval
79062306a36Sopenharmony_ci	 */
79162306a36Sopenharmony_ci	if (fail && --cmd->retries > 0) {
79262306a36Sopenharmony_ci		DPRINTK("SMU: i2c failure, starting timer...\n");
79362306a36Sopenharmony_ci		BUG_ON(cmd != smu->cmd_i2c_cur);
79462306a36Sopenharmony_ci		if (!smu_irq_inited) {
79562306a36Sopenharmony_ci			mdelay(5);
79662306a36Sopenharmony_ci			smu_i2c_retry(NULL);
79762306a36Sopenharmony_ci			return;
79862306a36Sopenharmony_ci		}
79962306a36Sopenharmony_ci		mod_timer(&smu->i2c_timer, jiffies + msecs_to_jiffies(5));
80062306a36Sopenharmony_ci		return;
80162306a36Sopenharmony_ci	}
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci	/* If failure or stage 1, command is complete */
80462306a36Sopenharmony_ci	if (fail || cmd->stage != 0) {
80562306a36Sopenharmony_ci		smu_i2c_complete_command(cmd, fail);
80662306a36Sopenharmony_ci		return;
80762306a36Sopenharmony_ci	}
80862306a36Sopenharmony_ci
80962306a36Sopenharmony_ci	DPRINTK("SMU: going to stage 1\n");
81062306a36Sopenharmony_ci
81162306a36Sopenharmony_ci	/* Ok, initial command complete, now poll status */
81262306a36Sopenharmony_ci	scmd->reply_buf = cmd->pdata;
81362306a36Sopenharmony_ci	scmd->reply_len = sizeof(cmd->pdata);
81462306a36Sopenharmony_ci	scmd->data_buf = cmd->pdata;
81562306a36Sopenharmony_ci	scmd->data_len = 1;
81662306a36Sopenharmony_ci	cmd->pdata[0] = 0;
81762306a36Sopenharmony_ci	cmd->stage = 1;
81862306a36Sopenharmony_ci	cmd->retries = 20;
81962306a36Sopenharmony_ci	smu_queue_cmd(scmd);
82062306a36Sopenharmony_ci}
82162306a36Sopenharmony_ci
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_ciint smu_queue_i2c(struct smu_i2c_cmd *cmd)
82462306a36Sopenharmony_ci{
82562306a36Sopenharmony_ci	unsigned long flags;
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci	if (smu == NULL)
82862306a36Sopenharmony_ci		return -ENODEV;
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_ci	/* Fill most fields of scmd */
83162306a36Sopenharmony_ci	cmd->scmd.cmd = SMU_CMD_I2C_COMMAND;
83262306a36Sopenharmony_ci	cmd->scmd.done = smu_i2c_low_completion;
83362306a36Sopenharmony_ci	cmd->scmd.misc = cmd;
83462306a36Sopenharmony_ci	cmd->scmd.reply_buf = cmd->pdata;
83562306a36Sopenharmony_ci	cmd->scmd.reply_len = sizeof(cmd->pdata);
83662306a36Sopenharmony_ci	cmd->scmd.data_buf = (u8 *)(char *)&cmd->info;
83762306a36Sopenharmony_ci	cmd->scmd.status = 1;
83862306a36Sopenharmony_ci	cmd->stage = 0;
83962306a36Sopenharmony_ci	cmd->pdata[0] = 0xff;
84062306a36Sopenharmony_ci	cmd->retries = 20;
84162306a36Sopenharmony_ci	cmd->status = 1;
84262306a36Sopenharmony_ci
84362306a36Sopenharmony_ci	/* Check transfer type, sanitize some "info" fields
84462306a36Sopenharmony_ci	 * based on transfer type and do more checking
84562306a36Sopenharmony_ci	 */
84662306a36Sopenharmony_ci	cmd->info.caddr = cmd->info.devaddr;
84762306a36Sopenharmony_ci	cmd->read = cmd->info.devaddr & 0x01;
84862306a36Sopenharmony_ci	switch(cmd->info.type) {
84962306a36Sopenharmony_ci	case SMU_I2C_TRANSFER_SIMPLE:
85062306a36Sopenharmony_ci		cmd->info.sublen = 0;
85162306a36Sopenharmony_ci		memset(cmd->info.subaddr, 0, sizeof(cmd->info.subaddr));
85262306a36Sopenharmony_ci		break;
85362306a36Sopenharmony_ci	case SMU_I2C_TRANSFER_COMBINED:
85462306a36Sopenharmony_ci		cmd->info.devaddr &= 0xfe;
85562306a36Sopenharmony_ci		fallthrough;
85662306a36Sopenharmony_ci	case SMU_I2C_TRANSFER_STDSUB:
85762306a36Sopenharmony_ci		if (cmd->info.sublen > 3)
85862306a36Sopenharmony_ci			return -EINVAL;
85962306a36Sopenharmony_ci		break;
86062306a36Sopenharmony_ci	default:
86162306a36Sopenharmony_ci		return -EINVAL;
86262306a36Sopenharmony_ci	}
86362306a36Sopenharmony_ci
86462306a36Sopenharmony_ci	/* Finish setting up command based on transfer direction
86562306a36Sopenharmony_ci	 */
86662306a36Sopenharmony_ci	if (cmd->read) {
86762306a36Sopenharmony_ci		if (cmd->info.datalen > SMU_I2C_READ_MAX)
86862306a36Sopenharmony_ci			return -EINVAL;
86962306a36Sopenharmony_ci		memset(cmd->info.data, 0xff, cmd->info.datalen);
87062306a36Sopenharmony_ci		cmd->scmd.data_len = 9;
87162306a36Sopenharmony_ci	} else {
87262306a36Sopenharmony_ci		if (cmd->info.datalen > SMU_I2C_WRITE_MAX)
87362306a36Sopenharmony_ci			return -EINVAL;
87462306a36Sopenharmony_ci		cmd->scmd.data_len = 9 + cmd->info.datalen;
87562306a36Sopenharmony_ci	}
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_ci	DPRINTK("SMU: i2c enqueuing command\n");
87862306a36Sopenharmony_ci	DPRINTK("SMU:   %s, len=%d bus=%x addr=%x sub0=%x type=%x\n",
87962306a36Sopenharmony_ci		cmd->read ? "read" : "write", cmd->info.datalen,
88062306a36Sopenharmony_ci		cmd->info.bus, cmd->info.caddr,
88162306a36Sopenharmony_ci		cmd->info.subaddr[0], cmd->info.type);
88262306a36Sopenharmony_ci
88362306a36Sopenharmony_ci
88462306a36Sopenharmony_ci	/* Enqueue command in i2c list, and if empty, enqueue also in
88562306a36Sopenharmony_ci	 * main command list
88662306a36Sopenharmony_ci	 */
88762306a36Sopenharmony_ci	spin_lock_irqsave(&smu->lock, flags);
88862306a36Sopenharmony_ci	if (smu->cmd_i2c_cur == NULL) {
88962306a36Sopenharmony_ci		smu->cmd_i2c_cur = cmd;
89062306a36Sopenharmony_ci		list_add_tail(&cmd->scmd.link, &smu->cmd_list);
89162306a36Sopenharmony_ci		if (smu->cmd_cur == NULL)
89262306a36Sopenharmony_ci			smu_start_cmd();
89362306a36Sopenharmony_ci	} else
89462306a36Sopenharmony_ci		list_add_tail(&cmd->link, &smu->cmd_i2c_list);
89562306a36Sopenharmony_ci	spin_unlock_irqrestore(&smu->lock, flags);
89662306a36Sopenharmony_ci
89762306a36Sopenharmony_ci	return 0;
89862306a36Sopenharmony_ci}
89962306a36Sopenharmony_ci
90062306a36Sopenharmony_ci/*
90162306a36Sopenharmony_ci * Handling of "partitions"
90262306a36Sopenharmony_ci */
90362306a36Sopenharmony_ci
90462306a36Sopenharmony_cistatic int smu_read_datablock(u8 *dest, unsigned int addr, unsigned int len)
90562306a36Sopenharmony_ci{
90662306a36Sopenharmony_ci	DECLARE_COMPLETION_ONSTACK(comp);
90762306a36Sopenharmony_ci	unsigned int chunk;
90862306a36Sopenharmony_ci	struct smu_cmd cmd;
90962306a36Sopenharmony_ci	int rc;
91062306a36Sopenharmony_ci	u8 params[8];
91162306a36Sopenharmony_ci
91262306a36Sopenharmony_ci	/* We currently use a chunk size of 0xe. We could check the
91362306a36Sopenharmony_ci	 * SMU firmware version and use bigger sizes though
91462306a36Sopenharmony_ci	 */
91562306a36Sopenharmony_ci	chunk = 0xe;
91662306a36Sopenharmony_ci
91762306a36Sopenharmony_ci	while (len) {
91862306a36Sopenharmony_ci		unsigned int clen = min(len, chunk);
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_ci		cmd.cmd = SMU_CMD_MISC_ee_COMMAND;
92162306a36Sopenharmony_ci		cmd.data_len = 7;
92262306a36Sopenharmony_ci		cmd.data_buf = params;
92362306a36Sopenharmony_ci		cmd.reply_len = chunk;
92462306a36Sopenharmony_ci		cmd.reply_buf = dest;
92562306a36Sopenharmony_ci		cmd.done = smu_done_complete;
92662306a36Sopenharmony_ci		cmd.misc = &comp;
92762306a36Sopenharmony_ci		params[0] = SMU_CMD_MISC_ee_GET_DATABLOCK_REC;
92862306a36Sopenharmony_ci		params[1] = 0x4;
92962306a36Sopenharmony_ci		*((u32 *)&params[2]) = addr;
93062306a36Sopenharmony_ci		params[6] = clen;
93162306a36Sopenharmony_ci
93262306a36Sopenharmony_ci		rc = smu_queue_cmd(&cmd);
93362306a36Sopenharmony_ci		if (rc)
93462306a36Sopenharmony_ci			return rc;
93562306a36Sopenharmony_ci		wait_for_completion(&comp);
93662306a36Sopenharmony_ci		if (cmd.status != 0)
93762306a36Sopenharmony_ci			return rc;
93862306a36Sopenharmony_ci		if (cmd.reply_len != clen) {
93962306a36Sopenharmony_ci			printk(KERN_DEBUG "SMU: short read in "
94062306a36Sopenharmony_ci			       "smu_read_datablock, got: %d, want: %d\n",
94162306a36Sopenharmony_ci			       cmd.reply_len, clen);
94262306a36Sopenharmony_ci			return -EIO;
94362306a36Sopenharmony_ci		}
94462306a36Sopenharmony_ci		len -= clen;
94562306a36Sopenharmony_ci		addr += clen;
94662306a36Sopenharmony_ci		dest += clen;
94762306a36Sopenharmony_ci	}
94862306a36Sopenharmony_ci	return 0;
94962306a36Sopenharmony_ci}
95062306a36Sopenharmony_ci
95162306a36Sopenharmony_cistatic struct smu_sdbp_header *smu_create_sdb_partition(int id)
95262306a36Sopenharmony_ci{
95362306a36Sopenharmony_ci	DECLARE_COMPLETION_ONSTACK(comp);
95462306a36Sopenharmony_ci	struct smu_simple_cmd cmd;
95562306a36Sopenharmony_ci	unsigned int addr, len, tlen;
95662306a36Sopenharmony_ci	struct smu_sdbp_header *hdr;
95762306a36Sopenharmony_ci	struct property *prop;
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_ci	/* First query the partition info */
96062306a36Sopenharmony_ci	DPRINTK("SMU: Query partition infos ... (irq=%d)\n", smu->db_irq);
96162306a36Sopenharmony_ci	smu_queue_simple(&cmd, SMU_CMD_PARTITION_COMMAND, 2,
96262306a36Sopenharmony_ci			 smu_done_complete, &comp,
96362306a36Sopenharmony_ci			 SMU_CMD_PARTITION_LATEST, id);
96462306a36Sopenharmony_ci	wait_for_completion(&comp);
96562306a36Sopenharmony_ci	DPRINTK("SMU: done, status: %d, reply_len: %d\n",
96662306a36Sopenharmony_ci		cmd.cmd.status, cmd.cmd.reply_len);
96762306a36Sopenharmony_ci
96862306a36Sopenharmony_ci	/* Partition doesn't exist (or other error) */
96962306a36Sopenharmony_ci	if (cmd.cmd.status != 0 || cmd.cmd.reply_len != 6)
97062306a36Sopenharmony_ci		return NULL;
97162306a36Sopenharmony_ci
97262306a36Sopenharmony_ci	/* Fetch address and length from reply */
97362306a36Sopenharmony_ci	addr = *((u16 *)cmd.buffer);
97462306a36Sopenharmony_ci	len = cmd.buffer[3] << 2;
97562306a36Sopenharmony_ci	/* Calucluate total length to allocate, including the 17 bytes
97662306a36Sopenharmony_ci	 * for "sdb-partition-XX" that we append at the end of the buffer
97762306a36Sopenharmony_ci	 */
97862306a36Sopenharmony_ci	tlen = sizeof(struct property) + len + 18;
97962306a36Sopenharmony_ci
98062306a36Sopenharmony_ci	prop = kzalloc(tlen, GFP_KERNEL);
98162306a36Sopenharmony_ci	if (prop == NULL)
98262306a36Sopenharmony_ci		return NULL;
98362306a36Sopenharmony_ci	hdr = (struct smu_sdbp_header *)(prop + 1);
98462306a36Sopenharmony_ci	prop->name = ((char *)prop) + tlen - 18;
98562306a36Sopenharmony_ci	sprintf(prop->name, "sdb-partition-%02x", id);
98662306a36Sopenharmony_ci	prop->length = len;
98762306a36Sopenharmony_ci	prop->value = hdr;
98862306a36Sopenharmony_ci	prop->next = NULL;
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_ci	/* Read the datablock */
99162306a36Sopenharmony_ci	if (smu_read_datablock((u8 *)hdr, addr, len)) {
99262306a36Sopenharmony_ci		printk(KERN_DEBUG "SMU: datablock read failed while reading "
99362306a36Sopenharmony_ci		       "partition %02x !\n", id);
99462306a36Sopenharmony_ci		goto failure;
99562306a36Sopenharmony_ci	}
99662306a36Sopenharmony_ci
99762306a36Sopenharmony_ci	/* Got it, check a few things and create the property */
99862306a36Sopenharmony_ci	if (hdr->id != id) {
99962306a36Sopenharmony_ci		printk(KERN_DEBUG "SMU: Reading partition %02x and got "
100062306a36Sopenharmony_ci		       "%02x !\n", id, hdr->id);
100162306a36Sopenharmony_ci		goto failure;
100262306a36Sopenharmony_ci	}
100362306a36Sopenharmony_ci	if (of_add_property(smu->of_node, prop)) {
100462306a36Sopenharmony_ci		printk(KERN_DEBUG "SMU: Failed creating sdb-partition-%02x "
100562306a36Sopenharmony_ci		       "property !\n", id);
100662306a36Sopenharmony_ci		goto failure;
100762306a36Sopenharmony_ci	}
100862306a36Sopenharmony_ci
100962306a36Sopenharmony_ci	return hdr;
101062306a36Sopenharmony_ci failure:
101162306a36Sopenharmony_ci	kfree(prop);
101262306a36Sopenharmony_ci	return NULL;
101362306a36Sopenharmony_ci}
101462306a36Sopenharmony_ci
101562306a36Sopenharmony_ci/* Note: Only allowed to return error code in pointers (using ERR_PTR)
101662306a36Sopenharmony_ci * when interruptible is 1
101762306a36Sopenharmony_ci */
101862306a36Sopenharmony_cistatic const struct smu_sdbp_header *__smu_get_sdb_partition(int id,
101962306a36Sopenharmony_ci		unsigned int *size, int interruptible)
102062306a36Sopenharmony_ci{
102162306a36Sopenharmony_ci	char pname[32];
102262306a36Sopenharmony_ci	const struct smu_sdbp_header *part;
102362306a36Sopenharmony_ci
102462306a36Sopenharmony_ci	if (!smu)
102562306a36Sopenharmony_ci		return NULL;
102662306a36Sopenharmony_ci
102762306a36Sopenharmony_ci	sprintf(pname, "sdb-partition-%02x", id);
102862306a36Sopenharmony_ci
102962306a36Sopenharmony_ci	DPRINTK("smu_get_sdb_partition(%02x)\n", id);
103062306a36Sopenharmony_ci
103162306a36Sopenharmony_ci	if (interruptible) {
103262306a36Sopenharmony_ci		int rc;
103362306a36Sopenharmony_ci		rc = mutex_lock_interruptible(&smu_part_access);
103462306a36Sopenharmony_ci		if (rc)
103562306a36Sopenharmony_ci			return ERR_PTR(rc);
103662306a36Sopenharmony_ci	} else
103762306a36Sopenharmony_ci		mutex_lock(&smu_part_access);
103862306a36Sopenharmony_ci
103962306a36Sopenharmony_ci	part = of_get_property(smu->of_node, pname, size);
104062306a36Sopenharmony_ci	if (part == NULL) {
104162306a36Sopenharmony_ci		DPRINTK("trying to extract from SMU ...\n");
104262306a36Sopenharmony_ci		part = smu_create_sdb_partition(id);
104362306a36Sopenharmony_ci		if (part != NULL && size)
104462306a36Sopenharmony_ci			*size = part->len << 2;
104562306a36Sopenharmony_ci	}
104662306a36Sopenharmony_ci	mutex_unlock(&smu_part_access);
104762306a36Sopenharmony_ci	return part;
104862306a36Sopenharmony_ci}
104962306a36Sopenharmony_ci
105062306a36Sopenharmony_ciconst struct smu_sdbp_header *smu_get_sdb_partition(int id, unsigned int *size)
105162306a36Sopenharmony_ci{
105262306a36Sopenharmony_ci	return __smu_get_sdb_partition(id, size, 0);
105362306a36Sopenharmony_ci}
105462306a36Sopenharmony_ciEXPORT_SYMBOL(smu_get_sdb_partition);
105562306a36Sopenharmony_ci
105662306a36Sopenharmony_ci
105762306a36Sopenharmony_ci/*
105862306a36Sopenharmony_ci * Userland driver interface
105962306a36Sopenharmony_ci */
106062306a36Sopenharmony_ci
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_cistatic LIST_HEAD(smu_clist);
106362306a36Sopenharmony_cistatic DEFINE_SPINLOCK(smu_clist_lock);
106462306a36Sopenharmony_ci
106562306a36Sopenharmony_cienum smu_file_mode {
106662306a36Sopenharmony_ci	smu_file_commands,
106762306a36Sopenharmony_ci	smu_file_events,
106862306a36Sopenharmony_ci	smu_file_closing
106962306a36Sopenharmony_ci};
107062306a36Sopenharmony_ci
107162306a36Sopenharmony_cistruct smu_private
107262306a36Sopenharmony_ci{
107362306a36Sopenharmony_ci	struct list_head	list;
107462306a36Sopenharmony_ci	enum smu_file_mode	mode;
107562306a36Sopenharmony_ci	int			busy;
107662306a36Sopenharmony_ci	struct smu_cmd		cmd;
107762306a36Sopenharmony_ci	spinlock_t		lock;
107862306a36Sopenharmony_ci	wait_queue_head_t	wait;
107962306a36Sopenharmony_ci	u8			buffer[SMU_MAX_DATA];
108062306a36Sopenharmony_ci};
108162306a36Sopenharmony_ci
108262306a36Sopenharmony_ci
108362306a36Sopenharmony_cistatic int smu_open(struct inode *inode, struct file *file)
108462306a36Sopenharmony_ci{
108562306a36Sopenharmony_ci	struct smu_private *pp;
108662306a36Sopenharmony_ci	unsigned long flags;
108762306a36Sopenharmony_ci
108862306a36Sopenharmony_ci	pp = kzalloc(sizeof(struct smu_private), GFP_KERNEL);
108962306a36Sopenharmony_ci	if (!pp)
109062306a36Sopenharmony_ci		return -ENOMEM;
109162306a36Sopenharmony_ci	spin_lock_init(&pp->lock);
109262306a36Sopenharmony_ci	pp->mode = smu_file_commands;
109362306a36Sopenharmony_ci	init_waitqueue_head(&pp->wait);
109462306a36Sopenharmony_ci
109562306a36Sopenharmony_ci	mutex_lock(&smu_mutex);
109662306a36Sopenharmony_ci	spin_lock_irqsave(&smu_clist_lock, flags);
109762306a36Sopenharmony_ci	list_add(&pp->list, &smu_clist);
109862306a36Sopenharmony_ci	spin_unlock_irqrestore(&smu_clist_lock, flags);
109962306a36Sopenharmony_ci	file->private_data = pp;
110062306a36Sopenharmony_ci	mutex_unlock(&smu_mutex);
110162306a36Sopenharmony_ci
110262306a36Sopenharmony_ci	return 0;
110362306a36Sopenharmony_ci}
110462306a36Sopenharmony_ci
110562306a36Sopenharmony_ci
110662306a36Sopenharmony_cistatic void smu_user_cmd_done(struct smu_cmd *cmd, void *misc)
110762306a36Sopenharmony_ci{
110862306a36Sopenharmony_ci	struct smu_private *pp = misc;
110962306a36Sopenharmony_ci
111062306a36Sopenharmony_ci	wake_up_all(&pp->wait);
111162306a36Sopenharmony_ci}
111262306a36Sopenharmony_ci
111362306a36Sopenharmony_ci
111462306a36Sopenharmony_cistatic ssize_t smu_write(struct file *file, const char __user *buf,
111562306a36Sopenharmony_ci			 size_t count, loff_t *ppos)
111662306a36Sopenharmony_ci{
111762306a36Sopenharmony_ci	struct smu_private *pp = file->private_data;
111862306a36Sopenharmony_ci	unsigned long flags;
111962306a36Sopenharmony_ci	struct smu_user_cmd_hdr hdr;
112062306a36Sopenharmony_ci	int rc = 0;
112162306a36Sopenharmony_ci
112262306a36Sopenharmony_ci	if (pp->busy)
112362306a36Sopenharmony_ci		return -EBUSY;
112462306a36Sopenharmony_ci	else if (copy_from_user(&hdr, buf, sizeof(hdr)))
112562306a36Sopenharmony_ci		return -EFAULT;
112662306a36Sopenharmony_ci	else if (hdr.cmdtype == SMU_CMDTYPE_WANTS_EVENTS) {
112762306a36Sopenharmony_ci		pp->mode = smu_file_events;
112862306a36Sopenharmony_ci		return 0;
112962306a36Sopenharmony_ci	} else if (hdr.cmdtype == SMU_CMDTYPE_GET_PARTITION) {
113062306a36Sopenharmony_ci		const struct smu_sdbp_header *part;
113162306a36Sopenharmony_ci		part = __smu_get_sdb_partition(hdr.cmd, NULL, 1);
113262306a36Sopenharmony_ci		if (part == NULL)
113362306a36Sopenharmony_ci			return -EINVAL;
113462306a36Sopenharmony_ci		else if (IS_ERR(part))
113562306a36Sopenharmony_ci			return PTR_ERR(part);
113662306a36Sopenharmony_ci		return 0;
113762306a36Sopenharmony_ci	} else if (hdr.cmdtype != SMU_CMDTYPE_SMU)
113862306a36Sopenharmony_ci		return -EINVAL;
113962306a36Sopenharmony_ci	else if (pp->mode != smu_file_commands)
114062306a36Sopenharmony_ci		return -EBADFD;
114162306a36Sopenharmony_ci	else if (hdr.data_len > SMU_MAX_DATA)
114262306a36Sopenharmony_ci		return -EINVAL;
114362306a36Sopenharmony_ci
114462306a36Sopenharmony_ci	spin_lock_irqsave(&pp->lock, flags);
114562306a36Sopenharmony_ci	if (pp->busy) {
114662306a36Sopenharmony_ci		spin_unlock_irqrestore(&pp->lock, flags);
114762306a36Sopenharmony_ci		return -EBUSY;
114862306a36Sopenharmony_ci	}
114962306a36Sopenharmony_ci	pp->busy = 1;
115062306a36Sopenharmony_ci	pp->cmd.status = 1;
115162306a36Sopenharmony_ci	spin_unlock_irqrestore(&pp->lock, flags);
115262306a36Sopenharmony_ci
115362306a36Sopenharmony_ci	if (copy_from_user(pp->buffer, buf + sizeof(hdr), hdr.data_len)) {
115462306a36Sopenharmony_ci		pp->busy = 0;
115562306a36Sopenharmony_ci		return -EFAULT;
115662306a36Sopenharmony_ci	}
115762306a36Sopenharmony_ci
115862306a36Sopenharmony_ci	pp->cmd.cmd = hdr.cmd;
115962306a36Sopenharmony_ci	pp->cmd.data_len = hdr.data_len;
116062306a36Sopenharmony_ci	pp->cmd.reply_len = SMU_MAX_DATA;
116162306a36Sopenharmony_ci	pp->cmd.data_buf = pp->buffer;
116262306a36Sopenharmony_ci	pp->cmd.reply_buf = pp->buffer;
116362306a36Sopenharmony_ci	pp->cmd.done = smu_user_cmd_done;
116462306a36Sopenharmony_ci	pp->cmd.misc = pp;
116562306a36Sopenharmony_ci	rc = smu_queue_cmd(&pp->cmd);
116662306a36Sopenharmony_ci	if (rc < 0)
116762306a36Sopenharmony_ci		return rc;
116862306a36Sopenharmony_ci	return count;
116962306a36Sopenharmony_ci}
117062306a36Sopenharmony_ci
117162306a36Sopenharmony_ci
117262306a36Sopenharmony_cistatic ssize_t smu_read_command(struct file *file, struct smu_private *pp,
117362306a36Sopenharmony_ci				char __user *buf, size_t count)
117462306a36Sopenharmony_ci{
117562306a36Sopenharmony_ci	DECLARE_WAITQUEUE(wait, current);
117662306a36Sopenharmony_ci	struct smu_user_reply_hdr hdr;
117762306a36Sopenharmony_ci	unsigned long flags;
117862306a36Sopenharmony_ci	int size, rc = 0;
117962306a36Sopenharmony_ci
118062306a36Sopenharmony_ci	if (!pp->busy)
118162306a36Sopenharmony_ci		return 0;
118262306a36Sopenharmony_ci	if (count < sizeof(struct smu_user_reply_hdr))
118362306a36Sopenharmony_ci		return -EOVERFLOW;
118462306a36Sopenharmony_ci	spin_lock_irqsave(&pp->lock, flags);
118562306a36Sopenharmony_ci	if (pp->cmd.status == 1) {
118662306a36Sopenharmony_ci		if (file->f_flags & O_NONBLOCK) {
118762306a36Sopenharmony_ci			spin_unlock_irqrestore(&pp->lock, flags);
118862306a36Sopenharmony_ci			return -EAGAIN;
118962306a36Sopenharmony_ci		}
119062306a36Sopenharmony_ci		add_wait_queue(&pp->wait, &wait);
119162306a36Sopenharmony_ci		for (;;) {
119262306a36Sopenharmony_ci			set_current_state(TASK_INTERRUPTIBLE);
119362306a36Sopenharmony_ci			rc = 0;
119462306a36Sopenharmony_ci			if (pp->cmd.status != 1)
119562306a36Sopenharmony_ci				break;
119662306a36Sopenharmony_ci			rc = -ERESTARTSYS;
119762306a36Sopenharmony_ci			if (signal_pending(current))
119862306a36Sopenharmony_ci				break;
119962306a36Sopenharmony_ci			spin_unlock_irqrestore(&pp->lock, flags);
120062306a36Sopenharmony_ci			schedule();
120162306a36Sopenharmony_ci			spin_lock_irqsave(&pp->lock, flags);
120262306a36Sopenharmony_ci		}
120362306a36Sopenharmony_ci		set_current_state(TASK_RUNNING);
120462306a36Sopenharmony_ci		remove_wait_queue(&pp->wait, &wait);
120562306a36Sopenharmony_ci	}
120662306a36Sopenharmony_ci	spin_unlock_irqrestore(&pp->lock, flags);
120762306a36Sopenharmony_ci	if (rc)
120862306a36Sopenharmony_ci		return rc;
120962306a36Sopenharmony_ci	if (pp->cmd.status != 0)
121062306a36Sopenharmony_ci		pp->cmd.reply_len = 0;
121162306a36Sopenharmony_ci	size = sizeof(hdr) + pp->cmd.reply_len;
121262306a36Sopenharmony_ci	if (count < size)
121362306a36Sopenharmony_ci		size = count;
121462306a36Sopenharmony_ci	rc = size;
121562306a36Sopenharmony_ci	hdr.status = pp->cmd.status;
121662306a36Sopenharmony_ci	hdr.reply_len = pp->cmd.reply_len;
121762306a36Sopenharmony_ci	if (copy_to_user(buf, &hdr, sizeof(hdr)))
121862306a36Sopenharmony_ci		return -EFAULT;
121962306a36Sopenharmony_ci	size -= sizeof(hdr);
122062306a36Sopenharmony_ci	if (size && copy_to_user(buf + sizeof(hdr), pp->buffer, size))
122162306a36Sopenharmony_ci		return -EFAULT;
122262306a36Sopenharmony_ci	pp->busy = 0;
122362306a36Sopenharmony_ci
122462306a36Sopenharmony_ci	return rc;
122562306a36Sopenharmony_ci}
122662306a36Sopenharmony_ci
122762306a36Sopenharmony_ci
122862306a36Sopenharmony_cistatic ssize_t smu_read_events(struct file *file, struct smu_private *pp,
122962306a36Sopenharmony_ci			       char __user *buf, size_t count)
123062306a36Sopenharmony_ci{
123162306a36Sopenharmony_ci	/* Not implemented */
123262306a36Sopenharmony_ci	msleep_interruptible(1000);
123362306a36Sopenharmony_ci	return 0;
123462306a36Sopenharmony_ci}
123562306a36Sopenharmony_ci
123662306a36Sopenharmony_ci
123762306a36Sopenharmony_cistatic ssize_t smu_read(struct file *file, char __user *buf,
123862306a36Sopenharmony_ci			size_t count, loff_t *ppos)
123962306a36Sopenharmony_ci{
124062306a36Sopenharmony_ci	struct smu_private *pp = file->private_data;
124162306a36Sopenharmony_ci
124262306a36Sopenharmony_ci	if (pp->mode == smu_file_commands)
124362306a36Sopenharmony_ci		return smu_read_command(file, pp, buf, count);
124462306a36Sopenharmony_ci	if (pp->mode == smu_file_events)
124562306a36Sopenharmony_ci		return smu_read_events(file, pp, buf, count);
124662306a36Sopenharmony_ci
124762306a36Sopenharmony_ci	return -EBADFD;
124862306a36Sopenharmony_ci}
124962306a36Sopenharmony_ci
125062306a36Sopenharmony_cistatic __poll_t smu_fpoll(struct file *file, poll_table *wait)
125162306a36Sopenharmony_ci{
125262306a36Sopenharmony_ci	struct smu_private *pp = file->private_data;
125362306a36Sopenharmony_ci	__poll_t mask = 0;
125462306a36Sopenharmony_ci	unsigned long flags;
125562306a36Sopenharmony_ci
125662306a36Sopenharmony_ci	if (!pp)
125762306a36Sopenharmony_ci		return 0;
125862306a36Sopenharmony_ci
125962306a36Sopenharmony_ci	if (pp->mode == smu_file_commands) {
126062306a36Sopenharmony_ci		poll_wait(file, &pp->wait, wait);
126162306a36Sopenharmony_ci
126262306a36Sopenharmony_ci		spin_lock_irqsave(&pp->lock, flags);
126362306a36Sopenharmony_ci		if (pp->busy && pp->cmd.status != 1)
126462306a36Sopenharmony_ci			mask |= EPOLLIN;
126562306a36Sopenharmony_ci		spin_unlock_irqrestore(&pp->lock, flags);
126662306a36Sopenharmony_ci	}
126762306a36Sopenharmony_ci	if (pp->mode == smu_file_events) {
126862306a36Sopenharmony_ci		/* Not yet implemented */
126962306a36Sopenharmony_ci	}
127062306a36Sopenharmony_ci	return mask;
127162306a36Sopenharmony_ci}
127262306a36Sopenharmony_ci
127362306a36Sopenharmony_cistatic int smu_release(struct inode *inode, struct file *file)
127462306a36Sopenharmony_ci{
127562306a36Sopenharmony_ci	struct smu_private *pp = file->private_data;
127662306a36Sopenharmony_ci	unsigned long flags;
127762306a36Sopenharmony_ci	unsigned int busy;
127862306a36Sopenharmony_ci
127962306a36Sopenharmony_ci	if (!pp)
128062306a36Sopenharmony_ci		return 0;
128162306a36Sopenharmony_ci
128262306a36Sopenharmony_ci	file->private_data = NULL;
128362306a36Sopenharmony_ci
128462306a36Sopenharmony_ci	/* Mark file as closing to avoid races with new request */
128562306a36Sopenharmony_ci	spin_lock_irqsave(&pp->lock, flags);
128662306a36Sopenharmony_ci	pp->mode = smu_file_closing;
128762306a36Sopenharmony_ci	busy = pp->busy;
128862306a36Sopenharmony_ci
128962306a36Sopenharmony_ci	/* Wait for any pending request to complete */
129062306a36Sopenharmony_ci	if (busy && pp->cmd.status == 1) {
129162306a36Sopenharmony_ci		DECLARE_WAITQUEUE(wait, current);
129262306a36Sopenharmony_ci
129362306a36Sopenharmony_ci		add_wait_queue(&pp->wait, &wait);
129462306a36Sopenharmony_ci		for (;;) {
129562306a36Sopenharmony_ci			set_current_state(TASK_UNINTERRUPTIBLE);
129662306a36Sopenharmony_ci			if (pp->cmd.status != 1)
129762306a36Sopenharmony_ci				break;
129862306a36Sopenharmony_ci			spin_unlock_irqrestore(&pp->lock, flags);
129962306a36Sopenharmony_ci			schedule();
130062306a36Sopenharmony_ci			spin_lock_irqsave(&pp->lock, flags);
130162306a36Sopenharmony_ci		}
130262306a36Sopenharmony_ci		set_current_state(TASK_RUNNING);
130362306a36Sopenharmony_ci		remove_wait_queue(&pp->wait, &wait);
130462306a36Sopenharmony_ci	}
130562306a36Sopenharmony_ci	spin_unlock_irqrestore(&pp->lock, flags);
130662306a36Sopenharmony_ci
130762306a36Sopenharmony_ci	spin_lock_irqsave(&smu_clist_lock, flags);
130862306a36Sopenharmony_ci	list_del(&pp->list);
130962306a36Sopenharmony_ci	spin_unlock_irqrestore(&smu_clist_lock, flags);
131062306a36Sopenharmony_ci	kfree(pp);
131162306a36Sopenharmony_ci
131262306a36Sopenharmony_ci	return 0;
131362306a36Sopenharmony_ci}
131462306a36Sopenharmony_ci
131562306a36Sopenharmony_ci
131662306a36Sopenharmony_cistatic const struct file_operations smu_device_fops = {
131762306a36Sopenharmony_ci	.llseek		= no_llseek,
131862306a36Sopenharmony_ci	.read		= smu_read,
131962306a36Sopenharmony_ci	.write		= smu_write,
132062306a36Sopenharmony_ci	.poll		= smu_fpoll,
132162306a36Sopenharmony_ci	.open		= smu_open,
132262306a36Sopenharmony_ci	.release	= smu_release,
132362306a36Sopenharmony_ci};
132462306a36Sopenharmony_ci
132562306a36Sopenharmony_cistatic struct miscdevice pmu_device = {
132662306a36Sopenharmony_ci	MISC_DYNAMIC_MINOR, "smu", &smu_device_fops
132762306a36Sopenharmony_ci};
132862306a36Sopenharmony_ci
132962306a36Sopenharmony_cistatic int smu_device_init(void)
133062306a36Sopenharmony_ci{
133162306a36Sopenharmony_ci	if (!smu)
133262306a36Sopenharmony_ci		return -ENODEV;
133362306a36Sopenharmony_ci	if (misc_register(&pmu_device) < 0)
133462306a36Sopenharmony_ci		printk(KERN_ERR "via-pmu: cannot register misc device.\n");
133562306a36Sopenharmony_ci	return 0;
133662306a36Sopenharmony_ci}
133762306a36Sopenharmony_cidevice_initcall(smu_device_init);
1338