1 /* 2 * SYSCON GPIO driver 3 * 4 * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 */ 11 12 #include <linux/err.h> 13 #include <linux/gpio.h> 14 #include <linux/module.h> 15 #include <linux/of.h> 16 #include <linux/of_device.h> 17 #include <linux/platform_device.h> 18 #include <linux/regmap.h> 19 #include <linux/mfd/syscon.h> 20 21 #define GPIO_SYSCON_FEAT_IN BIT(0) 22 #define GPIO_SYSCON_FEAT_OUT BIT(1) 23 #define GPIO_SYSCON_FEAT_DIR BIT(2) 24 25 /* SYSCON driver is designed to use 32-bit wide registers */ 26 #define SYSCON_REG_SIZE (4) 27 #define SYSCON_REG_BITS (SYSCON_REG_SIZE * 8) 28 29 /** 30 * struct syscon_gpio_data - Configuration for the device. 31 * compatible: SYSCON driver compatible string. 32 * flags: Set of GPIO_SYSCON_FEAT_ flags: 33 * GPIO_SYSCON_FEAT_IN: GPIOs supports input, 34 * GPIO_SYSCON_FEAT_OUT: GPIOs supports output, 35 * GPIO_SYSCON_FEAT_DIR: GPIOs supports switch direction. 36 * bit_count: Number of bits used as GPIOs. 37 * dat_bit_offset: Offset (in bits) to the first GPIO bit. 38 * dir_bit_offset: Optional offset (in bits) to the first bit to switch 39 * GPIO direction (Used with GPIO_SYSCON_FEAT_DIR flag). 40 */ 41 42 struct syscon_gpio_data { 43 const char *compatible; 44 unsigned int flags; 45 unsigned int bit_count; 46 unsigned int dat_bit_offset; 47 unsigned int dir_bit_offset; 48 }; 49 50 struct syscon_gpio_priv { 51 struct gpio_chip chip; 52 struct regmap *syscon; 53 const struct syscon_gpio_data *data; 54 }; 55 56 static inline struct syscon_gpio_priv *to_syscon_gpio(struct gpio_chip *chip) 57 { 58 return container_of(chip, struct syscon_gpio_priv, chip); 59 } 60 61 static int syscon_gpio_get(struct gpio_chip *chip, unsigned offset) 62 { 63 struct syscon_gpio_priv *priv = to_syscon_gpio(chip); 64 unsigned int val, offs = priv->data->dat_bit_offset + offset; 65 int ret; 66 67 ret = regmap_read(priv->syscon, 68 (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, &val); 69 if (ret) 70 return ret; 71 72 return !!(val & BIT(offs % SYSCON_REG_BITS)); 73 } 74 75 static void syscon_gpio_set(struct gpio_chip *chip, unsigned offset, int val) 76 { 77 struct syscon_gpio_priv *priv = to_syscon_gpio(chip); 78 unsigned int offs = priv->data->dat_bit_offset + offset; 79 80 regmap_update_bits(priv->syscon, 81 (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 82 BIT(offs % SYSCON_REG_BITS), 83 val ? BIT(offs % SYSCON_REG_BITS) : 0); 84 } 85 86 static int syscon_gpio_dir_in(struct gpio_chip *chip, unsigned offset) 87 { 88 struct syscon_gpio_priv *priv = to_syscon_gpio(chip); 89 90 if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) { 91 unsigned int offs = priv->data->dir_bit_offset + offset; 92 93 regmap_update_bits(priv->syscon, 94 (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 95 BIT(offs % SYSCON_REG_BITS), 0); 96 } 97 98 return 0; 99 } 100 101 static int syscon_gpio_dir_out(struct gpio_chip *chip, unsigned offset, int val) 102 { 103 struct syscon_gpio_priv *priv = to_syscon_gpio(chip); 104 105 if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) { 106 unsigned int offs = priv->data->dir_bit_offset + offset; 107 108 regmap_update_bits(priv->syscon, 109 (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 110 BIT(offs % SYSCON_REG_BITS), 111 BIT(offs % SYSCON_REG_BITS)); 112 } 113 114 syscon_gpio_set(chip, offset, val); 115 116 return 0; 117 } 118 119 static const struct syscon_gpio_data clps711x_mctrl_gpio = { 120 /* ARM CLPS711X SYSFLG1 Bits 8-10 */ 121 .compatible = "cirrus,clps711x-syscon1", 122 .flags = GPIO_SYSCON_FEAT_IN, 123 .bit_count = 3, 124 .dat_bit_offset = 0x40 * 8 + 8, 125 }; 126 127 static const struct of_device_id syscon_gpio_ids[] = { 128 { 129 .compatible = "cirrus,clps711x-mctrl-gpio", 130 .data = &clps711x_mctrl_gpio, 131 }, 132 { } 133 }; 134 MODULE_DEVICE_TABLE(of, syscon_gpio_ids); 135 136 static int syscon_gpio_probe(struct platform_device *pdev) 137 { 138 struct device *dev = &pdev->dev; 139 const struct of_device_id *of_id = of_match_device(syscon_gpio_ids, dev); 140 struct syscon_gpio_priv *priv; 141 142 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 143 if (!priv) 144 return -ENOMEM; 145 146 priv->data = of_id->data; 147 148 priv->syscon = 149 syscon_regmap_lookup_by_compatible(priv->data->compatible); 150 if (IS_ERR(priv->syscon)) 151 return PTR_ERR(priv->syscon); 152 153 priv->chip.dev = dev; 154 priv->chip.owner = THIS_MODULE; 155 priv->chip.label = dev_name(dev); 156 priv->chip.base = -1; 157 priv->chip.ngpio = priv->data->bit_count; 158 priv->chip.get = syscon_gpio_get; 159 if (priv->data->flags & GPIO_SYSCON_FEAT_IN) 160 priv->chip.direction_input = syscon_gpio_dir_in; 161 if (priv->data->flags & GPIO_SYSCON_FEAT_OUT) { 162 priv->chip.set = syscon_gpio_set; 163 priv->chip.direction_output = syscon_gpio_dir_out; 164 } 165 166 platform_set_drvdata(pdev, priv); 167 168 return gpiochip_add(&priv->chip); 169 } 170 171 static int syscon_gpio_remove(struct platform_device *pdev) 172 { 173 struct syscon_gpio_priv *priv = platform_get_drvdata(pdev); 174 175 gpiochip_remove(&priv->chip); 176 return 0; 177 } 178 179 static struct platform_driver syscon_gpio_driver = { 180 .driver = { 181 .name = "gpio-syscon", 182 .owner = THIS_MODULE, 183 .of_match_table = syscon_gpio_ids, 184 }, 185 .probe = syscon_gpio_probe, 186 .remove = syscon_gpio_remove, 187 }; 188 module_platform_driver(syscon_gpio_driver); 189 190 MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); 191 MODULE_DESCRIPTION("SYSCON GPIO driver"); 192 MODULE_LICENSE("GPL"); 193