Created
April 11, 2014 06:34
-
-
Save Kazu-zamasu/10444312 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright(c) 2013 Intel Corporation. All rights reserved. | |
* | |
* This program is free software; you can redistribute it and/or modify | |
* it under the terms of version 2 of the GNU General Public License as | |
* published by the Free Software Foundation. | |
* | |
* This program is distributed in the hope that it will be useful, but | |
* WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
* General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program; if not, write to the Free Software | |
* Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | |
* | |
* Contact Information: | |
* Intel Corporation | |
*/ | |
/* | |
* Driver for Cypress CY8C9540A I/O Expander and PWM | |
* | |
* Izmir-specific. | |
* Based on gpio-adp5588. | |
*/ | |
#include <linux/i2c.h> | |
#include <linux/interrupt.h> | |
#include <linux/irq.h> | |
#include <linux/gpio.h> | |
#include <linux/kernel.h> | |
#include <linux/module.h> | |
#include <linux/pwm.h> | |
#include <linux/slab.h> | |
#define DRV_NAME "cy8c9540a" | |
/* CY8C9540A settings */ | |
#define NGPIO 40 | |
#define NPWM 8 | |
#define PWM_MAX_PERIOD 0xff | |
#define DEVID_FAMILY_CY8C9540A 0x40 | |
#define DEVID_FAMILY_MASK 0xf0 | |
#define NPORTS 6 | |
/* Register offset */ | |
#define REG_INPUT_PORT0 0x00 | |
#define REG_OUTPUT_PORT0 0x08 | |
#define REG_INTR_STAT_PORT0 0x10 | |
#define REG_PORT_SELECT 0x18 | |
#define REG_INTR_MASK 0x19 | |
#define REG_SELECT_PWM 0x1a | |
#define REG_PIN_DIR 0x1c | |
#define REG_DRIVE_PULLUP 0x1d | |
#define REG_PWM_SELECT 0x28 | |
#define REG_PWM_CLK 0x29 | |
#define REG_PWM_PERIOD 0x2a | |
#define REG_PWM_PULSE_W 0x2b | |
#define REG_ENABLE 0x2d | |
#define REG_DEVID_STAT 0x2e | |
struct cy8c9540a { | |
struct i2c_client *client; | |
struct gpio_chip gpio_chip; | |
struct pwm_chip pwm_chip; | |
struct mutex lock; | |
/* protect serialized access to the interrupt controller bus */ | |
struct mutex irq_lock; | |
/* cached output registers */ | |
u8 outreg_cache[NPORTS]; | |
/* cached IRQ mask */ | |
u8 irq_mask_cache[NPORTS]; | |
/* IRQ mask to be applied */ | |
u8 irq_mask[NPORTS]; | |
}; | |
/* Per-port GPIO offset */ | |
static const u8 cy8c9540a_port_offs[] = { | |
0, | |
8, | |
16, | |
20, | |
28, | |
36, | |
}; | |
/* Galileo-specific data. */ | |
#define GPIO_BASE_ID 16 | |
#define GPIO_IRQBASE 64 | |
#define PWM_BASE_ID 0 | |
#define PWM_CLK 0x00 /* see resulting PWM_TCLK_NS */ | |
#define PWM_TCLK_NS 31250 /* 32kHz */ | |
#define SOC_GPIO_INT_PIN 13 | |
/* PWM-to-GPIO mapping (0 == first Cypress GPIO). */ | |
#define PWM_UNUSED -1 | |
static const int pwm2gpio_mapping[] = { | |
PWM_UNUSED, | |
3, | |
PWM_UNUSED, | |
2, | |
9, | |
1, | |
8, | |
0, | |
}; | |
static inline u8 cypress_get_port(unsigned gpio) | |
{ | |
u8 i = 0; | |
for (i = 0; i < sizeof(cy8c9540a_port_offs) - 1; i ++) { | |
if (! (gpio / cy8c9540a_port_offs[i + 1])) | |
break; | |
} | |
return i; | |
} | |
static inline u8 cypress_get_offs(unsigned gpio, u8 port) | |
{ | |
return gpio - cy8c9540a_port_offs[port]; | |
} | |
static int cy8c9540a_gpio_get_value(struct gpio_chip *chip, unsigned gpio) | |
{ | |
s32 ret = 0; | |
u8 port = 0; | |
u8 in_reg = 0; | |
struct cy8c9540a *dev = | |
container_of(chip, struct cy8c9540a, gpio_chip); | |
struct i2c_client *client = dev->client; | |
port = cypress_get_port(gpio); | |
in_reg = REG_INPUT_PORT0 + port; | |
ret = i2c_smbus_read_byte_data(client, in_reg); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't read input port%u\n", in_reg); | |
} | |
return !!(ret & BIT(cypress_get_offs(gpio, port))); | |
} | |
static void cy8c9540a_gpio_set_value(struct gpio_chip *chip, | |
unsigned gpio, int val) | |
{ | |
s32 ret = 0; | |
struct cy8c9540a *dev = | |
container_of(chip, struct cy8c9540a, gpio_chip); | |
struct i2c_client *client = dev->client; | |
u8 port = cypress_get_port(gpio); | |
u8 out_reg = REG_OUTPUT_PORT0 + port; | |
mutex_lock(&dev->lock); | |
if (val) { | |
dev->outreg_cache[port] |= BIT(cypress_get_offs(gpio, port)); | |
} else { | |
dev->outreg_cache[port] &= ~BIT(cypress_get_offs(gpio, port)); | |
} | |
ret = i2c_smbus_write_byte_data(client, out_reg, | |
dev->outreg_cache[port]); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write output port%u\n", port); | |
} | |
mutex_unlock(&dev->lock); | |
} | |
static int cy8c9540a_gpio_set_drive(struct gpio_chip *chip, unsigned gpio, | |
unsigned mode) | |
{ | |
int ret = 0; | |
struct cy8c9540a *dev = | |
container_of(chip, struct cy8c9540a, gpio_chip); | |
struct i2c_client *client = dev->client; | |
u8 port = cypress_get_port(gpio); | |
u8 pin = cypress_get_offs(gpio, port); | |
u8 offs = 0; | |
u8 val = 0; | |
switch(mode) { | |
case GPIOF_DRIVE_PULLUP: | |
offs = 0x0; | |
break; | |
case GPIOF_DRIVE_STRONG: | |
offs = 0x4; | |
break; | |
case GPIOF_DRIVE_HIZ: | |
offs = 0x6; | |
break; | |
default: | |
/* | |
* See databook for alternative modes. This driver won't | |
* support them though. | |
*/ | |
return -EINVAL; | |
break; | |
} | |
mutex_lock(&dev->lock); | |
ret = i2c_smbus_write_byte_data(client, REG_PORT_SELECT, port); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't select port %u\n", port); | |
goto end; | |
} | |
ret = i2c_smbus_read_byte_data(client, REG_DRIVE_PULLUP + offs); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't read pin direction\n"); | |
goto end; | |
} | |
val = (u8)(ret | BIT(pin)); | |
ret = i2c_smbus_write_byte_data(client, REG_DRIVE_PULLUP + offs, val); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't set drive mode port %u\n", port); | |
goto end; | |
} | |
ret = 0; | |
end: | |
mutex_unlock(&dev->lock); | |
return ret; | |
} | |
static int cy8c9540a_gpio_direction(struct gpio_chip *chip, unsigned gpio, | |
int out, int val) | |
{ | |
s32 ret = 0; | |
u8 pins = 0; | |
struct cy8c9540a *dev = | |
container_of(chip, struct cy8c9540a, gpio_chip); | |
struct i2c_client *client = dev->client; | |
u8 port = cypress_get_port(gpio); | |
if (out) { | |
cy8c9540a_gpio_set_value(chip, gpio, val); | |
} | |
mutex_lock(&dev->lock); | |
ret = i2c_smbus_write_byte_data(client, REG_PORT_SELECT, port); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't select port %u\n", port); | |
goto end; | |
} | |
ret = i2c_smbus_read_byte_data(client, REG_PIN_DIR); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't read pin direction\n"); | |
goto end; | |
} | |
pins = (u8)ret & 0xff; | |
if (out) { | |
pins &= ~BIT(cypress_get_offs(gpio, port)); | |
} else { | |
pins |= BIT(cypress_get_offs(gpio, port)); | |
} | |
ret = i2c_smbus_write_byte_data(client, REG_PIN_DIR, pins); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write pin direction\n"); | |
} | |
end: | |
mutex_unlock(&dev->lock); | |
return ret; | |
} | |
static int cy8c9540a_gpio_direction_output(struct gpio_chip *chip, | |
unsigned gpio, int val) | |
{ | |
return cy8c9540a_gpio_direction(chip, gpio, 1, val); | |
} | |
static int cy8c9540a_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) | |
{ | |
return cy8c9540a_gpio_direction(chip, gpio, 0, 0); | |
} | |
static void cy8c9540a_irq_bus_lock(struct irq_data *d) | |
{ | |
struct cy8c9540a *dev = irq_data_get_irq_chip_data(d); | |
mutex_lock(&dev->irq_lock); | |
} | |
static void cy8c9540a_irq_bus_sync_unlock(struct irq_data *d) | |
{ | |
struct cy8c9540a *dev = irq_data_get_irq_chip_data(d); | |
struct i2c_client *client = dev->client; | |
int ret = 0; | |
int i = 0; | |
for (i = 0; i < NPORTS; i++) { | |
if (dev->irq_mask_cache[i] ^ dev->irq_mask[i]) { | |
dev->irq_mask_cache[i] = dev->irq_mask[i]; | |
ret = i2c_smbus_write_byte_data(client, REG_PORT_SELECT, i); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't select port %u\n", i); | |
goto end; | |
} | |
ret = i2c_smbus_write_byte_data(client, REG_INTR_MASK, dev->irq_mask[i]); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write int mask on port %u\n", i); | |
goto end; | |
} | |
} | |
} | |
end: | |
mutex_unlock(&dev->irq_lock); | |
} | |
static void cy8c9540a_irq_mask(struct irq_data *d) | |
{ | |
struct cy8c9540a *dev = irq_data_get_irq_chip_data(d); | |
unsigned gpio = d->irq - GPIO_IRQBASE; | |
u8 port = cypress_get_port(gpio); | |
dev->irq_mask[port] |= BIT(cypress_get_offs(gpio, port)); | |
} | |
static void cy8c9540a_irq_unmask(struct irq_data *d) | |
{ | |
struct cy8c9540a *dev = irq_data_get_irq_chip_data(d); | |
unsigned gpio = d->irq - GPIO_IRQBASE; | |
u8 port = cypress_get_port(gpio); | |
dev->irq_mask[port] &= ~BIT(cypress_get_offs(gpio, port)); | |
} | |
static int cy8c9540a_gpio_to_irq(struct gpio_chip *chip, unsigned gpio) | |
{ | |
return GPIO_IRQBASE + gpio; | |
} | |
static int cy8c9540a_irq_set_type(struct irq_data *d, unsigned int type) | |
{ | |
struct cy8c9540a *dev = irq_data_get_irq_chip_data(d); | |
int ret = 0; | |
if ((type != IRQ_TYPE_EDGE_BOTH)) { | |
dev_err(&dev->client->dev, "irq %d: unsupported type %d\n", | |
d->irq, type); | |
ret = -EINVAL; | |
goto end; | |
} | |
end: | |
return ret; | |
} | |
static struct irq_chip cy8c9540a_irq_chip = { | |
.name = "cy8c9540a-irq", | |
.irq_mask = cy8c9540a_irq_mask, | |
.irq_unmask = cy8c9540a_irq_unmask, | |
.irq_bus_lock = cy8c9540a_irq_bus_lock, | |
.irq_bus_sync_unlock = cy8c9540a_irq_bus_sync_unlock, | |
.irq_set_type = cy8c9540a_irq_set_type, | |
}; | |
static irqreturn_t cy8c9540a_irq_handler(int irq, void *devid) | |
{ | |
struct cy8c9540a *dev = devid; | |
u8 stat[NPORTS], pending = 0; | |
unsigned port = 0, gpio = 0, gpio_irq = 0; | |
int ret = 0; | |
ret = i2c_smbus_read_i2c_block_data(dev->client, REG_INTR_STAT_PORT0, | |
NPORTS, stat); /* W1C */ | |
if (ret < 0) { | |
memset(stat, 0, sizeof(stat)); | |
} | |
ret = IRQ_NONE; | |
for (port = 0; port < NPORTS; port ++) { | |
/* Databook doesn't specify if this is a post-mask status | |
register or not. Consider it 'raw' for safety. */ | |
mutex_lock(&dev->irq_lock); | |
pending = stat[port] & (~dev->irq_mask[port]); | |
mutex_unlock(&dev->irq_lock); | |
while (pending) { | |
ret = IRQ_HANDLED; | |
gpio = __ffs(pending); | |
pending &= ~BIT(gpio); | |
gpio_irq = GPIO_IRQBASE + cy8c9540a_port_offs[port] | |
+ gpio; | |
handle_nested_irq(gpio_irq); | |
} | |
} | |
return ret; | |
} | |
static int cy8c9540a_irq_setup(struct cy8c9540a *dev) | |
{ | |
struct i2c_client *client = dev->client; | |
u8 dummy[NPORTS]; | |
unsigned gpio = 0; | |
int ret = 0; | |
int i = 0; | |
mutex_init(&dev->irq_lock); | |
/* Clear interrupt state */ | |
ret = i2c_smbus_read_i2c_block_data(dev->client, REG_INTR_STAT_PORT0, | |
NPORTS, dummy); /* W1C */ | |
if (ret < 0) { | |
dev_err(&client->dev, "couldn't clear int status\n"); | |
goto err; | |
} | |
/* Initialise interrupt mask */ | |
memset(dev->irq_mask_cache, 0xff, sizeof(dev->irq_mask_cache)); | |
memset(dev->irq_mask, 0xff, sizeof(dev->irq_mask)); | |
for (i = 0; i < NPORTS; i++) { | |
ret = i2c_smbus_write_byte_data(client, REG_PORT_SELECT, i); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't select port %u\n", i); | |
goto err; | |
} | |
ret = i2c_smbus_write_byte_data(client, REG_INTR_MASK, dev->irq_mask[i]); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write int mask on port %u\n", i); | |
goto err; | |
} | |
} | |
/* Allocate external interrupt GPIO pin */ | |
ret = gpio_request(SOC_GPIO_INT_PIN, "cy8c9540a-int"); | |
if (ret) { | |
dev_err(&client->dev, "failed to request gpio%u\n", | |
SOC_GPIO_INT_PIN); | |
goto err; | |
} | |
/* Allocate IRQ descriptors for Cypress GPIOs and set handler */ | |
ret = irq_alloc_descs(GPIO_IRQBASE, GPIO_IRQBASE, NGPIO, 0); | |
if (ret < 0) { | |
dev_err(&client->dev, "failed to allocate IRQ numbers\n"); | |
goto err_free_gpio; | |
} | |
for (gpio = 0; gpio < NGPIO; gpio++) { | |
int irq = gpio + GPIO_IRQBASE; | |
irq_set_chip_data(irq, dev); | |
irq_set_chip_and_handler(irq, &cy8c9540a_irq_chip, | |
handle_edge_irq); | |
irq_set_nested_thread(irq, 1); | |
irq_set_noprobe(irq); | |
} | |
ret = request_threaded_irq(gpio_to_irq(SOC_GPIO_INT_PIN), | |
NULL, | |
cy8c9540a_irq_handler, | |
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, | |
dev_name(&client->dev), dev); | |
if (ret) { | |
dev_err(&client->dev, "failed to request irq %d\n", | |
client->irq); | |
goto err_free_irq_descs; | |
} | |
return 0; | |
err_free_irq_descs: | |
irq_free_descs(GPIO_IRQBASE, NGPIO); | |
err_free_gpio: | |
gpio_free(SOC_GPIO_INT_PIN); | |
err: | |
mutex_destroy(&dev->irq_lock); | |
return ret; | |
} | |
static void cy8c9540a_irq_teardown(struct cy8c9540a *dev) | |
{ | |
mutex_destroy(&dev->irq_lock); | |
irq_free_descs(GPIO_IRQBASE, NGPIO); | |
free_irq(gpio_to_irq(SOC_GPIO_INT_PIN), dev); | |
gpio_free(SOC_GPIO_INT_PIN); | |
} | |
static int cy8c9540a_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | |
int duty_ns, int period_ns) | |
{ | |
int ret = 0; | |
int period = 0, duty = 0; | |
struct cy8c9540a *dev = | |
container_of(chip, struct cy8c9540a, pwm_chip); | |
struct i2c_client *client = dev->client; | |
if (pwm->pwm > NPWM) { | |
return -EINVAL; | |
} | |
period = period_ns / PWM_TCLK_NS; | |
duty = duty_ns / PWM_TCLK_NS; | |
/* | |
* Check period's upper bound. Note the duty cycle is already sanity | |
* checked by the PWM framework. | |
*/ | |
if (period > PWM_MAX_PERIOD) { | |
dev_err(&client->dev, "period must be within [0-%d]ns\n", | |
PWM_MAX_PERIOD * PWM_TCLK_NS); | |
return -EINVAL; | |
} | |
mutex_lock(&dev->lock); | |
ret = i2c_smbus_write_byte_data(client, REG_PWM_SELECT, (u8)pwm->pwm); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write to REG_PWM_SELECT\n"); | |
goto end; | |
} | |
ret = i2c_smbus_write_byte_data(client, REG_PWM_PERIOD, (u8)period); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write to REG_PWM_PERIOD\n"); | |
goto end; | |
} | |
ret = i2c_smbus_write_byte_data(client, REG_PWM_PULSE_W, (u8)duty); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write to REG_PWM_PULSE_W\n"); | |
goto end; | |
} | |
end: | |
mutex_unlock(&dev->lock); | |
return ret; | |
} | |
static int cy8c9540a_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | |
{ | |
int ret = 0; | |
int gpio = 0; | |
int port = 0, pin = 0; | |
u8 out_reg = 0; | |
u8 val = 0; | |
struct cy8c9540a *dev = | |
container_of(chip, struct cy8c9540a, pwm_chip); | |
struct i2c_client *client = dev->client; | |
if (pwm->pwm > NPWM) { | |
return -EINVAL; | |
} | |
gpio = pwm2gpio_mapping[pwm->pwm]; | |
port = cypress_get_port(gpio); | |
pin = cypress_get_offs(gpio, port); | |
out_reg = REG_OUTPUT_PORT0 + port; | |
/* Set pin as output driving high */ | |
ret = cy8c9540a_gpio_direction(&dev->gpio_chip, gpio, 1, 1); | |
if (val < 0) { | |
dev_err(&client->dev, "can't set pwm%u as output\n", pwm->pwm); | |
return ret; | |
} | |
mutex_lock(&dev->lock); | |
/* Enable PWM */ | |
val = i2c_smbus_read_byte_data(client, REG_SELECT_PWM); | |
if (val < 0) { | |
dev_err(&client->dev, "can't read REG_SELECT_PWM\n"); | |
ret = val; | |
goto end; | |
} | |
val |= BIT((u8)pin); | |
ret = i2c_smbus_write_byte_data(client, REG_SELECT_PWM, val); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write to SELECT_PWM\n"); | |
goto end; | |
} | |
end: | |
mutex_unlock(&dev->lock); | |
return ret; | |
} | |
static void cy8c9540a_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | |
{ | |
int ret = 0; | |
int gpio = 0; | |
int port = 0, pin = 0; | |
u8 val = 0; | |
struct cy8c9540a *dev = | |
container_of(chip, struct cy8c9540a, pwm_chip); | |
struct i2c_client *client = dev->client; | |
if (pwm->pwm > NPWM) { | |
return; | |
} | |
gpio = pwm2gpio_mapping[pwm->pwm]; | |
if (PWM_UNUSED == gpio) { | |
dev_err(&client->dev, "pwm%d is unused\n", pwm->pwm); | |
return; | |
} | |
port = cypress_get_port(gpio); | |
pin = cypress_get_offs(gpio, port); | |
mutex_lock(&dev->lock); | |
/* Disable PWM */ | |
val = i2c_smbus_read_byte_data(client, REG_SELECT_PWM); | |
if (val < 0) { | |
dev_err(&client->dev, "can't read REG_SELECT_PWM\n"); | |
goto end; | |
} | |
val &= ~BIT((u8)pin); | |
ret = i2c_smbus_write_byte_data(client, REG_SELECT_PWM, val); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write to SELECT_PWM\n"); | |
} | |
end: | |
mutex_unlock(&dev->lock); | |
return; | |
} | |
/* | |
* Some PWMs are unavailable on Galileo. Prevent user from reserving them. | |
*/ | |
static int cy8c9540a_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) | |
{ | |
int gpio = 0; | |
struct cy8c9540a *dev = | |
container_of(chip, struct cy8c9540a, pwm_chip); | |
struct i2c_client *client = dev->client; | |
if (pwm->pwm > NPWM) { | |
return -EINVAL; | |
} | |
gpio = pwm2gpio_mapping[pwm->pwm]; | |
if (PWM_UNUSED == gpio) { | |
dev_err(&client->dev, "pwm%d unavailable\n", pwm->pwm); | |
return -EINVAL; | |
} | |
return 0; | |
} | |
static const struct pwm_ops cy8c9540a_pwm_ops = { | |
.request = cy8c9540a_pwm_request, | |
.config = cy8c9540a_pwm_config, | |
.enable = cy8c9540a_pwm_enable, | |
.disable = cy8c9540a_pwm_disable, | |
}; | |
/* | |
* cy8c9540a_setup | |
* | |
* Initialise the device with default setup. | |
* No need to roll back if this fails. | |
*/ | |
static int cy8c9540a_setup(struct cy8c9540a *dev) | |
{ | |
int ret = 0; | |
struct i2c_client *client = dev->client; | |
int i = 0; | |
const u8 eeprom_enable_seq[] = {0x43, 0x4D, 0x53, 0x2}; | |
/* Disable PWM, set all GPIOs as input. */ | |
for (i = 0; i < NPORTS; i ++) { | |
ret = i2c_smbus_write_byte_data(client, REG_PORT_SELECT, i); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't select port %u\n", i); | |
goto end; | |
} | |
ret = i2c_smbus_write_byte_data(client, REG_SELECT_PWM, 0x00); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write to SELECT_PWM\n"); | |
goto end; | |
} | |
ret = i2c_smbus_write_byte_data(client, REG_PIN_DIR, 0xff); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write to PIN_DIR\n"); | |
goto end; | |
} | |
} | |
/* Cache the output registers */ | |
ret = i2c_smbus_read_i2c_block_data(dev->client, REG_OUTPUT_PORT0, | |
sizeof(dev->outreg_cache), | |
dev->outreg_cache); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't cache output registers\n"); | |
goto end; | |
} | |
/* Set default PWM clock source. */ | |
for (i = 0; i < NPWM; i ++) { | |
ret = i2c_smbus_write_byte_data(client, REG_PWM_SELECT, i); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't select pwm %u\n", i); | |
goto end; | |
} | |
ret = i2c_smbus_write_byte_data(client, REG_PWM_CLK, PWM_CLK); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't write to REG_PWM_CLK\n"); | |
goto end; | |
} | |
} | |
/* Enable the EEPROM */ | |
ret = i2c_smbus_write_i2c_block_data(client, REG_ENABLE, | |
sizeof(eeprom_enable_seq), | |
eeprom_enable_seq); | |
if (ret < 0) { | |
dev_err(&client->dev, "can't enable EEPROM\n"); | |
goto end; | |
} | |
end: | |
return ret; | |
} | |
static int cy8c9540a_probe(struct i2c_client *client, | |
const struct i2c_device_id *id) | |
{ | |
struct cy8c9540a *dev; | |
struct gpio_chip *gc; | |
int ret = 0; | |
s32 dev_id = 0; | |
if (!i2c_check_functionality(client->adapter, | |
I2C_FUNC_SMBUS_I2C_BLOCK | | |
I2C_FUNC_SMBUS_BYTE_DATA)) { | |
dev_err(&client->dev, "SMBUS Byte/Block unsupported\n"); | |
return -EIO; | |
} | |
dev = kzalloc(sizeof(*dev), GFP_KERNEL); | |
if (dev == NULL) { | |
dev_err(&client->dev, "failed to alloc memory\n"); | |
return -ENOMEM; | |
} | |
dev->client = client; | |
gc = &dev->gpio_chip; | |
gc->direction_input = cy8c9540a_gpio_direction_input; | |
gc->direction_output = cy8c9540a_gpio_direction_output; | |
gc->get = cy8c9540a_gpio_get_value; | |
gc->set = cy8c9540a_gpio_set_value; | |
gc->set_drive = cy8c9540a_gpio_set_drive; | |
gc->can_sleep = 1; | |
gc->base = GPIO_BASE_ID; | |
gc->ngpio = NGPIO; | |
gc->label = client->name; | |
gc->owner = THIS_MODULE; | |
gc->to_irq = cy8c9540a_gpio_to_irq; | |
mutex_init(&dev->lock); | |
/* Whoami */ | |
dev_id = i2c_smbus_read_byte_data(client, REG_DEVID_STAT); | |
if (dev_id < 0) { | |
dev_err(&client->dev, "can't read device ID\n"); | |
ret = dev_id; | |
goto err; | |
} | |
dev_info(&client->dev, "dev_id=0x%x\n", dev_id & 0xff); | |
if (DEVID_FAMILY_CY8C9540A != (dev_id & DEVID_FAMILY_MASK)) { | |
dev_err(&client->dev, "unknown/unsupported dev_id 0x%x\n", | |
dev_id & 0xff); | |
ret = -ENODEV; | |
goto err; | |
} | |
ret = cy8c9540a_setup(dev); | |
if (ret) { | |
goto err; | |
} | |
ret = cy8c9540a_irq_setup(dev); | |
if (ret) { | |
goto err; | |
} | |
ret = gpiochip_add(&dev->gpio_chip); | |
if (ret) { | |
dev_err(&client->dev, "gpiochip_add failed %d\n", ret); | |
goto err_irq; | |
} | |
dev->pwm_chip.dev = &client->dev; | |
dev->pwm_chip.ops = &cy8c9540a_pwm_ops; | |
dev->pwm_chip.base = PWM_BASE_ID; | |
dev->pwm_chip.npwm = NPWM; | |
ret = pwmchip_add(&dev->pwm_chip); | |
if (ret) { | |
dev_err(&client->dev, "pwmchip_add failed %d\n", ret); | |
goto err_gpiochip; | |
} | |
i2c_set_clientdata(client, dev); | |
return 0; | |
err_gpiochip: | |
if(gpiochip_remove(&dev->gpio_chip)) | |
dev_warn(&client->dev, "gpiochip_remove failed\n"); | |
err_irq: | |
cy8c9540a_irq_teardown(dev); | |
err: | |
mutex_destroy(&dev->lock); | |
kfree(dev); | |
return ret; | |
} | |
static int cy8c9540a_remove(struct i2c_client *client) | |
{ | |
struct cy8c9540a *dev = i2c_get_clientdata(client); | |
int ret = 0; | |
int err = 0; | |
ret = pwmchip_remove(&dev->pwm_chip); | |
if (ret < 0) { | |
dev_err(&client->dev, "pwmchip_remove failed %d\n", ret); | |
err = ret; | |
} | |
ret = gpiochip_remove(&dev->gpio_chip); | |
if (ret) { | |
dev_err(&client->dev, "gpiochip_remove failed %d\n", ret); | |
err = ret; | |
} | |
cy8c9540a_irq_teardown(dev); | |
mutex_destroy(&dev->lock); | |
kfree(dev); | |
return err; | |
} | |
static const struct i2c_device_id cy8c9540a_id[] = { | |
{DRV_NAME, 0}, | |
{} | |
}; | |
MODULE_DEVICE_TABLE(i2c, cy8c9540a_id); | |
static struct i2c_driver cy8c9540a_driver = { | |
.driver = { | |
.name = DRV_NAME, | |
}, | |
.probe = cy8c9540a_probe, | |
.remove = cy8c9540a_remove, | |
.id_table = cy8c9540a_id, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment