diff options
Diffstat (limited to 'drivers/platform/sprd/pin_switch.c')
| -rwxr-xr-x | drivers/platform/sprd/pin_switch.c | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/drivers/platform/sprd/pin_switch.c b/drivers/platform/sprd/pin_switch.c new file mode 100755 index 00000000..58591d3b --- /dev/null +++ b/drivers/platform/sprd/pin_switch.c @@ -0,0 +1,559 @@ +// #define DEBUG +// #define pr_fmt(fmt) "---sprd pinswitch---"fmt + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <asm/uaccess.h> +#include <linux/printk.h> +#include <soc/sprd/hardware.h> +#include <soc/sprd/arch_lock.h> +#include <linux/of.h> +#include <soc/sprd/pin_switch.h> +#include <linux/regulator/consumer.h> + +#ifdef CONFIG_PROC_FS + +#undef SPRD_PIN_BASE +unsigned long SPRD_PIN_BASE = 0; +struct sci_pin_switch { + const char *dirname; + const char *filename; + u32 reg; + u32 bit_offset; + u32 bit_width; + u32 func; +}; + +static struct sci_pin_switch sci_pin_switch_array[] = { +#if !(defined(CONFIG_ARCH_SCX15)||defined(CONFIG_ARCH_SCX35L)) + {"", "vbc_dac_iislrck_pin_in_sel", 0x4, 24, 1, 0}, + {"", "vbc_dac_iisclk_pin_in_sel", 0x4, 23, 1, 0}, + {"", "vbc_adc_iislrck_pin_in_sel", 0x4, 22, 1, 0}, + {"", "vbc_adc_iisdi_pin_in_sel", 0x4, 21, 1, 0}, + {"", "vbc_adc_iisclk_pin_in_sel", 0x4, 20, 1, 0}, + {"", "iis3mck_pin_in_sel", 0x4, 19, 1, 0}, + {"", "iis3lrck_pin_in_sel", 0x4, 18, 1, 0}, + {"", "iis3di_pin_in_sel", 0x4, 17, 1, 0}, + {"", "iis3clk_pin_in_sel", 0x4, 16, 1, 0}, + {"", "iis2mck_pin_in_sel", 0x4, 15, 1, 0}, + {"", "iis2lrck_pin_in_sel", 0x4, 14, 1, 0}, + {"", "iis2di_pin_in_sel", 0x4, 13, 1, 0}, + {"", "iis2clk_pin_in_sel", 0x4, 12, 1, 0}, +#if defined(CONFIG_ARCH_SCX30G) /*tshark add */ + {"", "bt_iis_con_eb", 0xc, 30, 1, 0}, + {"", "pad_oe_iis3_mck", 0xc, 27, 1, 0}, + {"", "pad_oe_iis2_mck", 0xc, 26, 1, 0}, + {"", "pad_oe_iis1_mck", 0xc, 25, 1, 0}, + {"", "pad_oe_iis0_mck", 0xc, 24, 1, 0}, +#endif +#endif + {"", "iis23_loop_sel", 0xc, 5, 1, 0}, + {"", "iis13_loop_sel", 0xc, 4, 1, 0}, + {"", "iis12_loop_sel", 0xc, 3, 1, 0}, + {"", "iis03_loop_sel", 0xc, 2, 1, 0}, + {"", "iis02_loop_sel", 0xc, 1, 1, 0}, + {"", "iis01_loop_sel", 0xc, 0, 1, 0}, +}; + + +#if defined(CONFIG_ARCH_SCX35L) +static struct sci_pin_switch iis_0_array[] = { + {"iis0_sys_sel", "ap_iis0", 0xc, 6, 3, 0}, + {"iis0_sys_sel", "cp0_iis0", 0xc, 6, 3, 1}, + {"iis0_sys_sel", "cp1_iis0", 0xc, 6, 3, 2}, + {"iis0_sys_sel", "vbc_iis0", 0xc, 6, 3, 3}, + {"iis0_sys_sel", "vbc_iis1", 0xc, 6, 3, 4}, +}; + +static struct sci_pin_switch iis_1_array[] = { + {"iis1_sys_sel", "ap_iis1", 0xc, 9, 3, 0}, + {"iis1_sys_sel", "cp0_iis1", 0xc, 9, 3, 1}, + {"iis1_sys_sel", "cp1_iis1", 0xc, 9, 3, 2}, + {"iis1_sys_sel", "vbc_iis0", 0xc, 9, 3, 3}, + {"iis1_sys_sel", "vbc_iis1", 0xc, 9, 3, 4}, +}; + +static struct sci_pin_switch iis_2_array[] = { + {"iis2_sys_sel", "ap_iis2", 0xc, 12, 3, 0}, + {"iis2_sys_sel", "cp0_iis2", 0xc, 12, 3, 1}, + {"iis2_sys_sel", "cp1_iis2", 0xc, 12, 3, 2}, + {"iis2_sys_sel", "vbc_iis0", 0xc, 12, 3, 3}, + {"iis2_sys_sel", "vbc_iis1", 0xc, 12, 3, 4}, +}; + +static struct sci_pin_switch iis_3_array[] = { + {"iis3_sys_sel", "ap_iis3", 0xc, 15, 3, 0}, + {"iis3_sys_sel", "cp0_iis3", 0xc, 15, 3, 1}, + {"iis3_sys_sel", "cp1_iis3", 0xc, 15, 3, 2}, + {"iis3_sys_sel", "vbc_iis0", 0xc, 15, 3, 3}, + {"iis3_sys_sel", "vbc_iis1", 0xc, 15, 3, 4}, +}; +#else +static struct sci_pin_switch bt_iis_sys_sel_array[] = { + {"bt_iis_sys_sel", "cp0_iis0", 0x8, 28, 4, 0}, + {"bt_iis_sys_sel", "cp0_iis1", 0x8, 28, 4, 1}, + {"bt_iis_sys_sel", "cp0_iis2", 0x8, 28, 4, 2}, + {"bt_iis_sys_sel", "cp0_iis3", 0x8, 28, 4, 3}, + {"bt_iis_sys_sel", "cp1_iis0", 0x8, 28, 4, 4}, + {"bt_iis_sys_sel", "cp1_iis1", 0x8, 28, 4, 5}, + {"bt_iis_sys_sel", "cp1_iis2", 0x8, 28, 4, 6}, + {"bt_iis_sys_sel", "cp1_iis3", 0x8, 28, 4, 7}, + {"bt_iis_sys_sel", "ap_iis0", 0x8, 28, 4, 8}, + {"bt_iis_sys_sel", "ap_iis1", 0x8, 28, 4, 9}, + {"bt_iis_sys_sel", "ap_iis2", 0x8, 28, 4, 10}, + {"bt_iis_sys_sel", "ap_iis3", 0x8, 28, 4, 11}, + {"bt_iis_sys_sel", "vbc_iis", 0x8, 28, 4, 12}, + {"bt_iis_sys_sel", "rsv_d", 0x8, 28, 4, 13}, + {"bt_iis_sys_sel", "rsv_e", 0x8, 28, 4, 14}, + {"bt_iis_sys_sel", "rsv_f", 0x8, 28, 4, 15}, +}; + +static struct sci_pin_switch iis_0_array[] = { + {"iis0_sys_sel", "ap_iis0", 0xc, 6, 3, 0}, + {"iis0_sys_sel", "cp0_iis0", 0xc, 6, 3, 1}, + {"iis0_sys_sel", "cp1_iis0", 0xc, 6, 3, 2}, + {"iis0_sys_sel", "cp2_iis0", 0xc, 6, 3, 3}, + {"iis0_sys_sel", "vbc_iis0", 0xc, 6, 3, 4}, +}; + +static struct sci_pin_switch iis_1_array[] = { + {"iis1_sys_sel", "ap_iis1", 0xc, 9, 3, 0}, + {"iis1_sys_sel", "cp0_iis1", 0xc, 9, 3, 1}, + {"iis1_sys_sel", "cp1_iis1", 0xc, 9, 3, 2}, + {"iis1_sys_sel", "cp2_iis1", 0xc, 9, 3, 3}, +}; + +#if !defined(CONFIG_ARCH_SCX15) +static struct sci_pin_switch iis_2_array[] = { + {"iis2_sys_sel", "ap_iis2", 0xc, 12, 3, 0}, + {"iis2_sys_sel", "cp0_iis2", 0xc, 12, 3, 1}, + {"iis2_sys_sel", "cp1_iis2", 0xc, 12, 3, 2}, + {"iis2_sys_sel", "cp2_iis2", 0xc, 12, 3, 3}, +}; + +static struct sci_pin_switch iis_3_array[] = { + {"iis3_sys_sel", "ap_iis3", 0xc, 15, 3, 0}, + {"iis3_sys_sel", "cp0_iis3", 0xc, 15, 3, 1}, + {"iis3_sys_sel", "cp1_iis3", 0xc, 15, 3, 2}, + {"iis3_sys_sel", "cp2_iis3", 0xc, 15, 3, 3}, +}; +#endif +#endif + +#if defined(CONFIG_PIN_POWER_DOMAIN_SWITCH) +struct pin_reg_desc { + struct sprd_pin_switch_platform_data *platform_data; + struct notifier_block nb; + struct regulator_bulk_data supplies; +} pin_reg_desc_array[PD_CNT]; +#endif + +static struct sci_pin_switch_dir { + struct sci_pin_switch *sci_pin_switch; + u32 array_size; +} sci_pin_switch_dir_array[] = { +#if !defined(CONFIG_ARCH_SCX35L) + { + bt_iis_sys_sel_array, ARRAY_SIZE(bt_iis_sys_sel_array)}, +#endif + { + iis_0_array, ARRAY_SIZE(iis_0_array)}, { + iis_1_array, ARRAY_SIZE(iis_1_array)}, +#if !defined(CONFIG_ARCH_SCX15) + { + iis_2_array, ARRAY_SIZE(iis_2_array)}, { + iis_3_array, ARRAY_SIZE(iis_3_array)}, +#endif +}; + +u32 pinmap_get(u32 offset) +{ + return readl(SPRD_PIN_BASE + offset); +} + +EXPORT_SYMBOL(pinmap_get); +int pinmap_set(u32 offset, u32 value) +{ + unsigned long flags; + __arch_default_lock(HWLOCK_GLB, &flags); + writel(value, SPRD_PIN_BASE + offset); + __arch_default_unlock(HWLOCK_GLB, &flags); + return 0; +} + +EXPORT_SYMBOL(pinmap_set); + +/* +* #define IIS_TO_AP (0) +* #define IIS_TO_CP0 (1) +* #define IIS_TO_CP1 (2) +* #define IIS_TO_CP2 (3) +* #define PIN_CTL_REG3 (SPRD_PIN_BASE + 0xc) +*/ +static int read_write_pin_switch(int is_read, int v, struct sci_pin_switch *p) +{ + int val = 0; + u32 shift = p->bit_offset; + u32 mask = (1 << (p->bit_width)) - 1; + if ((shift > 31)) + BUG_ON(1); + if (v > mask) + printk("v:0x%x overflow bitwidth:%ud, mask:0x%x it\n", + v, p->bit_width, mask); + val = pinmap_get(p->reg); + if (is_read) { + val >>= shift; + val &= mask; + return val; + } else { + val &= ~(mask << shift); + val |= (v & mask) << shift; + pinmap_set(p->reg, val); + } + return val; +} + +static int pin_switch_proc_show(struct seq_file *m, void *v) +{ + int val = 0; + struct sci_pin_switch *p = (struct sci_pin_switch *)(m->private); + val = read_write_pin_switch(1, (int)val, p); + seq_printf(m, "%d\n", val); + return 0; +} + +static int pin_switch_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, pin_switch_proc_show, PDE_DATA(inode)); +} + +static ssize_t pin_switch_proc_write(struct file *file, + const char __user * buffer, + size_t count, loff_t * pos) +{ + long val = 0; + int ret = 0; + struct sci_pin_switch *p = + (struct sci_pin_switch *)(PDE_DATA(file_inode(file))); + ret = kstrtol_from_user(buffer, count, 0, &val); + if (ret) { + pr_err("input err\n"); + return -EINVAL; + } + read_write_pin_switch(0, val, p); + return count; +} + +static const struct file_operations pin_switch_fops = { + .open = pin_switch_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = pin_switch_proc_write, +}; + +static int pin_switch_dir_proc_show(struct seq_file *m, void *v) +{ + int val = 0; + int func_tmp = 0; + struct sci_pin_switch *p = (struct sci_pin_switch *)m->private; + func_tmp = read_write_pin_switch(1, val, p); + if (p->func == func_tmp) + val = 1; + else + val = 0; + seq_printf(m, "%d\n", val); + return 0; +} + +static int pin_switch_dir_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, pin_switch_dir_proc_show, PDE_DATA(inode)); +} + +static ssize_t pin_switch_dir_proc_write(struct file *file, + const char __user * buffer, + size_t count, loff_t * pos) +{ + int val = 0; + int ret = 0; + struct sci_pin_switch *p = + (struct sci_pin_switch *)(PDE_DATA(file_inode(file))); + ret = kstrtol_from_user(buffer, count, 0, &val); + if (ret) { + pr_err("input err\n"); + return -EINVAL; + } + if (val == 1) + val = p->func; + else /*if val = 0 or other value, just ignore it */ + return 0; + read_write_pin_switch(0, val, p); + return count; +} + +static const struct file_operations pin_switch_dir_fops = { + .open = pin_switch_dir_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = pin_switch_dir_proc_write, +}; + +static struct proc_dir_entry *pin_switch_proc_base; +static int __init pin_switch_proc_add(struct sci_pin_switch *pin_switch) +{ + struct proc_dir_entry *tmp_proc; + tmp_proc = + proc_create_data(pin_switch->filename, S_IRWXUGO, + pin_switch_proc_base, &pin_switch_fops, + pin_switch); + if (!tmp_proc) + return -ENOMEM; + return 0; +} + +static int __init pin_switch_proc_add_dir(struct sci_pin_switch_dir + *pin_switch_dir) +{ + struct proc_dir_entry *tmp_proc_dir; + struct proc_dir_entry *tmp_proc; + int i; + if (pin_switch_dir->sci_pin_switch->dirname == NULL) + return EINVAL; + /* has dir name, first create parent dir */ + tmp_proc_dir = + proc_mkdir(pin_switch_dir->sci_pin_switch->dirname, + pin_switch_proc_base); + if (!tmp_proc_dir) + return -ENOMEM; + + for (i = 0; i < pin_switch_dir->array_size; ++i) { + if (pin_switch_dir->sci_pin_switch[i].filename == NULL) + return EINVAL; + tmp_proc = + proc_create_data(pin_switch_dir->sci_pin_switch[i].filename, + S_IRWXUGO, tmp_proc_dir, + &pin_switch_dir_fops, + &pin_switch_dir->sci_pin_switch[i]); + if (!tmp_proc) + return -EINVAL; + } + return 0; +} + +#if defined(CONFIG_PIN_POWER_DOMAIN_SWITCH) +static int sc271x_regulator_event(struct notifier_block *regu_nb, + unsigned long event, void *data) +{ + unsigned long flags; + struct pin_reg_desc *p_pin_reg_desc = + container_of(regu_nb, struct pin_reg_desc, nb); + unsigned long best_data = (unsigned long *)data; + const char *regu_name; + u32 bit; + u32 reg; + + if (!p_pin_reg_desc) { + return -EINVAL; + } + regu_name = p_pin_reg_desc->supplies.supply; + bit = p_pin_reg_desc->platform_data->bit_offset; + reg = p_pin_reg_desc->platform_data->reg; + pr_info("event:0x%x, best_data:0x%x\n", event, best_data); + + if (event & REGULATOR_EVENT_VOLTAGE_CHANGE) { + if (p_pin_reg_desc) { + if (best_data > VOL_THRESHOLD) { + #if (defined(CONFIG_ARCH_SCX20L)||defined(CONFIG_ARCH_SCX20)) + pinmap_set(reg, (pinmap_get(reg) | (1 << bit))); + #else + pinmap_set(reg, (pinmap_get(reg) & ~(1 << bit))); + #endif + pr_info + ("%s()->Line:%d, --REGULATOR_EVENT_VOLTAGE_CHANGE--> %s event %ld, data %ld\n", + __func__, __LINE__, regu_name, event, + best_data); + } else { + #if (defined(CONFIG_ARCH_SCX20L)||defined(CONFIG_ARCH_SCX20)) + pinmap_set(reg, (pinmap_get(reg) & ~(1 << bit))); + #else + pinmap_set(reg, (pinmap_get(reg) | (1 << bit))); + #endif + pr_info + ("%s()->Line:%d, --REGULATOR_EVENT_VOLTAGE_CHANGE--> %s event %ld, data %ld\n", + __func__, __LINE__, regu_name, event, + best_data); + } + } + } else if (event & REGULATOR_EVENT_DISABLE) { + pr_info + ("%s()->Line:%d, --REGULATOR_EVENT_DISABLE--> %s event %ld\n", + __func__, __LINE__, regu_name, event); + } + + return NOTIFY_OK; +} + +static int pin_regulator_notifier_register(struct pin_reg_desc *table) +{ + int i, ret; + struct regulator *regu; + + for (i = 0; i < PD_CNT; i++) { + if (IS_ERR_OR_NULL(table[i].platform_data->power_domain)) { + pr_err("invalid platform data: 0x%p\n", + table[i].platform_data->power_domain); + return -EINVAL; + } + regu = + regulator_get(NULL, table[i].platform_data->power_domain); + pr_info("%s, %d, regu_name:%s\n", __FUNCTION__, __LINE__, + table[i].platform_data->power_domain); + if (IS_ERR_OR_NULL(regu)) { + pr_err("no regu %s !\n", + table[i].platform_data->power_domain); + return -ENXIO;; + } + table[i].supplies.consumer = regu; + table[i].supplies.supply = table[i].platform_data->power_domain; + table[i].nb.notifier_call = sc271x_regulator_event; + ret = regulator_register_notifier(regu, &(table[i].nb)); + if (ret) { + pr_err("alert: regu %s reg notifier failed\n", + table[i].platform_data->power_domain); + } + } + return 0; +} + +static int sprd_pin_switch_probe(struct platform_device *pdev) +{ + int i, ret; + struct sprd_pin_switch_platform_data *p_pin_switch_data; + +#ifdef CONFIG_OF + struct resource *res; + struct device_node *np = pdev->dev.of_node; + + p_pin_switch_data = + devm_kzalloc(&pdev->dev, sizeof(*p_pin_switch_data) * PD_CNT, + GFP_KERNEL); + if (!p_pin_switch_data) { + pr_err("pin_switch alloc err\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "No reg of property specified\n"); + return -ENODEV; + } + + SPRD_PIN_BASE = (unsigned long)ioremap_nocache(res->start,res->end - res->start); + if (!SPRD_PIN_BASE) + panic("pin_switch get address failed!\n"); + + + pr_info("-----SPRD_PIN_BASE:0x%x-----\n", SPRD_PIN_BASE); + + for (i = 0; i < PD_CNT; i++) { + /*1. get pin power domain vddname */ + ret = + of_property_read_string_index(np, "pwr_domain", i, + &p_pin_switch_data[i]. + power_domain); + if (ret) { + pr_err("fail to get pwr_domain\n"); + return ret; + } + pr_info("pwr_domain[%d]:%s\n", i, + p_pin_switch_data[i].power_domain); + /*2. get pin power domain reg ctrl description info */ + ret = + of_property_read_u32_index(np, "ctrl_desc", i * 3, + &p_pin_switch_data[i].reg); + if (ret) { + pr_err("fail to get ctrl_desc reg\n"); + return ret; + } + pr_info("reg[%d]:0x%x\n", i, p_pin_switch_data[i].reg); + + ret = + of_property_read_u32_index(np, "ctrl_desc", i * 3 + 1, + &p_pin_switch_data[i]. + bit_offset); + if (ret) { + pr_err("fail to get ctrl_desc bit_offset\n"); + return ret; + } + pr_info("bit_offset[%d]:0x%x\n", i, + p_pin_switch_data[i].bit_offset); + ret = + of_property_read_u32_index(np, "ctrl_desc", i * 3 + 2, + &p_pin_switch_data[i].bit_width); + if (ret) { + pr_err("fail to get ctrl_desc bit_width\n"); + return ret; + } + pr_info("bit_width[%d]:0x%x\n", i, + p_pin_switch_data[i].bit_width); + } +#else + p_pin_switch_data = dev_get_platdata(&pdev->dev); + if (IS_ERR_OR_NULL(p_pin_switch_data)) { + pr_err("invalid platform data: 0x%p\n", p_pin_switch_data); + return -EINVAL; + } +#endif + for (i = 0; i < PD_CNT; i++) { + pin_reg_desc_array[i].platform_data = &p_pin_switch_data[i]; // + i*sizeof(struct sprd_pin_switch_platform_data); + } + ret = pin_regulator_notifier_register(pin_reg_desc_array); + if (ret) { + pr_err("register notifier err\n"); + } + return ret; +} + +static struct of_device_id sprd_pinctrl_match_table[] = { + {.compatible = "sprd,pinctrl",}, + {}, +}; + +static struct platform_driver pin_switch_driver = { + .probe = sprd_pin_switch_probe, + .driver = { + .owner = THIS_MODULE, + .name = "pin_switch", + .of_match_table = of_match_ptr(sprd_pinctrl_match_table), + }, +}; +#endif + +int __init pin_switch_proc_init(void) +{ + int i; + int ret = 0; + pin_switch_proc_base = proc_mkdir("pin_switch", NULL); + if (!pin_switch_proc_base) + return -ENOMEM; + for (i = 0; i < ARRAY_SIZE(sci_pin_switch_array); ++i) { + pin_switch_proc_add(&sci_pin_switch_array[i]); + } + for (i = 0; i < ARRAY_SIZE(sci_pin_switch_dir_array); ++i) { + pin_switch_proc_add_dir(&sci_pin_switch_dir_array[i]); + } +#if defined(CONFIG_PIN_POWER_DOMAIN_SWITCH) + ret = platform_driver_register(&pin_switch_driver); +#endif + return ret; +} + +subsys_initcall_sync(pin_switch_proc_init); +#else +#error "CONFIG_PROC_FS needed by mach-sc/pin_switch" +#endif /* CONFIG_PROC_FS */ |
