| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * MAX1600 PCMCIA power switch library |
| * |
| * Copyright (C) 2016 Russell King |
| */ |
| #include <linux/device.h> |
| #include <linux/module.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/slab.h> |
| #include "max1600.h" |
| |
| static const char *max1600_gpio_name[2][MAX1600_GPIO_MAX] = { |
| { "a0vcc", "a1vcc", "a0vpp", "a1vpp" }, |
| { "b0vcc", "b1vcc", "b0vpp", "b1vpp" }, |
| }; |
| |
| int max1600_init(struct device *dev, struct max1600 **ptr, |
| unsigned int channel, unsigned int code) |
| { |
| struct max1600 *m; |
| int chan; |
| int i; |
| |
| switch (channel) { |
| case MAX1600_CHAN_A: |
| chan = 0; |
| break; |
| case MAX1600_CHAN_B: |
| chan = 1; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| if (code != MAX1600_CODE_LOW && code != MAX1600_CODE_HIGH) |
| return -EINVAL; |
| |
| m = devm_kzalloc(dev, sizeof(*m), GFP_KERNEL); |
| if (!m) |
| return -ENOMEM; |
| |
| m->dev = dev; |
| m->code = code; |
| |
| for (i = 0; i < MAX1600_GPIO_MAX; i++) { |
| const char *name; |
| |
| name = max1600_gpio_name[chan][i]; |
| if (i != MAX1600_GPIO_0VPP) { |
| m->gpio[i] = devm_gpiod_get(dev, name, GPIOD_OUT_LOW); |
| } else { |
| m->gpio[i] = devm_gpiod_get_optional(dev, name, |
| GPIOD_OUT_LOW); |
| if (!m->gpio[i]) |
| break; |
| } |
| if (IS_ERR(m->gpio[i])) |
| return PTR_ERR(m->gpio[i]); |
| } |
| |
| *ptr = m; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(max1600_init); |
| |
| int max1600_configure(struct max1600 *m, unsigned int vcc, unsigned int vpp) |
| { |
| DECLARE_BITMAP(values, MAX1600_GPIO_MAX) = { 0, }; |
| int n = MAX1600_GPIO_0VPP; |
| |
| if (m->gpio[MAX1600_GPIO_0VPP]) { |
| if (vpp == 0) { |
| __assign_bit(MAX1600_GPIO_0VPP, values, 0); |
| __assign_bit(MAX1600_GPIO_1VPP, values, 0); |
| } else if (vpp == 120) { |
| __assign_bit(MAX1600_GPIO_0VPP, values, 0); |
| __assign_bit(MAX1600_GPIO_1VPP, values, 1); |
| } else if (vpp == vcc) { |
| __assign_bit(MAX1600_GPIO_0VPP, values, 1); |
| __assign_bit(MAX1600_GPIO_1VPP, values, 0); |
| } else { |
| dev_err(m->dev, "unrecognised Vpp %u.%uV\n", |
| vpp / 10, vpp % 10); |
| return -EINVAL; |
| } |
| n = MAX1600_GPIO_MAX; |
| } else if (vpp != vcc && vpp != 0) { |
| dev_err(m->dev, "no VPP control\n"); |
| return -EINVAL; |
| } |
| |
| if (vcc == 0) { |
| __assign_bit(MAX1600_GPIO_0VCC, values, 0); |
| __assign_bit(MAX1600_GPIO_1VCC, values, 0); |
| } else if (vcc == 33) { /* VY */ |
| __assign_bit(MAX1600_GPIO_0VCC, values, 1); |
| __assign_bit(MAX1600_GPIO_1VCC, values, 0); |
| } else if (vcc == 50) { /* VX */ |
| __assign_bit(MAX1600_GPIO_0VCC, values, 0); |
| __assign_bit(MAX1600_GPIO_1VCC, values, 1); |
| } else { |
| dev_err(m->dev, "unrecognised Vcc %u.%uV\n", |
| vcc / 10, vcc % 10); |
| return -EINVAL; |
| } |
| |
| if (m->code == MAX1600_CODE_HIGH) { |
| /* |
| * Cirrus mode appears to be the same as Intel mode, |
| * except the VCC pins are inverted. |
| */ |
| __change_bit(MAX1600_GPIO_0VCC, values); |
| __change_bit(MAX1600_GPIO_1VCC, values); |
| } |
| |
| return gpiod_set_array_value_cansleep(n, m->gpio, NULL, values); |
| } |
| EXPORT_SYMBOL_GPL(max1600_configure); |
| |
| MODULE_DESCRIPTION("MAX1600 PCMCIA power switch library"); |
| MODULE_LICENSE("GPL v2"); |