1/* 2 * linux/arch/arm/plat-pxa/mfp.c 3 * 4 * Multi-Function Pin Support 5 * 6 * Copyright (C) 2007 Marvell Internation Ltd. 7 * 8 * 2007-08-21: eric miao <eric.miao@marvell.com> 9 * initial version 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License version 2 as 13 * published by the Free Software Foundation. 14 */ 15 16#include <linux/module.h> 17#include <linux/kernel.h> 18#include <linux/init.h> 19#include <linux/io.h> 20 21#include <plat/mfp.h> 22 23#define MFPR_SIZE (PAGE_SIZE) 24 25/* MFPR register bit definitions */ 26#define MFPR_PULL_SEL (0x1 << 15) 27#define MFPR_PULLUP_EN (0x1 << 14) 28#define MFPR_PULLDOWN_EN (0x1 << 13) 29#define MFPR_SLEEP_SEL (0x1 << 9) 30#define MFPR_SLEEP_OE_N (0x1 << 7) 31#define MFPR_EDGE_CLEAR (0x1 << 6) 32#define MFPR_EDGE_FALL_EN (0x1 << 5) 33#define MFPR_EDGE_RISE_EN (0x1 << 4) 34 35#define MFPR_SLEEP_DATA(x) ((x) << 8) 36#define MFPR_DRIVE(x) (((x) & 0x7) << 10) 37#define MFPR_AF_SEL(x) (((x) & 0x7) << 0) 38 39#define MFPR_EDGE_NONE (0) 40#define MFPR_EDGE_RISE (MFPR_EDGE_RISE_EN) 41#define MFPR_EDGE_FALL (MFPR_EDGE_FALL_EN) 42#define MFPR_EDGE_BOTH (MFPR_EDGE_RISE | MFPR_EDGE_FALL) 43 44/* 45 * Table that determines the low power modes outputs, with actual settings 46 * used in parentheses for don't-care values. Except for the float output, 47 * the configured driven and pulled levels match, so if there is a need for 48 * non-LPM pulled output, the same configuration could probably be used. 49 * 50 * Output value sleep_oe_n sleep_data pullup_en pulldown_en pull_sel 51 * (bit 7) (bit 8) (bit 14) (bit 13) (bit 15) 52 * 53 * Input 0 X(0) X(0) X(0) 0 54 * Drive 0 0 0 0 X(1) 0 55 * Drive 1 0 1 X(1) 0 0 56 * Pull hi (1) 1 X(1) 1 0 0 57 * Pull lo (0) 1 X(0) 0 1 0 58 * Z (float) 1 X(0) 0 0 0 59 */ 60#define MFPR_LPM_INPUT (0) 61#define MFPR_LPM_DRIVE_LOW (MFPR_SLEEP_DATA(0) | MFPR_PULLDOWN_EN) 62#define MFPR_LPM_DRIVE_HIGH (MFPR_SLEEP_DATA(1) | MFPR_PULLUP_EN) 63#define MFPR_LPM_PULL_LOW (MFPR_LPM_DRIVE_LOW | MFPR_SLEEP_OE_N) 64#define MFPR_LPM_PULL_HIGH (MFPR_LPM_DRIVE_HIGH | MFPR_SLEEP_OE_N) 65#define MFPR_LPM_FLOAT (MFPR_SLEEP_OE_N) 66#define MFPR_LPM_MASK (0xe080) 67 68/* 69 * The pullup and pulldown state of the MFP pin at run mode is by default 70 * determined by the selected alternate function. In case that some buggy 71 * devices need to override this default behavior, the definitions below 72 * indicates the setting of corresponding MFPR bits 73 * 74 * Definition pull_sel pullup_en pulldown_en 75 * MFPR_PULL_NONE 0 0 0 76 * MFPR_PULL_LOW 1 0 1 77 * MFPR_PULL_HIGH 1 1 0 78 * MFPR_PULL_BOTH 1 1 1 79 * MFPR_PULL_FLOAT 1 0 0 80 */ 81#define MFPR_PULL_NONE (0) 82#define MFPR_PULL_LOW (MFPR_PULL_SEL | MFPR_PULLDOWN_EN) 83#define MFPR_PULL_BOTH (MFPR_PULL_LOW | MFPR_PULLUP_EN) 84#define MFPR_PULL_HIGH (MFPR_PULL_SEL | MFPR_PULLUP_EN) 85#define MFPR_PULL_FLOAT (MFPR_PULL_SEL) 86 87/* mfp_spin_lock is used to ensure that MFP register configuration 88 * (most likely a read-modify-write operation) is atomic, and that 89 * mfp_table[] is consistent 90 */ 91static DEFINE_SPINLOCK(mfp_spin_lock); 92 93static void __iomem *mfpr_mmio_base; 94 95struct mfp_pin { 96 unsigned long config; /* -1 for not configured */ 97 unsigned long mfpr_off; /* MFPRxx Register offset */ 98 unsigned long mfpr_run; /* Run-Mode Register Value */ 99 unsigned long mfpr_lpm; /* Low Power Mode Register Value */ 100}; 101 102static struct mfp_pin mfp_table[MFP_PIN_MAX]; 103 104/* mapping of MFP_LPM_* definitions to MFPR_LPM_* register bits */ 105static const unsigned long mfpr_lpm[] = { 106 MFPR_LPM_INPUT, 107 MFPR_LPM_DRIVE_LOW, 108 MFPR_LPM_DRIVE_HIGH, 109 MFPR_LPM_PULL_LOW, 110 MFPR_LPM_PULL_HIGH, 111 MFPR_LPM_FLOAT, 112 MFPR_LPM_INPUT, 113}; 114 115/* mapping of MFP_PULL_* definitions to MFPR_PULL_* register bits */ 116static const unsigned long mfpr_pull[] = { 117 MFPR_PULL_NONE, 118 MFPR_PULL_LOW, 119 MFPR_PULL_HIGH, 120 MFPR_PULL_BOTH, 121 MFPR_PULL_FLOAT, 122}; 123 124/* mapping of MFP_LPM_EDGE_* definitions to MFPR_EDGE_* register bits */ 125static const unsigned long mfpr_edge[] = { 126 MFPR_EDGE_NONE, 127 MFPR_EDGE_RISE, 128 MFPR_EDGE_FALL, 129 MFPR_EDGE_BOTH, 130}; 131 132#define mfpr_readl(off) \ 133 __raw_readl(mfpr_mmio_base + (off)) 134 135#define mfpr_writel(off, val) \ 136 __raw_writel(val, mfpr_mmio_base + (off)) 137 138#define mfp_configured(p) ((p)->config != -1) 139 140/* 141 * perform a read-back of any valid MFPR register to make sure the 142 * previous writings are finished 143 */ 144static unsigned long mfpr_off_readback; 145#define mfpr_sync() (void)__raw_readl(mfpr_mmio_base + mfpr_off_readback) 146 147static inline void __mfp_config_run(struct mfp_pin *p) 148{ 149 if (mfp_configured(p)) 150 mfpr_writel(p->mfpr_off, p->mfpr_run); 151} 152 153static inline void __mfp_config_lpm(struct mfp_pin *p) 154{ 155 if (mfp_configured(p)) { 156 unsigned long mfpr_clr = (p->mfpr_run & ~MFPR_EDGE_BOTH) | MFPR_EDGE_CLEAR; 157 if (mfpr_clr != p->mfpr_run) 158 mfpr_writel(p->mfpr_off, mfpr_clr); 159 if (p->mfpr_lpm != mfpr_clr) 160 mfpr_writel(p->mfpr_off, p->mfpr_lpm); 161 } 162} 163 164void mfp_config(unsigned long *mfp_cfgs, int num) 165{ 166 unsigned long flags; 167 int i; 168 169 spin_lock_irqsave(&mfp_spin_lock, flags); 170 171 for (i = 0; i < num; i++, mfp_cfgs++) { 172 unsigned long tmp, c = *mfp_cfgs; 173 struct mfp_pin *p; 174 int pin, af, drv, lpm, edge, pull; 175 176 pin = MFP_PIN(c); 177 BUG_ON(pin >= MFP_PIN_MAX); 178 p = &mfp_table[pin]; 179 180 af = MFP_AF(c); 181 drv = MFP_DS(c); 182 lpm = MFP_LPM_STATE(c); 183 edge = MFP_LPM_EDGE(c); 184 pull = MFP_PULL(c); 185 186 /* run-mode pull settings will conflict with MFPR bits of 187 * low power mode state, calculate mfpr_run and mfpr_lpm 188 * individually if pull != MFP_PULL_NONE 189 */ 190 tmp = MFPR_AF_SEL(af) | MFPR_DRIVE(drv); 191 192 if (likely(pull == MFP_PULL_NONE)) { 193 p->mfpr_run = tmp | mfpr_lpm[lpm] | mfpr_edge[edge]; 194 p->mfpr_lpm = p->mfpr_run; 195 } else { 196 p->mfpr_lpm = tmp | mfpr_lpm[lpm] | mfpr_edge[edge]; 197 p->mfpr_run = tmp | mfpr_pull[pull]; 198 } 199 200 p->config = c; __mfp_config_run(p); 201 } 202 203 mfpr_sync(); 204 spin_unlock_irqrestore(&mfp_spin_lock, flags); 205} 206 207unsigned long mfp_read(int mfp) 208{ 209 unsigned long val, flags; 210 211 BUG_ON(mfp < 0 || mfp >= MFP_PIN_MAX); 212 213 spin_lock_irqsave(&mfp_spin_lock, flags); 214 val = mfpr_readl(mfp_table[mfp].mfpr_off); 215 spin_unlock_irqrestore(&mfp_spin_lock, flags); 216 217 return val; 218} 219 220void mfp_write(int mfp, unsigned long val) 221{ 222 unsigned long flags; 223 224 BUG_ON(mfp < 0 || mfp >= MFP_PIN_MAX); 225 226 spin_lock_irqsave(&mfp_spin_lock, flags); 227 mfpr_writel(mfp_table[mfp].mfpr_off, val); 228 mfpr_sync(); 229 spin_unlock_irqrestore(&mfp_spin_lock, flags); 230} 231 232void __init mfp_init_base(void __iomem *mfpr_base) 233{ 234 int i; 235 236 /* initialize the table with default - unconfigured */ 237 for (i = 0; i < ARRAY_SIZE(mfp_table); i++) 238 mfp_table[i].config = -1; 239 240 mfpr_mmio_base = mfpr_base; 241} 242 243void __init mfp_init_addr(struct mfp_addr_map *map) 244{ 245 struct mfp_addr_map *p; 246 unsigned long offset, flags; 247 int i; 248 249 spin_lock_irqsave(&mfp_spin_lock, flags); 250 251 /* mfp offset for readback */ 252 mfpr_off_readback = map[0].offset; 253 254 for (p = map; p->start != MFP_PIN_INVALID; p++) { 255 offset = p->offset; 256 i = p->start; 257 258 do { 259 mfp_table[i].mfpr_off = offset; 260 mfp_table[i].mfpr_run = 0; 261 mfp_table[i].mfpr_lpm = 0; 262 offset += 4; i++; 263 } while ((i <= p->end) && (p->end != -1)); 264 } 265 266 spin_unlock_irqrestore(&mfp_spin_lock, flags); 267} 268 269void mfp_config_lpm(void) 270{ 271 struct mfp_pin *p = &mfp_table[0]; 272 int pin; 273 274 for (pin = 0; pin < ARRAY_SIZE(mfp_table); pin++, p++) 275 __mfp_config_lpm(p); 276} 277 278void mfp_config_run(void) 279{ 280 struct mfp_pin *p = &mfp_table[0]; 281 int pin; 282 283 for (pin = 0; pin < ARRAY_SIZE(mfp_table); pin++, p++) 284 __mfp_config_run(p); 285} 286