| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  * PowerMac G5 SMU driver | 
 | 3 |  * | 
 | 4 |  * Copyright 2004 J. Mayer <l_indien@magic.fr> | 
 | 5 |  * Copyright 2005 Benjamin Herrenschmidt, IBM Corp. | 
 | 6 |  * | 
 | 7 |  * Released under the term of the GNU GPL v2. | 
 | 8 |  */ | 
 | 9 |  | 
 | 10 | /* | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 11 |  * TODO: | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 12 |  *  - maybe add timeout to commands ? | 
 | 13 |  *  - blocking version of time functions | 
 | 14 |  *  - polling version of i2c commands (including timer that works with | 
 | 15 |  *    interrutps off) | 
 | 16 |  *  - maybe avoid some data copies with i2c by directly using the smu cmd | 
 | 17 |  *    buffer and a lower level internal interface | 
 | 18 |  *  - understand SMU -> CPU events and implement reception of them via | 
 | 19 |  *    the userland interface | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 20 |  */ | 
 | 21 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 22 | #include <linux/types.h> | 
 | 23 | #include <linux/kernel.h> | 
 | 24 | #include <linux/device.h> | 
 | 25 | #include <linux/dmapool.h> | 
 | 26 | #include <linux/bootmem.h> | 
 | 27 | #include <linux/vmalloc.h> | 
 | 28 | #include <linux/highmem.h> | 
 | 29 | #include <linux/jiffies.h> | 
 | 30 | #include <linux/interrupt.h> | 
 | 31 | #include <linux/rtc.h> | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 32 | #include <linux/completion.h> | 
 | 33 | #include <linux/miscdevice.h> | 
 | 34 | #include <linux/delay.h> | 
 | 35 | #include <linux/sysdev.h> | 
 | 36 | #include <linux/poll.h> | 
| Ingo Molnar | 14cc3e2 | 2006-03-26 01:37:14 -0800 | [diff] [blame] | 37 | #include <linux/mutex.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 38 |  | 
 | 39 | #include <asm/byteorder.h> | 
 | 40 | #include <asm/io.h> | 
 | 41 | #include <asm/prom.h> | 
 | 42 | #include <asm/machdep.h> | 
 | 43 | #include <asm/pmac_feature.h> | 
 | 44 | #include <asm/smu.h> | 
 | 45 | #include <asm/sections.h> | 
 | 46 | #include <asm/abs_addr.h> | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 47 | #include <asm/uaccess.h> | 
 | 48 | #include <asm/of_device.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 49 |  | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 50 | #define VERSION "0.7" | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 51 | #define AUTHOR  "(c) 2005 Benjamin Herrenschmidt, IBM Corp." | 
 | 52 |  | 
 | 53 | #undef DEBUG_SMU | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 54 |  | 
 | 55 | #ifdef DEBUG_SMU | 
| Benjamin Herrenschmidt | 1beb6a7 | 2005-12-14 13:10:10 +1100 | [diff] [blame] | 56 | #define DPRINTK(fmt, args...) do { printk(KERN_DEBUG fmt , ##args); } while (0) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 57 | #else | 
 | 58 | #define DPRINTK(fmt, args...) do { } while (0) | 
 | 59 | #endif | 
 | 60 |  | 
 | 61 | /* | 
 | 62 |  * This is the command buffer passed to the SMU hardware | 
 | 63 |  */ | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 64 | #define SMU_MAX_DATA	254 | 
 | 65 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 66 | struct smu_cmd_buf { | 
 | 67 | 	u8 cmd; | 
 | 68 | 	u8 length; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 69 | 	u8 data[SMU_MAX_DATA]; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 70 | }; | 
 | 71 |  | 
 | 72 | struct smu_device { | 
 | 73 | 	spinlock_t		lock; | 
 | 74 | 	struct device_node	*of_node; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 75 | 	struct of_device	*of_dev; | 
 | 76 | 	int			doorbell;	/* doorbell gpio */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 77 | 	u32 __iomem		*db_buf;	/* doorbell buffer */ | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 78 | 	struct device_node	*db_node; | 
 | 79 | 	unsigned int		db_irq; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 80 | 	int			msg; | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 81 | 	struct device_node	*msg_node; | 
 | 82 | 	unsigned int		msg_irq; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 83 | 	struct smu_cmd_buf	*cmd_buf;	/* command buffer virtual */ | 
 | 84 | 	u32			cmd_buf_abs;	/* command buffer absolute */ | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 85 | 	struct list_head	cmd_list; | 
 | 86 | 	struct smu_cmd		*cmd_cur;	/* pending command */ | 
 | 87 | 	struct list_head	cmd_i2c_list; | 
 | 88 | 	struct smu_i2c_cmd	*cmd_i2c_cur;	/* pending i2c command */ | 
 | 89 | 	struct timer_list	i2c_timer; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 90 | }; | 
 | 91 |  | 
 | 92 | /* | 
 | 93 |  * I don't think there will ever be more than one SMU, so | 
 | 94 |  * for now, just hard code that | 
 | 95 |  */ | 
 | 96 | static struct smu_device	*smu; | 
| Ingo Molnar | 14cc3e2 | 2006-03-26 01:37:14 -0800 | [diff] [blame] | 97 | static DEFINE_MUTEX(smu_part_access); | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 98 | static int smu_irq_inited; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 99 |  | 
| Benjamin Herrenschmidt | 730745a | 2006-01-07 11:30:44 +1100 | [diff] [blame] | 100 | static void smu_i2c_retry(unsigned long data); | 
 | 101 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 102 | /* | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 103 |  * SMU driver low level stuff | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 104 |  */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 105 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 106 | static void smu_start_cmd(void) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 107 | { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 108 | 	unsigned long faddr, fend; | 
 | 109 | 	struct smu_cmd *cmd; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 110 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 111 | 	if (list_empty(&smu->cmd_list)) | 
 | 112 | 		return; | 
 | 113 |  | 
 | 114 | 	/* Fetch first command in queue */ | 
 | 115 | 	cmd = list_entry(smu->cmd_list.next, struct smu_cmd, link); | 
 | 116 | 	smu->cmd_cur = cmd; | 
 | 117 | 	list_del(&cmd->link); | 
 | 118 |  | 
 | 119 | 	DPRINTK("SMU: starting cmd %x, %d bytes data\n", cmd->cmd, | 
 | 120 | 		cmd->data_len); | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 121 | 	DPRINTK("SMU: data buffer: %02x %02x %02x %02x %02x %02x %02x %02x\n", | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 122 | 		((u8 *)cmd->data_buf)[0], ((u8 *)cmd->data_buf)[1], | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 123 | 		((u8 *)cmd->data_buf)[2], ((u8 *)cmd->data_buf)[3], | 
 | 124 | 		((u8 *)cmd->data_buf)[4], ((u8 *)cmd->data_buf)[5], | 
 | 125 | 		((u8 *)cmd->data_buf)[6], ((u8 *)cmd->data_buf)[7]); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 126 |  | 
 | 127 | 	/* Fill the SMU command buffer */ | 
 | 128 | 	smu->cmd_buf->cmd = cmd->cmd; | 
 | 129 | 	smu->cmd_buf->length = cmd->data_len; | 
 | 130 | 	memcpy(smu->cmd_buf->data, cmd->data_buf, cmd->data_len); | 
 | 131 |  | 
 | 132 | 	/* Flush command and data to RAM */ | 
 | 133 | 	faddr = (unsigned long)smu->cmd_buf; | 
 | 134 | 	fend = faddr + smu->cmd_buf->length + 2; | 
 | 135 | 	flush_inval_dcache_range(faddr, fend); | 
 | 136 |  | 
 | 137 | 	/* This isn't exactly a DMA mapping here, I suspect | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 138 | 	 * the SMU is actually communicating with us via i2c to the | 
 | 139 | 	 * northbridge or the CPU to access RAM. | 
 | 140 | 	 */ | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 141 | 	writel(smu->cmd_buf_abs, smu->db_buf); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 142 |  | 
 | 143 | 	/* Ring the SMU doorbell */ | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 144 | 	pmac_do_feature_call(PMAC_FTR_WRITE_GPIO, NULL, smu->doorbell, 4); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 145 | } | 
 | 146 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 147 |  | 
 | 148 | static irqreturn_t smu_db_intr(int irq, void *arg, struct pt_regs *regs) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 149 | { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 150 | 	unsigned long flags; | 
 | 151 | 	struct smu_cmd *cmd; | 
 | 152 | 	void (*done)(struct smu_cmd *cmd, void *misc) = NULL; | 
 | 153 | 	void *misc = NULL; | 
 | 154 | 	u8 gpio; | 
 | 155 | 	int rc = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 156 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 157 | 	/* SMU completed the command, well, we hope, let's make sure | 
 | 158 | 	 * of it | 
 | 159 | 	 */ | 
 | 160 | 	spin_lock_irqsave(&smu->lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 161 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 162 | 	gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, smu->doorbell); | 
| Benjamin Herrenschmidt | a44fe13 | 2005-09-30 08:25:17 +1000 | [diff] [blame] | 163 | 	if ((gpio & 7) != 7) { | 
 | 164 | 		spin_unlock_irqrestore(&smu->lock, flags); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 165 | 		return IRQ_HANDLED; | 
| Benjamin Herrenschmidt | a44fe13 | 2005-09-30 08:25:17 +1000 | [diff] [blame] | 166 | 	} | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 167 |  | 
 | 168 | 	cmd = smu->cmd_cur; | 
 | 169 | 	smu->cmd_cur = NULL; | 
 | 170 | 	if (cmd == NULL) | 
 | 171 | 		goto bail; | 
 | 172 |  | 
 | 173 | 	if (rc == 0) { | 
 | 174 | 		unsigned long faddr; | 
 | 175 | 		int reply_len; | 
 | 176 | 		u8 ack; | 
 | 177 |  | 
 | 178 | 		/* CPU might have brought back the cache line, so we need | 
 | 179 | 		 * to flush again before peeking at the SMU response. We | 
 | 180 | 		 * flush the entire buffer for now as we haven't read the | 
 | 181 | 		 * reply lenght (it's only 2 cache lines anyway) | 
 | 182 | 		 */ | 
 | 183 | 		faddr = (unsigned long)smu->cmd_buf; | 
 | 184 | 		flush_inval_dcache_range(faddr, faddr + 256); | 
 | 185 |  | 
 | 186 | 		/* Now check ack */ | 
 | 187 | 		ack = (~cmd->cmd) & 0xff; | 
 | 188 | 		if (ack != smu->cmd_buf->cmd) { | 
 | 189 | 			DPRINTK("SMU: incorrect ack, want %x got %x\n", | 
 | 190 | 				ack, smu->cmd_buf->cmd); | 
 | 191 | 			rc = -EIO; | 
 | 192 | 		} | 
 | 193 | 		reply_len = rc == 0 ? smu->cmd_buf->length : 0; | 
 | 194 | 		DPRINTK("SMU: reply len: %d\n", reply_len); | 
 | 195 | 		if (reply_len > cmd->reply_len) { | 
 | 196 | 			printk(KERN_WARNING "SMU: reply buffer too small," | 
 | 197 | 			       "got %d bytes for a %d bytes buffer\n", | 
 | 198 | 			       reply_len, cmd->reply_len); | 
 | 199 | 			reply_len = cmd->reply_len; | 
 | 200 | 		} | 
 | 201 | 		cmd->reply_len = reply_len; | 
 | 202 | 		if (cmd->reply_buf && reply_len) | 
 | 203 | 			memcpy(cmd->reply_buf, smu->cmd_buf->data, reply_len); | 
 | 204 | 	} | 
 | 205 |  | 
 | 206 | 	/* Now complete the command. Write status last in order as we lost | 
 | 207 | 	 * ownership of the command structure as soon as it's no longer -1 | 
 | 208 | 	 */ | 
 | 209 | 	done = cmd->done; | 
 | 210 | 	misc = cmd->misc; | 
 | 211 | 	mb(); | 
 | 212 | 	cmd->status = rc; | 
 | 213 |  bail: | 
 | 214 | 	/* Start next command if any */ | 
 | 215 | 	smu_start_cmd(); | 
 | 216 | 	spin_unlock_irqrestore(&smu->lock, flags); | 
 | 217 |  | 
 | 218 | 	/* Call command completion handler if any */ | 
 | 219 | 	if (done) | 
 | 220 | 		done(cmd, misc); | 
 | 221 |  | 
 | 222 | 	/* It's an edge interrupt, nothing to do */ | 
 | 223 | 	return IRQ_HANDLED; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 224 | } | 
 | 225 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 226 |  | 
 | 227 | static irqreturn_t smu_msg_intr(int irq, void *arg, struct pt_regs *regs) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 228 | { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 229 | 	/* I don't quite know what to do with this one, we seem to never | 
 | 230 | 	 * receive it, so I suspect we have to arm it someway in the SMU | 
 | 231 | 	 * to start getting events that way. | 
 | 232 | 	 */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 233 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 234 | 	printk(KERN_INFO "SMU: message interrupt !\n"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 235 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 236 | 	/* It's an edge interrupt, nothing to do */ | 
 | 237 | 	return IRQ_HANDLED; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 238 | } | 
 | 239 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 240 |  | 
 | 241 | /* | 
 | 242 |  * Queued command management. | 
 | 243 |  * | 
 | 244 |  */ | 
 | 245 |  | 
 | 246 | int smu_queue_cmd(struct smu_cmd *cmd) | 
 | 247 | { | 
 | 248 | 	unsigned long flags; | 
 | 249 |  | 
 | 250 | 	if (smu == NULL) | 
 | 251 | 		return -ENODEV; | 
 | 252 | 	if (cmd->data_len > SMU_MAX_DATA || | 
 | 253 | 	    cmd->reply_len > SMU_MAX_DATA) | 
 | 254 | 		return -EINVAL; | 
 | 255 |  | 
 | 256 | 	cmd->status = 1; | 
 | 257 | 	spin_lock_irqsave(&smu->lock, flags); | 
 | 258 | 	list_add_tail(&cmd->link, &smu->cmd_list); | 
 | 259 | 	if (smu->cmd_cur == NULL) | 
 | 260 | 		smu_start_cmd(); | 
 | 261 | 	spin_unlock_irqrestore(&smu->lock, flags); | 
 | 262 |  | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 263 | 	/* Workaround for early calls when irq isn't available */ | 
 | 264 | 	if (!smu_irq_inited || smu->db_irq == NO_IRQ) | 
 | 265 | 		smu_spinwait_cmd(cmd); | 
 | 266 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 267 | 	return 0; | 
 | 268 | } | 
 | 269 | EXPORT_SYMBOL(smu_queue_cmd); | 
 | 270 |  | 
 | 271 |  | 
 | 272 | int smu_queue_simple(struct smu_simple_cmd *scmd, u8 command, | 
 | 273 | 		     unsigned int data_len, | 
 | 274 | 		     void (*done)(struct smu_cmd *cmd, void *misc), | 
 | 275 | 		     void *misc, ...) | 
 | 276 | { | 
 | 277 | 	struct smu_cmd *cmd = &scmd->cmd; | 
 | 278 | 	va_list list; | 
 | 279 | 	int i; | 
 | 280 |  | 
 | 281 | 	if (data_len > sizeof(scmd->buffer)) | 
 | 282 | 		return -EINVAL; | 
 | 283 |  | 
 | 284 | 	memset(scmd, 0, sizeof(*scmd)); | 
 | 285 | 	cmd->cmd = command; | 
 | 286 | 	cmd->data_len = data_len; | 
 | 287 | 	cmd->data_buf = scmd->buffer; | 
 | 288 | 	cmd->reply_len = sizeof(scmd->buffer); | 
 | 289 | 	cmd->reply_buf = scmd->buffer; | 
 | 290 | 	cmd->done = done; | 
 | 291 | 	cmd->misc = misc; | 
 | 292 |  | 
 | 293 | 	va_start(list, misc); | 
 | 294 | 	for (i = 0; i < data_len; ++i) | 
 | 295 | 		scmd->buffer[i] = (u8)va_arg(list, int); | 
 | 296 | 	va_end(list); | 
 | 297 |  | 
 | 298 | 	return smu_queue_cmd(cmd); | 
 | 299 | } | 
 | 300 | EXPORT_SYMBOL(smu_queue_simple); | 
 | 301 |  | 
 | 302 |  | 
 | 303 | void smu_poll(void) | 
 | 304 | { | 
 | 305 | 	u8 gpio; | 
 | 306 |  | 
 | 307 | 	if (smu == NULL) | 
 | 308 | 		return; | 
 | 309 |  | 
 | 310 | 	gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, smu->doorbell); | 
 | 311 | 	if ((gpio & 7) == 7) | 
 | 312 | 		smu_db_intr(smu->db_irq, smu, NULL); | 
 | 313 | } | 
 | 314 | EXPORT_SYMBOL(smu_poll); | 
 | 315 |  | 
 | 316 |  | 
 | 317 | void smu_done_complete(struct smu_cmd *cmd, void *misc) | 
 | 318 | { | 
 | 319 | 	struct completion *comp = misc; | 
 | 320 |  | 
 | 321 | 	complete(comp); | 
 | 322 | } | 
 | 323 | EXPORT_SYMBOL(smu_done_complete); | 
 | 324 |  | 
 | 325 |  | 
 | 326 | void smu_spinwait_cmd(struct smu_cmd *cmd) | 
 | 327 | { | 
 | 328 | 	while(cmd->status == 1) | 
 | 329 | 		smu_poll(); | 
 | 330 | } | 
 | 331 | EXPORT_SYMBOL(smu_spinwait_cmd); | 
 | 332 |  | 
 | 333 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 334 | /* RTC low level commands */ | 
 | 335 | static inline int bcd2hex (int n) | 
 | 336 | { | 
 | 337 | 	return (((n & 0xf0) >> 4) * 10) + (n & 0xf); | 
 | 338 | } | 
 | 339 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 340 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 341 | static inline int hex2bcd (int n) | 
 | 342 | { | 
 | 343 | 	return ((n / 10) << 4) + (n % 10); | 
 | 344 | } | 
 | 345 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 346 |  | 
 | 347 | static inline void smu_fill_set_rtc_cmd(struct smu_cmd_buf *cmd_buf, | 
 | 348 | 					struct rtc_time *time) | 
 | 349 | { | 
 | 350 | 	cmd_buf->cmd = 0x8e; | 
 | 351 | 	cmd_buf->length = 8; | 
 | 352 | 	cmd_buf->data[0] = 0x80; | 
 | 353 | 	cmd_buf->data[1] = hex2bcd(time->tm_sec); | 
 | 354 | 	cmd_buf->data[2] = hex2bcd(time->tm_min); | 
 | 355 | 	cmd_buf->data[3] = hex2bcd(time->tm_hour); | 
 | 356 | 	cmd_buf->data[4] = time->tm_wday; | 
 | 357 | 	cmd_buf->data[5] = hex2bcd(time->tm_mday); | 
 | 358 | 	cmd_buf->data[6] = hex2bcd(time->tm_mon) + 1; | 
 | 359 | 	cmd_buf->data[7] = hex2bcd(time->tm_year - 100); | 
 | 360 | } | 
 | 361 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 362 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 363 | int smu_get_rtc_time(struct rtc_time *time, int spinwait) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 364 | { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 365 | 	struct smu_simple_cmd cmd; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 366 | 	int rc; | 
 | 367 |  | 
 | 368 | 	if (smu == NULL) | 
 | 369 | 		return -ENODEV; | 
 | 370 |  | 
 | 371 | 	memset(time, 0, sizeof(struct rtc_time)); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 372 | 	rc = smu_queue_simple(&cmd, SMU_CMD_RTC_COMMAND, 1, NULL, NULL, | 
 | 373 | 			      SMU_CMD_RTC_GET_DATETIME); | 
 | 374 | 	if (rc) | 
 | 375 | 		return rc; | 
 | 376 | 	smu_spinwait_simple(&cmd); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 377 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 378 | 	time->tm_sec = bcd2hex(cmd.buffer[0]); | 
 | 379 | 	time->tm_min = bcd2hex(cmd.buffer[1]); | 
 | 380 | 	time->tm_hour = bcd2hex(cmd.buffer[2]); | 
 | 381 | 	time->tm_wday = bcd2hex(cmd.buffer[3]); | 
 | 382 | 	time->tm_mday = bcd2hex(cmd.buffer[4]); | 
 | 383 | 	time->tm_mon = bcd2hex(cmd.buffer[5]) - 1; | 
 | 384 | 	time->tm_year = bcd2hex(cmd.buffer[6]) + 100; | 
 | 385 |  | 
 | 386 | 	return 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 387 | } | 
 | 388 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 389 |  | 
 | 390 | int smu_set_rtc_time(struct rtc_time *time, int spinwait) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 391 | { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 392 | 	struct smu_simple_cmd cmd; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 393 | 	int rc; | 
 | 394 |  | 
 | 395 | 	if (smu == NULL) | 
 | 396 | 		return -ENODEV; | 
 | 397 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 398 | 	rc = smu_queue_simple(&cmd, SMU_CMD_RTC_COMMAND, 8, NULL, NULL, | 
 | 399 | 			      SMU_CMD_RTC_SET_DATETIME, | 
 | 400 | 			      hex2bcd(time->tm_sec), | 
 | 401 | 			      hex2bcd(time->tm_min), | 
 | 402 | 			      hex2bcd(time->tm_hour), | 
 | 403 | 			      time->tm_wday, | 
 | 404 | 			      hex2bcd(time->tm_mday), | 
 | 405 | 			      hex2bcd(time->tm_mon) + 1, | 
 | 406 | 			      hex2bcd(time->tm_year - 100)); | 
 | 407 | 	if (rc) | 
 | 408 | 		return rc; | 
 | 409 | 	smu_spinwait_simple(&cmd); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 410 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 411 | 	return 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 412 | } | 
 | 413 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 414 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 415 | void smu_shutdown(void) | 
 | 416 | { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 417 | 	struct smu_simple_cmd cmd; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 418 |  | 
 | 419 | 	if (smu == NULL) | 
 | 420 | 		return; | 
 | 421 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 422 | 	if (smu_queue_simple(&cmd, SMU_CMD_POWER_COMMAND, 9, NULL, NULL, | 
 | 423 | 			     'S', 'H', 'U', 'T', 'D', 'O', 'W', 'N', 0)) | 
 | 424 | 		return; | 
 | 425 | 	smu_spinwait_simple(&cmd); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 426 | 	for (;;) | 
 | 427 | 		; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 428 | } | 
 | 429 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 430 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 431 | void smu_restart(void) | 
 | 432 | { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 433 | 	struct smu_simple_cmd cmd; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 434 |  | 
 | 435 | 	if (smu == NULL) | 
 | 436 | 		return; | 
 | 437 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 438 | 	if (smu_queue_simple(&cmd, SMU_CMD_POWER_COMMAND, 8, NULL, NULL, | 
 | 439 | 			     'R', 'E', 'S', 'T', 'A', 'R', 'T', 0)) | 
 | 440 | 		return; | 
 | 441 | 	smu_spinwait_simple(&cmd); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 442 | 	for (;;) | 
 | 443 | 		; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 444 | } | 
 | 445 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 446 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 447 | int smu_present(void) | 
 | 448 | { | 
 | 449 | 	return smu != NULL; | 
 | 450 | } | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 451 | EXPORT_SYMBOL(smu_present); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 452 |  | 
 | 453 |  | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 454 | int __init smu_init (void) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 455 | { | 
 | 456 | 	struct device_node *np; | 
 | 457 | 	u32 *data; | 
 | 458 |  | 
 | 459 |         np = of_find_node_by_type(NULL, "smu"); | 
 | 460 |         if (np == NULL) | 
 | 461 | 		return -ENODEV; | 
 | 462 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 463 | 	printk(KERN_INFO "SMU driver %s %s\n", VERSION, AUTHOR); | 
 | 464 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 465 | 	if (smu_cmdbuf_abs == 0) { | 
 | 466 | 		printk(KERN_ERR "SMU: Command buffer not allocated !\n"); | 
 | 467 | 		return -EINVAL; | 
 | 468 | 	} | 
 | 469 |  | 
 | 470 | 	smu = alloc_bootmem(sizeof(struct smu_device)); | 
 | 471 | 	if (smu == NULL) | 
 | 472 | 		return -ENOMEM; | 
 | 473 | 	memset(smu, 0, sizeof(*smu)); | 
 | 474 |  | 
 | 475 | 	spin_lock_init(&smu->lock); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 476 | 	INIT_LIST_HEAD(&smu->cmd_list); | 
 | 477 | 	INIT_LIST_HEAD(&smu->cmd_i2c_list); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 478 | 	smu->of_node = np; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 479 | 	smu->db_irq = NO_IRQ; | 
 | 480 | 	smu->msg_irq = NO_IRQ; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 481 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 482 | 	/* smu_cmdbuf_abs is in the low 2G of RAM, can be converted to a | 
 | 483 | 	 * 32 bits value safely | 
 | 484 | 	 */ | 
 | 485 | 	smu->cmd_buf_abs = (u32)smu_cmdbuf_abs; | 
 | 486 | 	smu->cmd_buf = (struct smu_cmd_buf *)abs_to_virt(smu_cmdbuf_abs); | 
 | 487 |  | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 488 | 	smu->db_node = of_find_node_by_name(NULL, "smu-doorbell"); | 
 | 489 | 	if (smu->db_node == NULL) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 490 | 		printk(KERN_ERR "SMU: Can't find doorbell GPIO !\n"); | 
 | 491 | 		goto fail; | 
 | 492 | 	} | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 493 | 	data = (u32 *)get_property(smu->db_node, "reg", NULL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 494 | 	if (data == NULL) { | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 495 | 		of_node_put(smu->db_node); | 
 | 496 | 		smu->db_node = NULL; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 497 | 		printk(KERN_ERR "SMU: Can't find doorbell GPIO address !\n"); | 
 | 498 | 		goto fail; | 
 | 499 | 	} | 
 | 500 |  | 
 | 501 | 	/* Current setup has one doorbell GPIO that does both doorbell | 
 | 502 | 	 * and ack. GPIOs are at 0x50, best would be to find that out | 
 | 503 | 	 * in the device-tree though. | 
 | 504 | 	 */ | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 505 | 	smu->doorbell = *data; | 
 | 506 | 	if (smu->doorbell < 0x50) | 
 | 507 | 		smu->doorbell += 0x50; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 508 |  | 
 | 509 | 	/* Now look for the smu-interrupt GPIO */ | 
 | 510 | 	do { | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 511 | 		smu->msg_node = of_find_node_by_name(NULL, "smu-interrupt"); | 
 | 512 | 		if (smu->msg_node == NULL) | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 513 | 			break; | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 514 | 		data = (u32 *)get_property(smu->msg_node, "reg", NULL); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 515 | 		if (data == NULL) { | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 516 | 			of_node_put(smu->msg_node); | 
 | 517 | 			smu->msg_node = NULL; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 518 | 			break; | 
 | 519 | 		} | 
 | 520 | 		smu->msg = *data; | 
 | 521 | 		if (smu->msg < 0x50) | 
 | 522 | 			smu->msg += 0x50; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 523 | 	} while(0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 524 |  | 
 | 525 | 	/* Doorbell buffer is currently hard-coded, I didn't find a proper | 
 | 526 | 	 * device-tree entry giving the address. Best would probably to use | 
 | 527 | 	 * an offset for K2 base though, but let's do it that way for now. | 
 | 528 | 	 */ | 
 | 529 | 	smu->db_buf = ioremap(0x8000860c, 0x1000); | 
 | 530 | 	if (smu->db_buf == NULL) { | 
 | 531 | 		printk(KERN_ERR "SMU: Can't map doorbell buffer pointer !\n"); | 
 | 532 | 		goto fail; | 
 | 533 | 	} | 
 | 534 |  | 
 | 535 | 	sys_ctrler = SYS_CTRLER_SMU; | 
 | 536 | 	return 0; | 
 | 537 |  | 
 | 538 |  fail: | 
 | 539 | 	smu = NULL; | 
 | 540 | 	return -ENXIO; | 
 | 541 |  | 
 | 542 | } | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 543 |  | 
 | 544 |  | 
 | 545 | static int smu_late_init(void) | 
 | 546 | { | 
 | 547 | 	if (!smu) | 
 | 548 | 		return 0; | 
 | 549 |  | 
| Benjamin Herrenschmidt | 730745a | 2006-01-07 11:30:44 +1100 | [diff] [blame] | 550 | 	init_timer(&smu->i2c_timer); | 
 | 551 | 	smu->i2c_timer.function = smu_i2c_retry; | 
 | 552 | 	smu->i2c_timer.data = (unsigned long)smu; | 
 | 553 |  | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 554 | 	if (smu->db_node) { | 
 | 555 | 		smu->db_irq = irq_of_parse_and_map(smu->db_node, 0); | 
 | 556 | 		if (smu->db_irq == NO_IRQ) | 
 | 557 | 			printk(KERN_ERR "smu: failed to map irq for node %s\n", | 
 | 558 | 			       smu->db_node->full_name); | 
 | 559 | 	} | 
 | 560 | 	if (smu->msg_node) { | 
 | 561 | 		smu->msg_irq = irq_of_parse_and_map(smu->msg_node, 0); | 
 | 562 | 		if (smu->msg_irq == NO_IRQ) | 
 | 563 | 			printk(KERN_ERR "smu: failed to map irq for node %s\n", | 
 | 564 | 			       smu->msg_node->full_name); | 
 | 565 | 	} | 
 | 566 |  | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 567 | 	/* | 
 | 568 | 	 * Try to request the interrupts | 
 | 569 | 	 */ | 
 | 570 |  | 
 | 571 | 	if (smu->db_irq != NO_IRQ) { | 
 | 572 | 		if (request_irq(smu->db_irq, smu_db_intr, | 
| Thomas Gleixner | dace145 | 2006-07-01 19:29:38 -0700 | [diff] [blame] | 573 | 				IRQF_SHARED, "SMU doorbell", smu) < 0) { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 574 | 			printk(KERN_WARNING "SMU: can't " | 
 | 575 | 			       "request interrupt %d\n", | 
 | 576 | 			       smu->db_irq); | 
 | 577 | 			smu->db_irq = NO_IRQ; | 
 | 578 | 		} | 
 | 579 | 	} | 
 | 580 |  | 
 | 581 | 	if (smu->msg_irq != NO_IRQ) { | 
 | 582 | 		if (request_irq(smu->msg_irq, smu_msg_intr, | 
| Thomas Gleixner | dace145 | 2006-07-01 19:29:38 -0700 | [diff] [blame] | 583 | 				IRQF_SHARED, "SMU message", smu) < 0) { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 584 | 			printk(KERN_WARNING "SMU: can't " | 
 | 585 | 			       "request interrupt %d\n", | 
 | 586 | 			       smu->msg_irq); | 
 | 587 | 			smu->msg_irq = NO_IRQ; | 
 | 588 | 		} | 
 | 589 | 	} | 
 | 590 |  | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 591 | 	smu_irq_inited = 1; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 592 | 	return 0; | 
 | 593 | } | 
| Benjamin Herrenschmidt | 730745a | 2006-01-07 11:30:44 +1100 | [diff] [blame] | 594 | /* This has to be before arch_initcall as the low i2c stuff relies on the | 
 | 595 |  * above having been done before we reach arch_initcalls | 
 | 596 |  */ | 
 | 597 | core_initcall(smu_late_init); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 598 |  | 
 | 599 | /* | 
 | 600 |  * sysfs visibility | 
 | 601 |  */ | 
 | 602 |  | 
 | 603 | static void smu_expose_childs(void *unused) | 
 | 604 | { | 
| Benjamin Herrenschmidt | a28d3af | 2006-01-07 11:35:26 +1100 | [diff] [blame] | 605 | 	struct device_node *np; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 606 |  | 
| Benjamin Herrenschmidt | a28d3af | 2006-01-07 11:35:26 +1100 | [diff] [blame] | 607 | 	for (np = NULL; (np = of_get_next_child(smu->of_node, np)) != NULL;) | 
| Benjamin Herrenschmidt | 75722d3 | 2005-11-07 16:08:17 +1100 | [diff] [blame] | 608 | 		if (device_is_compatible(np, "smu-sensors")) | 
| Benjamin Herrenschmidt | 730745a | 2006-01-07 11:30:44 +1100 | [diff] [blame] | 609 | 			of_platform_device_create(np, "smu-sensors", | 
 | 610 | 						  &smu->of_dev->dev); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 611 | } | 
 | 612 |  | 
 | 613 | static DECLARE_WORK(smu_expose_childs_work, smu_expose_childs, NULL); | 
 | 614 |  | 
 | 615 | static int smu_platform_probe(struct of_device* dev, | 
 | 616 | 			      const struct of_device_id *match) | 
 | 617 | { | 
 | 618 | 	if (!smu) | 
 | 619 | 		return -ENODEV; | 
 | 620 | 	smu->of_dev = dev; | 
 | 621 |  | 
 | 622 | 	/* | 
 | 623 | 	 * Ok, we are matched, now expose all i2c busses. We have to defer | 
 | 624 | 	 * that unfortunately or it would deadlock inside the device model | 
 | 625 | 	 */ | 
 | 626 | 	schedule_work(&smu_expose_childs_work); | 
 | 627 |  | 
 | 628 | 	return 0; | 
 | 629 | } | 
 | 630 |  | 
 | 631 | static struct of_device_id smu_platform_match[] = | 
 | 632 | { | 
 | 633 | 	{ | 
 | 634 | 		.type		= "smu", | 
 | 635 | 	}, | 
 | 636 | 	{}, | 
 | 637 | }; | 
 | 638 |  | 
 | 639 | static struct of_platform_driver smu_of_platform_driver = | 
 | 640 | { | 
 | 641 | 	.name 		= "smu", | 
 | 642 | 	.match_table	= smu_platform_match, | 
 | 643 | 	.probe		= smu_platform_probe, | 
 | 644 | }; | 
 | 645 |  | 
 | 646 | static int __init smu_init_sysfs(void) | 
 | 647 | { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 648 | 	/* | 
 | 649 | 	 * Due to sysfs bogosity, a sysdev is not a real device, so | 
 | 650 | 	 * we should in fact create both if we want sysdev semantics | 
 | 651 | 	 * for power management. | 
 | 652 | 	 * For now, we don't power manage machines with an SMU chip, | 
 | 653 | 	 * I'm a bit too far from figuring out how that works with those | 
 | 654 | 	 * new chipsets, but that will come back and bite us | 
 | 655 | 	 */ | 
| Bjorn Helgaas | 6ea671a | 2006-03-21 23:20:27 -0800 | [diff] [blame] | 656 | 	of_register_driver(&smu_of_platform_driver); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 657 | 	return 0; | 
 | 658 | } | 
 | 659 |  | 
 | 660 | device_initcall(smu_init_sysfs); | 
 | 661 |  | 
 | 662 | struct of_device *smu_get_ofdev(void) | 
 | 663 | { | 
 | 664 | 	if (!smu) | 
 | 665 | 		return NULL; | 
 | 666 | 	return smu->of_dev; | 
 | 667 | } | 
 | 668 |  | 
 | 669 | EXPORT_SYMBOL_GPL(smu_get_ofdev); | 
 | 670 |  | 
 | 671 | /* | 
 | 672 |  * i2c interface | 
 | 673 |  */ | 
 | 674 |  | 
 | 675 | static void smu_i2c_complete_command(struct smu_i2c_cmd *cmd, int fail) | 
 | 676 | { | 
 | 677 | 	void (*done)(struct smu_i2c_cmd *cmd, void *misc) = cmd->done; | 
 | 678 | 	void *misc = cmd->misc; | 
 | 679 | 	unsigned long flags; | 
 | 680 |  | 
 | 681 | 	/* Check for read case */ | 
 | 682 | 	if (!fail && cmd->read) { | 
 | 683 | 		if (cmd->pdata[0] < 1) | 
 | 684 | 			fail = 1; | 
 | 685 | 		else | 
 | 686 | 			memcpy(cmd->info.data, &cmd->pdata[1], | 
 | 687 | 			       cmd->info.datalen); | 
 | 688 | 	} | 
 | 689 |  | 
 | 690 | 	DPRINTK("SMU: completing, success: %d\n", !fail); | 
 | 691 |  | 
 | 692 | 	/* Update status and mark no pending i2c command with lock | 
 | 693 | 	 * held so nobody comes in while we dequeue an eventual | 
 | 694 | 	 * pending next i2c command | 
 | 695 | 	 */ | 
 | 696 | 	spin_lock_irqsave(&smu->lock, flags); | 
 | 697 | 	smu->cmd_i2c_cur = NULL; | 
 | 698 | 	wmb(); | 
 | 699 | 	cmd->status = fail ? -EIO : 0; | 
 | 700 |  | 
 | 701 | 	/* Is there another i2c command waiting ? */ | 
 | 702 | 	if (!list_empty(&smu->cmd_i2c_list)) { | 
 | 703 | 		struct smu_i2c_cmd *newcmd; | 
 | 704 |  | 
 | 705 | 		/* Fetch it, new current, remove from list */ | 
 | 706 | 		newcmd = list_entry(smu->cmd_i2c_list.next, | 
 | 707 | 				    struct smu_i2c_cmd, link); | 
 | 708 | 		smu->cmd_i2c_cur = newcmd; | 
 | 709 | 		list_del(&cmd->link); | 
 | 710 |  | 
 | 711 | 		/* Queue with low level smu */ | 
 | 712 | 		list_add_tail(&cmd->scmd.link, &smu->cmd_list); | 
 | 713 | 		if (smu->cmd_cur == NULL) | 
 | 714 | 			smu_start_cmd(); | 
 | 715 | 	} | 
 | 716 | 	spin_unlock_irqrestore(&smu->lock, flags); | 
 | 717 |  | 
 | 718 | 	/* Call command completion handler if any */ | 
 | 719 | 	if (done) | 
 | 720 | 		done(cmd, misc); | 
 | 721 |  | 
 | 722 | } | 
 | 723 |  | 
 | 724 |  | 
 | 725 | static void smu_i2c_retry(unsigned long data) | 
 | 726 | { | 
| Benjamin Herrenschmidt | 730745a | 2006-01-07 11:30:44 +1100 | [diff] [blame] | 727 | 	struct smu_i2c_cmd	*cmd = smu->cmd_i2c_cur; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 728 |  | 
 | 729 | 	DPRINTK("SMU: i2c failure, requeuing...\n"); | 
 | 730 |  | 
 | 731 | 	/* requeue command simply by resetting reply_len */ | 
 | 732 | 	cmd->pdata[0] = 0xff; | 
| Benjamin Herrenschmidt | 730745a | 2006-01-07 11:30:44 +1100 | [diff] [blame] | 733 | 	cmd->scmd.reply_len = sizeof(cmd->pdata); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 734 | 	smu_queue_cmd(&cmd->scmd); | 
 | 735 | } | 
 | 736 |  | 
 | 737 |  | 
 | 738 | static void smu_i2c_low_completion(struct smu_cmd *scmd, void *misc) | 
 | 739 | { | 
 | 740 | 	struct smu_i2c_cmd	*cmd = misc; | 
 | 741 | 	int			fail = 0; | 
 | 742 |  | 
 | 743 | 	DPRINTK("SMU: i2c compl. stage=%d status=%x pdata[0]=%x rlen: %x\n", | 
 | 744 | 		cmd->stage, scmd->status, cmd->pdata[0], scmd->reply_len); | 
 | 745 |  | 
 | 746 | 	/* Check for possible status */ | 
 | 747 | 	if (scmd->status < 0) | 
 | 748 | 		fail = 1; | 
 | 749 | 	else if (cmd->read) { | 
 | 750 | 		if (cmd->stage == 0) | 
 | 751 | 			fail = cmd->pdata[0] != 0; | 
 | 752 | 		else | 
 | 753 | 			fail = cmd->pdata[0] >= 0x80; | 
 | 754 | 	} else { | 
 | 755 | 		fail = cmd->pdata[0] != 0; | 
 | 756 | 	} | 
 | 757 |  | 
 | 758 | 	/* Handle failures by requeuing command, after 5ms interval | 
 | 759 | 	 */ | 
 | 760 | 	if (fail && --cmd->retries > 0) { | 
 | 761 | 		DPRINTK("SMU: i2c failure, starting timer...\n"); | 
| Benjamin Herrenschmidt | 730745a | 2006-01-07 11:30:44 +1100 | [diff] [blame] | 762 | 		BUG_ON(cmd != smu->cmd_i2c_cur); | 
| Benjamin Herrenschmidt | f620753 | 2006-07-10 04:44:44 -0700 | [diff] [blame] | 763 | 		if (!smu_irq_inited) { | 
 | 764 | 			mdelay(5); | 
 | 765 | 			smu_i2c_retry(0); | 
 | 766 | 			return; | 
 | 767 | 		} | 
| Benjamin Herrenschmidt | 730745a | 2006-01-07 11:30:44 +1100 | [diff] [blame] | 768 | 		mod_timer(&smu->i2c_timer, jiffies + msecs_to_jiffies(5)); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 769 | 		return; | 
 | 770 | 	} | 
 | 771 |  | 
 | 772 | 	/* If failure or stage 1, command is complete */ | 
 | 773 | 	if (fail || cmd->stage != 0) { | 
 | 774 | 		smu_i2c_complete_command(cmd, fail); | 
 | 775 | 		return; | 
 | 776 | 	} | 
 | 777 |  | 
 | 778 | 	DPRINTK("SMU: going to stage 1\n"); | 
 | 779 |  | 
 | 780 | 	/* Ok, initial command complete, now poll status */ | 
 | 781 | 	scmd->reply_buf = cmd->pdata; | 
| Benjamin Herrenschmidt | 730745a | 2006-01-07 11:30:44 +1100 | [diff] [blame] | 782 | 	scmd->reply_len = sizeof(cmd->pdata); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 783 | 	scmd->data_buf = cmd->pdata; | 
 | 784 | 	scmd->data_len = 1; | 
 | 785 | 	cmd->pdata[0] = 0; | 
 | 786 | 	cmd->stage = 1; | 
 | 787 | 	cmd->retries = 20; | 
 | 788 | 	smu_queue_cmd(scmd); | 
 | 789 | } | 
 | 790 |  | 
 | 791 |  | 
 | 792 | int smu_queue_i2c(struct smu_i2c_cmd *cmd) | 
 | 793 | { | 
 | 794 | 	unsigned long flags; | 
 | 795 |  | 
 | 796 | 	if (smu == NULL) | 
 | 797 | 		return -ENODEV; | 
 | 798 |  | 
 | 799 | 	/* Fill most fields of scmd */ | 
 | 800 | 	cmd->scmd.cmd = SMU_CMD_I2C_COMMAND; | 
 | 801 | 	cmd->scmd.done = smu_i2c_low_completion; | 
 | 802 | 	cmd->scmd.misc = cmd; | 
 | 803 | 	cmd->scmd.reply_buf = cmd->pdata; | 
| Benjamin Herrenschmidt | 730745a | 2006-01-07 11:30:44 +1100 | [diff] [blame] | 804 | 	cmd->scmd.reply_len = sizeof(cmd->pdata); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 805 | 	cmd->scmd.data_buf = (u8 *)(char *)&cmd->info; | 
 | 806 | 	cmd->scmd.status = 1; | 
 | 807 | 	cmd->stage = 0; | 
 | 808 | 	cmd->pdata[0] = 0xff; | 
 | 809 | 	cmd->retries = 20; | 
 | 810 | 	cmd->status = 1; | 
 | 811 |  | 
 | 812 | 	/* Check transfer type, sanitize some "info" fields | 
 | 813 | 	 * based on transfer type and do more checking | 
 | 814 | 	 */ | 
 | 815 | 	cmd->info.caddr = cmd->info.devaddr; | 
 | 816 | 	cmd->read = cmd->info.devaddr & 0x01; | 
 | 817 | 	switch(cmd->info.type) { | 
 | 818 | 	case SMU_I2C_TRANSFER_SIMPLE: | 
 | 819 | 		memset(&cmd->info.sublen, 0, 4); | 
 | 820 | 		break; | 
 | 821 | 	case SMU_I2C_TRANSFER_COMBINED: | 
 | 822 | 		cmd->info.devaddr &= 0xfe; | 
 | 823 | 	case SMU_I2C_TRANSFER_STDSUB: | 
 | 824 | 		if (cmd->info.sublen > 3) | 
 | 825 | 			return -EINVAL; | 
 | 826 | 		break; | 
 | 827 | 	default: | 
 | 828 | 		return -EINVAL; | 
 | 829 | 	} | 
 | 830 |  | 
 | 831 | 	/* Finish setting up command based on transfer direction | 
 | 832 | 	 */ | 
 | 833 | 	if (cmd->read) { | 
 | 834 | 		if (cmd->info.datalen > SMU_I2C_READ_MAX) | 
 | 835 | 			return -EINVAL; | 
 | 836 | 		memset(cmd->info.data, 0xff, cmd->info.datalen); | 
 | 837 | 		cmd->scmd.data_len = 9; | 
 | 838 | 	} else { | 
 | 839 | 		if (cmd->info.datalen > SMU_I2C_WRITE_MAX) | 
 | 840 | 			return -EINVAL; | 
 | 841 | 		cmd->scmd.data_len = 9 + cmd->info.datalen; | 
 | 842 | 	} | 
 | 843 |  | 
 | 844 | 	DPRINTK("SMU: i2c enqueuing command\n"); | 
 | 845 | 	DPRINTK("SMU:   %s, len=%d bus=%x addr=%x sub0=%x type=%x\n", | 
 | 846 | 		cmd->read ? "read" : "write", cmd->info.datalen, | 
 | 847 | 		cmd->info.bus, cmd->info.caddr, | 
 | 848 | 		cmd->info.subaddr[0], cmd->info.type); | 
 | 849 |  | 
 | 850 |  | 
 | 851 | 	/* Enqueue command in i2c list, and if empty, enqueue also in | 
 | 852 | 	 * main command list | 
 | 853 | 	 */ | 
 | 854 | 	spin_lock_irqsave(&smu->lock, flags); | 
 | 855 | 	if (smu->cmd_i2c_cur == NULL) { | 
 | 856 | 		smu->cmd_i2c_cur = cmd; | 
 | 857 | 		list_add_tail(&cmd->scmd.link, &smu->cmd_list); | 
 | 858 | 		if (smu->cmd_cur == NULL) | 
 | 859 | 			smu_start_cmd(); | 
 | 860 | 	} else | 
 | 861 | 		list_add_tail(&cmd->link, &smu->cmd_i2c_list); | 
 | 862 | 	spin_unlock_irqrestore(&smu->lock, flags); | 
 | 863 |  | 
 | 864 | 	return 0; | 
 | 865 | } | 
 | 866 |  | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 867 | /* | 
 | 868 |  * Handling of "partitions" | 
 | 869 |  */ | 
 | 870 |  | 
 | 871 | static int smu_read_datablock(u8 *dest, unsigned int addr, unsigned int len) | 
 | 872 | { | 
 | 873 | 	DECLARE_COMPLETION(comp); | 
 | 874 | 	unsigned int chunk; | 
 | 875 | 	struct smu_cmd cmd; | 
 | 876 | 	int rc; | 
 | 877 | 	u8 params[8]; | 
 | 878 |  | 
 | 879 | 	/* We currently use a chunk size of 0xe. We could check the | 
 | 880 | 	 * SMU firmware version and use bigger sizes though | 
 | 881 | 	 */ | 
 | 882 | 	chunk = 0xe; | 
 | 883 |  | 
 | 884 | 	while (len) { | 
 | 885 | 		unsigned int clen = min(len, chunk); | 
 | 886 |  | 
 | 887 | 		cmd.cmd = SMU_CMD_MISC_ee_COMMAND; | 
 | 888 | 		cmd.data_len = 7; | 
 | 889 | 		cmd.data_buf = params; | 
 | 890 | 		cmd.reply_len = chunk; | 
 | 891 | 		cmd.reply_buf = dest; | 
 | 892 | 		cmd.done = smu_done_complete; | 
 | 893 | 		cmd.misc = ∁ | 
 | 894 | 		params[0] = SMU_CMD_MISC_ee_GET_DATABLOCK_REC; | 
 | 895 | 		params[1] = 0x4; | 
 | 896 | 		*((u32 *)¶ms[2]) = addr; | 
 | 897 | 		params[6] = clen; | 
 | 898 |  | 
 | 899 | 		rc = smu_queue_cmd(&cmd); | 
 | 900 | 		if (rc) | 
 | 901 | 			return rc; | 
 | 902 | 		wait_for_completion(&comp); | 
 | 903 | 		if (cmd.status != 0) | 
 | 904 | 			return rc; | 
 | 905 | 		if (cmd.reply_len != clen) { | 
 | 906 | 			printk(KERN_DEBUG "SMU: short read in " | 
 | 907 | 			       "smu_read_datablock, got: %d, want: %d\n", | 
 | 908 | 			       cmd.reply_len, clen); | 
 | 909 | 			return -EIO; | 
 | 910 | 		} | 
 | 911 | 		len -= clen; | 
 | 912 | 		addr += clen; | 
 | 913 | 		dest += clen; | 
 | 914 | 	} | 
 | 915 | 	return 0; | 
 | 916 | } | 
 | 917 |  | 
 | 918 | static struct smu_sdbp_header *smu_create_sdb_partition(int id) | 
 | 919 | { | 
 | 920 | 	DECLARE_COMPLETION(comp); | 
 | 921 | 	struct smu_simple_cmd cmd; | 
 | 922 | 	unsigned int addr, len, tlen; | 
 | 923 | 	struct smu_sdbp_header *hdr; | 
 | 924 | 	struct property *prop; | 
 | 925 |  | 
 | 926 | 	/* First query the partition info */ | 
| Benjamin Herrenschmidt | 1beb6a7 | 2005-12-14 13:10:10 +1100 | [diff] [blame] | 927 | 	DPRINTK("SMU: Query partition infos ... (irq=%d)\n", smu->db_irq); | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 928 | 	smu_queue_simple(&cmd, SMU_CMD_PARTITION_COMMAND, 2, | 
 | 929 | 			 smu_done_complete, &comp, | 
 | 930 | 			 SMU_CMD_PARTITION_LATEST, id); | 
 | 931 | 	wait_for_completion(&comp); | 
| Benjamin Herrenschmidt | 1beb6a7 | 2005-12-14 13:10:10 +1100 | [diff] [blame] | 932 | 	DPRINTK("SMU: done, status: %d, reply_len: %d\n", | 
 | 933 | 		cmd.cmd.status, cmd.cmd.reply_len); | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 934 |  | 
 | 935 | 	/* Partition doesn't exist (or other error) */ | 
 | 936 | 	if (cmd.cmd.status != 0 || cmd.cmd.reply_len != 6) | 
 | 937 | 		return NULL; | 
 | 938 |  | 
 | 939 | 	/* Fetch address and length from reply */ | 
 | 940 | 	addr = *((u16 *)cmd.buffer); | 
 | 941 | 	len = cmd.buffer[3] << 2; | 
 | 942 | 	/* Calucluate total length to allocate, including the 17 bytes | 
 | 943 | 	 * for "sdb-partition-XX" that we append at the end of the buffer | 
 | 944 | 	 */ | 
 | 945 | 	tlen = sizeof(struct property) + len + 18; | 
 | 946 |  | 
 | 947 | 	prop = kcalloc(tlen, 1, GFP_KERNEL); | 
 | 948 | 	if (prop == NULL) | 
 | 949 | 		return NULL; | 
 | 950 | 	hdr = (struct smu_sdbp_header *)(prop + 1); | 
 | 951 | 	prop->name = ((char *)prop) + tlen - 18; | 
 | 952 | 	sprintf(prop->name, "sdb-partition-%02x", id); | 
 | 953 | 	prop->length = len; | 
 | 954 | 	prop->value = (unsigned char *)hdr; | 
 | 955 | 	prop->next = NULL; | 
 | 956 |  | 
 | 957 | 	/* Read the datablock */ | 
 | 958 | 	if (smu_read_datablock((u8 *)hdr, addr, len)) { | 
 | 959 | 		printk(KERN_DEBUG "SMU: datablock read failed while reading " | 
 | 960 | 		       "partition %02x !\n", id); | 
 | 961 | 		goto failure; | 
 | 962 | 	} | 
 | 963 |  | 
 | 964 | 	/* Got it, check a few things and create the property */ | 
 | 965 | 	if (hdr->id != id) { | 
 | 966 | 		printk(KERN_DEBUG "SMU: Reading partition %02x and got " | 
 | 967 | 		       "%02x !\n", id, hdr->id); | 
 | 968 | 		goto failure; | 
 | 969 | 	} | 
 | 970 | 	if (prom_add_property(smu->of_node, prop)) { | 
 | 971 | 		printk(KERN_DEBUG "SMU: Failed creating sdb-partition-%02x " | 
 | 972 | 		       "property !\n", id); | 
 | 973 | 		goto failure; | 
 | 974 | 	} | 
 | 975 |  | 
 | 976 | 	return hdr; | 
 | 977 |  failure: | 
 | 978 | 	kfree(prop); | 
 | 979 | 	return NULL; | 
 | 980 | } | 
 | 981 |  | 
 | 982 | /* Note: Only allowed to return error code in pointers (using ERR_PTR) | 
 | 983 |  * when interruptible is 1 | 
 | 984 |  */ | 
 | 985 | struct smu_sdbp_header *__smu_get_sdb_partition(int id, unsigned int *size, | 
 | 986 | 						int interruptible) | 
| Benjamin Herrenschmidt | 4350147 | 2005-11-07 14:27:33 +1100 | [diff] [blame] | 987 | { | 
 | 988 | 	char pname[32]; | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 989 | 	struct smu_sdbp_header *part; | 
| Benjamin Herrenschmidt | 4350147 | 2005-11-07 14:27:33 +1100 | [diff] [blame] | 990 |  | 
 | 991 | 	if (!smu) | 
 | 992 | 		return NULL; | 
 | 993 |  | 
 | 994 | 	sprintf(pname, "sdb-partition-%02x", id); | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 995 |  | 
| Benjamin Herrenschmidt | 1beb6a7 | 2005-12-14 13:10:10 +1100 | [diff] [blame] | 996 | 	DPRINTK("smu_get_sdb_partition(%02x)\n", id); | 
 | 997 |  | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 998 | 	if (interruptible) { | 
 | 999 | 		int rc; | 
| Ingo Molnar | 14cc3e2 | 2006-03-26 01:37:14 -0800 | [diff] [blame] | 1000 | 		rc = mutex_lock_interruptible(&smu_part_access); | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 1001 | 		if (rc) | 
 | 1002 | 			return ERR_PTR(rc); | 
 | 1003 | 	} else | 
| Ingo Molnar | 14cc3e2 | 2006-03-26 01:37:14 -0800 | [diff] [blame] | 1004 | 		mutex_lock(&smu_part_access); | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 1005 |  | 
 | 1006 | 	part = (struct smu_sdbp_header *)get_property(smu->of_node, | 
| Benjamin Herrenschmidt | 4350147 | 2005-11-07 14:27:33 +1100 | [diff] [blame] | 1007 | 						      pname, size); | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 1008 | 	if (part == NULL) { | 
| Benjamin Herrenschmidt | 1beb6a7 | 2005-12-14 13:10:10 +1100 | [diff] [blame] | 1009 | 		DPRINTK("trying to extract from SMU ...\n"); | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 1010 | 		part = smu_create_sdb_partition(id); | 
 | 1011 | 		if (part != NULL && size) | 
 | 1012 | 			*size = part->len << 2; | 
 | 1013 | 	} | 
| Ingo Molnar | 14cc3e2 | 2006-03-26 01:37:14 -0800 | [diff] [blame] | 1014 | 	mutex_unlock(&smu_part_access); | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 1015 | 	return part; | 
 | 1016 | } | 
 | 1017 |  | 
 | 1018 | struct smu_sdbp_header *smu_get_sdb_partition(int id, unsigned int *size) | 
 | 1019 | { | 
 | 1020 | 	return __smu_get_sdb_partition(id, size, 0); | 
| Benjamin Herrenschmidt | 4350147 | 2005-11-07 14:27:33 +1100 | [diff] [blame] | 1021 | } | 
 | 1022 | EXPORT_SYMBOL(smu_get_sdb_partition); | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 1023 |  | 
 | 1024 |  | 
 | 1025 | /* | 
 | 1026 |  * Userland driver interface | 
 | 1027 |  */ | 
 | 1028 |  | 
 | 1029 |  | 
 | 1030 | static LIST_HEAD(smu_clist); | 
 | 1031 | static DEFINE_SPINLOCK(smu_clist_lock); | 
 | 1032 |  | 
 | 1033 | enum smu_file_mode { | 
 | 1034 | 	smu_file_commands, | 
 | 1035 | 	smu_file_events, | 
 | 1036 | 	smu_file_closing | 
 | 1037 | }; | 
 | 1038 |  | 
 | 1039 | struct smu_private | 
 | 1040 | { | 
 | 1041 | 	struct list_head	list; | 
 | 1042 | 	enum smu_file_mode	mode; | 
 | 1043 | 	int			busy; | 
 | 1044 | 	struct smu_cmd		cmd; | 
 | 1045 | 	spinlock_t		lock; | 
 | 1046 | 	wait_queue_head_t	wait; | 
 | 1047 | 	u8			buffer[SMU_MAX_DATA]; | 
 | 1048 | }; | 
 | 1049 |  | 
 | 1050 |  | 
 | 1051 | static int smu_open(struct inode *inode, struct file *file) | 
 | 1052 | { | 
 | 1053 | 	struct smu_private *pp; | 
 | 1054 | 	unsigned long flags; | 
 | 1055 |  | 
 | 1056 | 	pp = kmalloc(sizeof(struct smu_private), GFP_KERNEL); | 
 | 1057 | 	if (pp == 0) | 
 | 1058 | 		return -ENOMEM; | 
 | 1059 | 	memset(pp, 0, sizeof(struct smu_private)); | 
 | 1060 | 	spin_lock_init(&pp->lock); | 
 | 1061 | 	pp->mode = smu_file_commands; | 
 | 1062 | 	init_waitqueue_head(&pp->wait); | 
 | 1063 |  | 
 | 1064 | 	spin_lock_irqsave(&smu_clist_lock, flags); | 
 | 1065 | 	list_add(&pp->list, &smu_clist); | 
 | 1066 | 	spin_unlock_irqrestore(&smu_clist_lock, flags); | 
 | 1067 | 	file->private_data = pp; | 
 | 1068 |  | 
 | 1069 | 	return 0; | 
 | 1070 | } | 
 | 1071 |  | 
 | 1072 |  | 
 | 1073 | static void smu_user_cmd_done(struct smu_cmd *cmd, void *misc) | 
 | 1074 | { | 
 | 1075 | 	struct smu_private *pp = misc; | 
 | 1076 |  | 
 | 1077 | 	wake_up_all(&pp->wait); | 
 | 1078 | } | 
 | 1079 |  | 
 | 1080 |  | 
 | 1081 | static ssize_t smu_write(struct file *file, const char __user *buf, | 
 | 1082 | 			 size_t count, loff_t *ppos) | 
 | 1083 | { | 
 | 1084 | 	struct smu_private *pp = file->private_data; | 
 | 1085 | 	unsigned long flags; | 
 | 1086 | 	struct smu_user_cmd_hdr hdr; | 
 | 1087 | 	int rc = 0; | 
 | 1088 |  | 
 | 1089 | 	if (pp->busy) | 
 | 1090 | 		return -EBUSY; | 
 | 1091 | 	else if (copy_from_user(&hdr, buf, sizeof(hdr))) | 
 | 1092 | 		return -EFAULT; | 
 | 1093 | 	else if (hdr.cmdtype == SMU_CMDTYPE_WANTS_EVENTS) { | 
 | 1094 | 		pp->mode = smu_file_events; | 
 | 1095 | 		return 0; | 
| Benjamin Herrenschmidt | 183d020 | 2005-11-07 14:29:02 +1100 | [diff] [blame] | 1096 | 	} else if (hdr.cmdtype == SMU_CMDTYPE_GET_PARTITION) { | 
 | 1097 | 		struct smu_sdbp_header *part; | 
 | 1098 | 		part = __smu_get_sdb_partition(hdr.cmd, NULL, 1); | 
 | 1099 | 		if (part == NULL) | 
 | 1100 | 			return -EINVAL; | 
 | 1101 | 		else if (IS_ERR(part)) | 
 | 1102 | 			return PTR_ERR(part); | 
 | 1103 | 		return 0; | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 1104 | 	} else if (hdr.cmdtype != SMU_CMDTYPE_SMU) | 
 | 1105 | 		return -EINVAL; | 
 | 1106 | 	else if (pp->mode != smu_file_commands) | 
 | 1107 | 		return -EBADFD; | 
 | 1108 | 	else if (hdr.data_len > SMU_MAX_DATA) | 
 | 1109 | 		return -EINVAL; | 
 | 1110 |  | 
 | 1111 | 	spin_lock_irqsave(&pp->lock, flags); | 
 | 1112 | 	if (pp->busy) { | 
 | 1113 | 		spin_unlock_irqrestore(&pp->lock, flags); | 
 | 1114 | 		return -EBUSY; | 
 | 1115 | 	} | 
 | 1116 | 	pp->busy = 1; | 
 | 1117 | 	pp->cmd.status = 1; | 
 | 1118 | 	spin_unlock_irqrestore(&pp->lock, flags); | 
 | 1119 |  | 
 | 1120 | 	if (copy_from_user(pp->buffer, buf + sizeof(hdr), hdr.data_len)) { | 
 | 1121 | 		pp->busy = 0; | 
 | 1122 | 		return -EFAULT; | 
 | 1123 | 	} | 
 | 1124 |  | 
 | 1125 | 	pp->cmd.cmd = hdr.cmd; | 
 | 1126 | 	pp->cmd.data_len = hdr.data_len; | 
 | 1127 | 	pp->cmd.reply_len = SMU_MAX_DATA; | 
 | 1128 | 	pp->cmd.data_buf = pp->buffer; | 
 | 1129 | 	pp->cmd.reply_buf = pp->buffer; | 
 | 1130 | 	pp->cmd.done = smu_user_cmd_done; | 
 | 1131 | 	pp->cmd.misc = pp; | 
 | 1132 | 	rc = smu_queue_cmd(&pp->cmd); | 
 | 1133 | 	if (rc < 0) | 
 | 1134 | 		return rc; | 
 | 1135 | 	return count; | 
 | 1136 | } | 
 | 1137 |  | 
 | 1138 |  | 
 | 1139 | static ssize_t smu_read_command(struct file *file, struct smu_private *pp, | 
 | 1140 | 				char __user *buf, size_t count) | 
 | 1141 | { | 
 | 1142 | 	DECLARE_WAITQUEUE(wait, current); | 
 | 1143 | 	struct smu_user_reply_hdr hdr; | 
 | 1144 | 	unsigned long flags; | 
 | 1145 | 	int size, rc = 0; | 
 | 1146 |  | 
 | 1147 | 	if (!pp->busy) | 
 | 1148 | 		return 0; | 
 | 1149 | 	if (count < sizeof(struct smu_user_reply_hdr)) | 
 | 1150 | 		return -EOVERFLOW; | 
 | 1151 | 	spin_lock_irqsave(&pp->lock, flags); | 
 | 1152 | 	if (pp->cmd.status == 1) { | 
 | 1153 | 		if (file->f_flags & O_NONBLOCK) | 
 | 1154 | 			return -EAGAIN; | 
 | 1155 | 		add_wait_queue(&pp->wait, &wait); | 
 | 1156 | 		for (;;) { | 
 | 1157 | 			set_current_state(TASK_INTERRUPTIBLE); | 
 | 1158 | 			rc = 0; | 
 | 1159 | 			if (pp->cmd.status != 1) | 
 | 1160 | 				break; | 
 | 1161 | 			rc = -ERESTARTSYS; | 
 | 1162 | 			if (signal_pending(current)) | 
 | 1163 | 				break; | 
 | 1164 | 			spin_unlock_irqrestore(&pp->lock, flags); | 
 | 1165 | 			schedule(); | 
 | 1166 | 			spin_lock_irqsave(&pp->lock, flags); | 
 | 1167 | 		} | 
 | 1168 | 		set_current_state(TASK_RUNNING); | 
 | 1169 | 		remove_wait_queue(&pp->wait, &wait); | 
 | 1170 | 	} | 
 | 1171 | 	spin_unlock_irqrestore(&pp->lock, flags); | 
 | 1172 | 	if (rc) | 
 | 1173 | 		return rc; | 
 | 1174 | 	if (pp->cmd.status != 0) | 
 | 1175 | 		pp->cmd.reply_len = 0; | 
 | 1176 | 	size = sizeof(hdr) + pp->cmd.reply_len; | 
 | 1177 | 	if (count < size) | 
 | 1178 | 		size = count; | 
 | 1179 | 	rc = size; | 
 | 1180 | 	hdr.status = pp->cmd.status; | 
 | 1181 | 	hdr.reply_len = pp->cmd.reply_len; | 
 | 1182 | 	if (copy_to_user(buf, &hdr, sizeof(hdr))) | 
 | 1183 | 		return -EFAULT; | 
 | 1184 | 	size -= sizeof(hdr); | 
 | 1185 | 	if (size && copy_to_user(buf + sizeof(hdr), pp->buffer, size)) | 
 | 1186 | 		return -EFAULT; | 
 | 1187 | 	pp->busy = 0; | 
 | 1188 |  | 
 | 1189 | 	return rc; | 
 | 1190 | } | 
 | 1191 |  | 
 | 1192 |  | 
 | 1193 | static ssize_t smu_read_events(struct file *file, struct smu_private *pp, | 
 | 1194 | 			       char __user *buf, size_t count) | 
 | 1195 | { | 
 | 1196 | 	/* Not implemented */ | 
 | 1197 | 	msleep_interruptible(1000); | 
 | 1198 | 	return 0; | 
 | 1199 | } | 
 | 1200 |  | 
 | 1201 |  | 
 | 1202 | static ssize_t smu_read(struct file *file, char __user *buf, | 
 | 1203 | 			size_t count, loff_t *ppos) | 
 | 1204 | { | 
 | 1205 | 	struct smu_private *pp = file->private_data; | 
 | 1206 |  | 
 | 1207 | 	if (pp->mode == smu_file_commands) | 
 | 1208 | 		return smu_read_command(file, pp, buf, count); | 
 | 1209 | 	if (pp->mode == smu_file_events) | 
 | 1210 | 		return smu_read_events(file, pp, buf, count); | 
 | 1211 |  | 
 | 1212 | 	return -EBADFD; | 
 | 1213 | } | 
 | 1214 |  | 
 | 1215 | static unsigned int smu_fpoll(struct file *file, poll_table *wait) | 
 | 1216 | { | 
 | 1217 | 	struct smu_private *pp = file->private_data; | 
 | 1218 | 	unsigned int mask = 0; | 
 | 1219 | 	unsigned long flags; | 
 | 1220 |  | 
 | 1221 | 	if (pp == 0) | 
 | 1222 | 		return 0; | 
 | 1223 |  | 
 | 1224 | 	if (pp->mode == smu_file_commands) { | 
 | 1225 | 		poll_wait(file, &pp->wait, wait); | 
 | 1226 |  | 
 | 1227 | 		spin_lock_irqsave(&pp->lock, flags); | 
 | 1228 | 		if (pp->busy && pp->cmd.status != 1) | 
 | 1229 | 			mask |= POLLIN; | 
 | 1230 | 		spin_unlock_irqrestore(&pp->lock, flags); | 
 | 1231 | 	} if (pp->mode == smu_file_events) { | 
 | 1232 | 		/* Not yet implemented */ | 
 | 1233 | 	} | 
 | 1234 | 	return mask; | 
 | 1235 | } | 
 | 1236 |  | 
 | 1237 | static int smu_release(struct inode *inode, struct file *file) | 
 | 1238 | { | 
 | 1239 | 	struct smu_private *pp = file->private_data; | 
 | 1240 | 	unsigned long flags; | 
 | 1241 | 	unsigned int busy; | 
 | 1242 |  | 
 | 1243 | 	if (pp == 0) | 
 | 1244 | 		return 0; | 
 | 1245 |  | 
 | 1246 | 	file->private_data = NULL; | 
 | 1247 |  | 
 | 1248 | 	/* Mark file as closing to avoid races with new request */ | 
 | 1249 | 	spin_lock_irqsave(&pp->lock, flags); | 
 | 1250 | 	pp->mode = smu_file_closing; | 
 | 1251 | 	busy = pp->busy; | 
 | 1252 |  | 
 | 1253 | 	/* Wait for any pending request to complete */ | 
 | 1254 | 	if (busy && pp->cmd.status == 1) { | 
 | 1255 | 		DECLARE_WAITQUEUE(wait, current); | 
 | 1256 |  | 
 | 1257 | 		add_wait_queue(&pp->wait, &wait); | 
 | 1258 | 		for (;;) { | 
 | 1259 | 			set_current_state(TASK_UNINTERRUPTIBLE); | 
 | 1260 | 			if (pp->cmd.status != 1) | 
 | 1261 | 				break; | 
 | 1262 | 			spin_lock_irqsave(&pp->lock, flags); | 
 | 1263 | 			schedule(); | 
 | 1264 | 			spin_unlock_irqrestore(&pp->lock, flags); | 
 | 1265 | 		} | 
 | 1266 | 		set_current_state(TASK_RUNNING); | 
 | 1267 | 		remove_wait_queue(&pp->wait, &wait); | 
 | 1268 | 	} | 
 | 1269 | 	spin_unlock_irqrestore(&pp->lock, flags); | 
 | 1270 |  | 
 | 1271 | 	spin_lock_irqsave(&smu_clist_lock, flags); | 
 | 1272 | 	list_del(&pp->list); | 
 | 1273 | 	spin_unlock_irqrestore(&smu_clist_lock, flags); | 
 | 1274 | 	kfree(pp); | 
 | 1275 |  | 
 | 1276 | 	return 0; | 
 | 1277 | } | 
 | 1278 |  | 
 | 1279 |  | 
| Stephen Rothwell | 6b67f62c | 2005-09-27 14:09:39 +1000 | [diff] [blame] | 1280 | static struct file_operations smu_device_fops = { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 1281 | 	.llseek		= no_llseek, | 
 | 1282 | 	.read		= smu_read, | 
 | 1283 | 	.write		= smu_write, | 
 | 1284 | 	.poll		= smu_fpoll, | 
 | 1285 | 	.open		= smu_open, | 
 | 1286 | 	.release	= smu_release, | 
 | 1287 | }; | 
 | 1288 |  | 
| Stephen Rothwell | 6b67f62c | 2005-09-27 14:09:39 +1000 | [diff] [blame] | 1289 | static struct miscdevice pmu_device = { | 
| Benjamin Herrenschmidt | 0365ba7 | 2005-09-22 21:44:06 -0700 | [diff] [blame] | 1290 | 	MISC_DYNAMIC_MINOR, "smu", &smu_device_fops | 
 | 1291 | }; | 
 | 1292 |  | 
 | 1293 | static int smu_device_init(void) | 
 | 1294 | { | 
 | 1295 | 	if (!smu) | 
 | 1296 | 		return -ENODEV; | 
 | 1297 | 	if (misc_register(&pmu_device) < 0) | 
 | 1298 | 		printk(KERN_ERR "via-pmu: cannot register misc device.\n"); | 
 | 1299 | 	return 0; | 
 | 1300 | } | 
 | 1301 | device_initcall(smu_device_init); |