| 
									
										
										
										
											2009-04-23 21:15:04 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * arch/arm/mach-u300/padmux.c | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 2009 ST-Ericsson AB | 
					
						
							|  |  |  |  * License terms: GNU General Public License (GPL) version 2 | 
					
						
							|  |  |  |  * U300 PADMUX functions | 
					
						
							| 
									
										
										
										
											2009-08-10 12:52:40 +01:00
										 |  |  |  * Author: Martin Persson <martin.persson@stericsson.com> | 
					
						
							| 
									
										
										
										
											2009-04-23 21:15:04 +01:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2009-08-10 12:52:40 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/device.h>
 | 
					
						
							| 
									
										
										
										
											2009-04-23 21:15:04 +01:00
										 |  |  | #include <linux/err.h>
 | 
					
						
							| 
									
										
										
										
											2009-08-10 12:52:40 +01:00
										 |  |  | #include <linux/errno.h>
 | 
					
						
							|  |  |  | #include <linux/io.h>
 | 
					
						
							|  |  |  | #include <linux/mutex.h>
 | 
					
						
							|  |  |  | #include <linux/string.h>
 | 
					
						
							|  |  |  | #include <linux/bug.h>
 | 
					
						
							|  |  |  | #include <linux/debugfs.h>
 | 
					
						
							|  |  |  | #include <linux/seq_file.h>
 | 
					
						
							| 
									
										
										
										
											2009-04-23 21:15:04 +01:00
										 |  |  | #include <mach/u300-regs.h>
 | 
					
						
							|  |  |  | #include <mach/syscon.h>
 | 
					
						
							|  |  |  | #include "padmux.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-08-10 12:52:40 +01:00
										 |  |  | static DEFINE_MUTEX(pmx_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const u32 pmx_registers[] = { | 
					
						
							|  |  |  | 	(U300_SYSCON_VBASE + U300_SYSCON_PMC1LR), | 
					
						
							|  |  |  | 	(U300_SYSCON_VBASE + U300_SYSCON_PMC1HR), | 
					
						
							|  |  |  | 	(U300_SYSCON_VBASE + U300_SYSCON_PMC2R), | 
					
						
							|  |  |  | 	(U300_SYSCON_VBASE + U300_SYSCON_PMC3R), | 
					
						
							|  |  |  | 	(U300_SYSCON_VBASE + U300_SYSCON_PMC4R) | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* High level functionality */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Lazy dog:
 | 
					
						
							|  |  |  |  * onmask = { | 
					
						
							|  |  |  |  *   {"PMC1LR" mask, "PMC1LR" value}, | 
					
						
							|  |  |  |  *   {"PMC1HR" mask, "PMC1HR" value}, | 
					
						
							|  |  |  |  *   {"PMC2R"  mask, "PMC2R"  value}, | 
					
						
							|  |  |  |  *   {"PMC3R"  mask, "PMC3R"  value}, | 
					
						
							|  |  |  |  *   {"PMC4R"  mask, "PMC4R"  value} | 
					
						
							|  |  |  |  * } | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static struct pmx mmc_setting = { | 
					
						
							|  |  |  | 	.setting = U300_APP_PMX_MMC_SETTING, | 
					
						
							|  |  |  | 	.default_on = false, | 
					
						
							|  |  |  | 	.activated = false, | 
					
						
							|  |  |  | 	.name = "MMC", | 
					
						
							|  |  |  | 	.onmask = { | 
					
						
							|  |  |  | 		   {U300_SYSCON_PMC1LR_MMCSD_MASK, | 
					
						
							|  |  |  | 		    U300_SYSCON_PMC1LR_MMCSD_MMCSD}, | 
					
						
							|  |  |  | 		   {0, 0}, | 
					
						
							|  |  |  | 		   {0, 0}, | 
					
						
							|  |  |  | 		   {0, 0}, | 
					
						
							|  |  |  | 		   {U300_SYSCON_PMC4R_APP_MISC_12_MASK, | 
					
						
							|  |  |  | 		    U300_SYSCON_PMC4R_APP_MISC_12_APP_GPIO} | 
					
						
							|  |  |  | 		   }, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct pmx spi_setting = { | 
					
						
							|  |  |  | 	.setting = U300_APP_PMX_SPI_SETTING, | 
					
						
							|  |  |  | 	.default_on = false, | 
					
						
							|  |  |  | 	.activated = false, | 
					
						
							|  |  |  | 	.name = "SPI", | 
					
						
							|  |  |  | 	.onmask = {{0, 0}, | 
					
						
							|  |  |  | 		   {U300_SYSCON_PMC1HR_APP_SPI_2_MASK | | 
					
						
							|  |  |  | 		    U300_SYSCON_PMC1HR_APP_SPI_CS_1_MASK | | 
					
						
							|  |  |  | 		    U300_SYSCON_PMC1HR_APP_SPI_CS_2_MASK, | 
					
						
							|  |  |  | 		    U300_SYSCON_PMC1HR_APP_SPI_2_SPI | | 
					
						
							|  |  |  | 		    U300_SYSCON_PMC1HR_APP_SPI_CS_1_SPI | | 
					
						
							|  |  |  | 		    U300_SYSCON_PMC1HR_APP_SPI_CS_2_SPI}, | 
					
						
							|  |  |  | 		   {0, 0}, | 
					
						
							|  |  |  | 		   {0, 0}, | 
					
						
							|  |  |  | 		   {0, 0} | 
					
						
							|  |  |  | 		   }, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Available padmux settings */ | 
					
						
							|  |  |  | static struct pmx *pmx_settings[] = { | 
					
						
							|  |  |  | 	&mmc_setting, | 
					
						
							|  |  |  | 	&spi_setting, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void update_registers(struct pmx *pmx, bool activate) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u16 regval, val, mask; | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < ARRAY_SIZE(pmx_registers); i++) { | 
					
						
							|  |  |  | 		if (activate) | 
					
						
							|  |  |  | 			val = pmx->onmask[i].val; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			val = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		mask = pmx->onmask[i].mask; | 
					
						
							|  |  |  | 		if (mask != 0) { | 
					
						
							|  |  |  | 			regval = readw(pmx_registers[i]); | 
					
						
							|  |  |  | 			regval &= ~mask; | 
					
						
							|  |  |  | 			regval |= val; | 
					
						
							|  |  |  | 			writew(regval, pmx_registers[i]); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct pmx *pmx_get(struct device *dev, enum pmx_settings setting) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 	struct pmx *pmx = ERR_PTR(-ENOENT); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (dev == NULL) | 
					
						
							|  |  |  | 		return ERR_PTR(-EINVAL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&pmx_mutex); | 
					
						
							|  |  |  | 	for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (setting == pmx_settings[i]->setting) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (pmx_settings[i]->dev != NULL) { | 
					
						
							|  |  |  | 				WARN(1, "padmux: required setting " | 
					
						
							|  |  |  | 				     "in use by another consumer\n"); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				pmx = pmx_settings[i]; | 
					
						
							|  |  |  | 				pmx->dev = dev; | 
					
						
							|  |  |  | 				dev_dbg(dev, "padmux: setting nr %d is now " | 
					
						
							|  |  |  | 					"bound to %s and ready to use\n", | 
					
						
							|  |  |  | 					setting, dev_name(dev)); | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	mutex_unlock(&pmx_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return pmx; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(pmx_get); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int pmx_put(struct device *dev, struct pmx *pmx) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 	int ret = -ENOENT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (pmx == NULL || dev == NULL) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&pmx_mutex); | 
					
						
							|  |  |  | 	for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (pmx->setting == pmx_settings[i]->setting) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (dev != pmx->dev) { | 
					
						
							|  |  |  | 				WARN(1, "padmux: cannot release handle as " | 
					
						
							|  |  |  | 					"it is bound to another consumer\n"); | 
					
						
							|  |  |  | 				ret = -EINVAL; | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				pmx_settings[i]->dev = NULL; | 
					
						
							|  |  |  | 				ret = 0; | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	mutex_unlock(&pmx_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(pmx_put); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int pmx_activate(struct device *dev, struct pmx *pmx) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int i, j, ret; | 
					
						
							|  |  |  | 	ret = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (pmx == NULL || dev == NULL) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&pmx_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Make sure the required bits are not used */ | 
					
						
							|  |  |  | 	for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (pmx_settings[i]->dev == NULL || pmx_settings[i] == pmx) | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for (j = 0; j < ARRAY_SIZE(pmx_registers); j++) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (pmx_settings[i]->onmask[j].mask & pmx-> | 
					
						
							|  |  |  | 				onmask[j].mask) { | 
					
						
							|  |  |  | 				/* More than one entry on the same bits */ | 
					
						
							|  |  |  | 				WARN(1, "padmux: cannot activate " | 
					
						
							|  |  |  | 					"setting. Bit conflict with " | 
					
						
							|  |  |  | 					"an active setting\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				ret = -EUSERS; | 
					
						
							|  |  |  | 				goto exit; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	update_registers(pmx, true); | 
					
						
							|  |  |  | 	pmx->activated = true; | 
					
						
							|  |  |  | 	dev_dbg(dev, "padmux: setting nr %d is activated\n", | 
					
						
							|  |  |  | 		pmx->setting); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | exit: | 
					
						
							|  |  |  | 	mutex_unlock(&pmx_mutex); | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(pmx_activate); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int pmx_deactivate(struct device *dev, struct pmx *pmx) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 	int ret = -ENOENT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (pmx == NULL || dev == NULL) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&pmx_mutex); | 
					
						
							|  |  |  | 	for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (pmx_settings[i]->dev == NULL) | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (pmx->setting == pmx_settings[i]->setting) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (dev != pmx->dev) { | 
					
						
							|  |  |  | 				WARN(1, "padmux: cannot deactivate " | 
					
						
							|  |  |  | 				     "pmx setting as it was activated " | 
					
						
							|  |  |  | 				     "by another consumer\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				ret = -EBUSY; | 
					
						
							|  |  |  | 				continue; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				update_registers(pmx, false); | 
					
						
							|  |  |  | 				pmx_settings[i]->dev = NULL; | 
					
						
							|  |  |  | 				pmx->activated = false; | 
					
						
							|  |  |  | 				ret = 0; | 
					
						
							|  |  |  | 				dev_dbg(dev, "padmux: setting nr %d is deactivated", | 
					
						
							|  |  |  | 					pmx->setting); | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	mutex_unlock(&pmx_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(pmx_deactivate); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * For internal use only. If it is to be exported, | 
					
						
							|  |  |  |  * it should be reentrant. Notice that pmx_activate | 
					
						
							|  |  |  |  * (i.e. runtime settings) always override default settings. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int pmx_set_default(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* Used to identify several entries on the same bits */ | 
					
						
							|  |  |  | 	u16 modbits[ARRAY_SIZE(pmx_registers)]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	int i, j; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memset(modbits, 0, ARRAY_SIZE(pmx_registers) * sizeof(u16)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!pmx_settings[i]->default_on) | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for (j = 0; j < ARRAY_SIZE(pmx_registers); j++) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* Make sure there is only one entry on the same bits */ | 
					
						
							|  |  |  | 			if (modbits[j] & pmx_settings[i]->onmask[j].mask) { | 
					
						
							|  |  |  | 				BUG(); | 
					
						
							|  |  |  | 				return -EUSERS; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			modbits[j] |= pmx_settings[i]->onmask[j].mask; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		update_registers(pmx_settings[i], true); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							| 
									
										
										
										
											2009-04-23 21:15:04 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2009-08-10 12:52:40 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | #if (defined(CONFIG_DEBUG_FS) && defined(CONFIG_U300_DEBUG))
 | 
					
						
							|  |  |  | static int pmx_show(struct seq_file *s, void *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 	seq_printf(s, "-------------------------------------------------\n"); | 
					
						
							|  |  |  | 	seq_printf(s, "SETTING     BOUND TO DEVICE               STATE\n"); | 
					
						
							|  |  |  | 	seq_printf(s, "-------------------------------------------------\n"); | 
					
						
							|  |  |  | 	mutex_lock(&pmx_mutex); | 
					
						
							|  |  |  | 	for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) { | 
					
						
							|  |  |  | 		/* Format pmx and device name nicely */ | 
					
						
							|  |  |  | 		char cdp[33]; | 
					
						
							|  |  |  | 		int chars; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		chars = snprintf(&cdp[0], 17, "%s", pmx_settings[i]->name); | 
					
						
							|  |  |  | 		while (chars < 16) { | 
					
						
							|  |  |  | 			cdp[chars] = ' '; | 
					
						
							|  |  |  | 			chars++; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		chars = snprintf(&cdp[16], 17, "%s", pmx_settings[i]->dev ? | 
					
						
							|  |  |  | 				dev_name(pmx_settings[i]->dev) : "N/A"); | 
					
						
							|  |  |  | 		while (chars < 16) { | 
					
						
							|  |  |  | 			cdp[chars+16] = ' '; | 
					
						
							|  |  |  | 			chars++; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		cdp[32] = '\0'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		seq_printf(s, | 
					
						
							|  |  |  | 			"%s\t%s\n", | 
					
						
							|  |  |  | 			&cdp[0], | 
					
						
							|  |  |  | 			pmx_settings[i]->activated ? | 
					
						
							|  |  |  | 			"ACTIVATED" : "DEACTIVATED" | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	mutex_unlock(&pmx_mutex); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int pmx_open(struct inode *inode, struct file *file) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return single_open(file, pmx_show, NULL); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct file_operations pmx_operations = { | 
					
						
							|  |  |  | 	.owner		= THIS_MODULE, | 
					
						
							|  |  |  | 	.open		= pmx_open, | 
					
						
							|  |  |  | 	.read		= seq_read, | 
					
						
							|  |  |  | 	.llseek		= seq_lseek, | 
					
						
							|  |  |  | 	.release	= single_release, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __init init_pmx_read_debugfs(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* Expose a simple debugfs interface to view pmx settings */ | 
					
						
							|  |  |  | 	(void) debugfs_create_file("padmux", S_IFREG | S_IRUGO, | 
					
						
							|  |  |  | 				   NULL, NULL, | 
					
						
							|  |  |  | 				   &pmx_operations); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * This needs to come in after the core_initcall(), | 
					
						
							|  |  |  |  * because debugfs is not available until | 
					
						
							|  |  |  |  * the subsystems come up. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | module_init(init_pmx_read_debugfs); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __init pmx_init(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = pmx_set_default(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (IS_ERR_VALUE(ret)) | 
					
						
							|  |  |  | 		pr_crit("padmux: default settings could not be set\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Should be initialized before consumers */ | 
					
						
							|  |  |  | core_initcall(pmx_init); |