blob: c0d738fc1ef4679a482f027fc3fb7e1c8702aac6 [file] [log] [blame]
/*
* arch/arm/plat-ambarella/generic/pwm.c
*
* Author: Zhenwu Xue, <zwxue@ambarella.com>
*
* Copyright (C) 2004-2012, Ambarella, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/bootmem.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/dma-mapping.h>
#include <linux/pwm_backlight.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <mach/board.h>
#define PWM_DEFAULT_FREQUENCY 2200000
#define PWM_DEFAULT_DIVIDER 1
#define PWM_CMD_SIZE (5)
#define PWM_MAX_INSTANCES (4)
#define PWM_ARRAY_SIZE (PWM_MAX_INSTANCES * PWM_CMD_SIZE)
/*================================ PWM Device ================================*/
struct reg_bit_field {
unsigned int addr;
unsigned int msb;
unsigned int lsb;
};
#define SET_REG_BIT_FILED(reg, val) \
{ \
unsigned int _reg, _tmp, _val; \
\
_tmp = 0x1 << ((reg).msb - (reg).lsb + 1); \
_tmp = _tmp - 1; \
_val = ((val) & _tmp) << (reg).lsb; \
_tmp = _tmp << (reg).lsb; \
_reg = amba_readl((reg).addr); \
_reg &= (~_tmp); \
_reg |= _val; \
amba_writel((reg).addr, _reg); \
}
typedef unsigned int (*get_pwm_clock_t)(void);
typedef void (*set_pwm_clock_t)(unsigned int);
struct pwm_device {
unsigned int pwm_id;
unsigned int gpio_id;
unsigned int active_level;
get_pwm_clock_t get_clock;
struct reg_bit_field enable;
struct reg_bit_field divider;
struct reg_bit_field high;
struct reg_bit_field low;
unsigned int optional_clk_src;
struct reg_bit_field clock_source;
unsigned int optional_inversion;
struct reg_bit_field inversion_enable;
unsigned int use_count;
const char *label;
struct list_head node;
};
static DEFINE_MUTEX(pwm_lock);
static LIST_HEAD(pwm_list);
static void add_pwm_device(struct pwm_device *pwm)
{
mutex_lock(&pwm_lock);
list_add_tail(&pwm->node, &pwm_list);
mutex_unlock(&pwm_lock);
}
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
{
int retval = 0;
unsigned int clock, on, off;
if (!pwm->get_clock) {
retval = -EINVAL;
printk("%s: Can not get pwm clock!\n", __func__);
goto pwm_config_exit;
}
if (pwm->optional_clk_src == 1) {
SET_REG_BIT_FILED(pwm->clock_source, 1);
}
if (pwm->optional_inversion == 1) {
SET_REG_BIT_FILED(pwm->inversion_enable, 0);
}
clock = (pwm->get_clock() + 50000) / 100000;
SET_REG_BIT_FILED(pwm->divider, PWM_DEFAULT_DIVIDER - 1);
on = (clock * duty_ns + 5000) / 10000;
off = (clock * (period_ns - duty_ns) + 5000) / 10000;
if (on == 0)
on = 1;
if (off == 0)
off = 1;
if (pwm->active_level) {
SET_REG_BIT_FILED(pwm->high, on - 1);
SET_REG_BIT_FILED(pwm->low, off - 1);
} else {
SET_REG_BIT_FILED(pwm->high, off - 1);
SET_REG_BIT_FILED(pwm->low, on - 1);
}
pwm_config_exit:
return retval;
}
EXPORT_SYMBOL(pwm_config);
int pwm_enable(struct pwm_device *pwm)
{
SET_REG_BIT_FILED(pwm->enable, 1);
ambarella_gpio_config(pwm->gpio_id, GPIO_FUNC_HW);
return 0;
}
EXPORT_SYMBOL(pwm_enable);
void pwm_disable(struct pwm_device *pwm)
{
SET_REG_BIT_FILED(pwm->enable, 0);
ambarella_gpio_config(pwm->gpio_id, GPIO_FUNC_SW_OUTPUT);
if (pwm->active_level) {
ambarella_gpio_set(pwm->gpio_id, GPIO_LOW);
} else {
ambarella_gpio_set(pwm->gpio_id, GPIO_HIGH);
}
}
EXPORT_SYMBOL(pwm_disable);
struct pwm_device *pwm_request(int pwm_id, const char *label)
{
struct pwm_device *pwm;
int found = 0;
mutex_lock(&pwm_lock);
list_for_each_entry(pwm, &pwm_list, node) {
if (pwm->pwm_id == pwm_id) {
found = 1;
break;
}
}
if (found) {
if (pwm->use_count == 0) {
pwm->use_count++;
pwm->label = label;
} else
pwm = ERR_PTR(-EBUSY);
} else
pwm = ERR_PTR(-ENOENT);
mutex_unlock(&pwm_lock);
return pwm;
}
EXPORT_SYMBOL(pwm_request);
void pwm_free(struct pwm_device *pwm)
{
mutex_lock(&pwm_lock);
if (pwm->use_count) {
pwm->use_count--;
pwm->label = NULL;
} else
pr_warning("PWM device already freed\n");
mutex_unlock(&pwm_lock);
}
EXPORT_SYMBOL(pwm_free);
static struct pwm_device ambarella_pwm0 = {
.pwm_id = 0,
.gpio_id = GPIO(16),
.active_level = 1,
.get_clock = get_pwm_freq_hz,
.enable = {
.addr = PWM_ENABLE_REG,
.msb = 0,
.lsb = 0,
},
.divider = {
.addr = PWM_MODE_REG,
.msb = 10,
.lsb = 1,
},
.high = {
.addr = PWM_CONTROL_REG,
.msb = 31,
.lsb = 16,
},
.low = {
.addr = PWM_CONTROL_REG,
.msb = 15,
.lsb = 0,
},
.optional_clk_src = 0,
.optional_inversion = 0,
.use_count = 0,
.label = NULL,
};
static struct pwm_device ambarella_pwm1 = {
.pwm_id = 1,
.gpio_id = GPIO(45),
.active_level = 1,
.get_clock = get_pwm_freq_hz,
.enable = {
.addr = PWM_B0_ENABLE_REG,
.msb = 0,
.lsb = 0,
},
.divider = {
.addr = PWM_B0_ENABLE_REG,
.msb = 10,
.lsb = 1,
},
.high = {
.addr = PWM_B0_DATA1_REG,
.msb = 19,
.lsb = 10,
},
.low = {
.addr = PWM_B0_DATA1_REG,
.msb = 9,
.lsb = 0,
},
.optional_clk_src = 1,
.clock_source = {
.addr = PWM_B0_ENABLE_REG,
.msb = 31,
.lsb = 31,
},
.optional_inversion = 0,
.use_count = 0,
.label = NULL,
};
static struct pwm_device ambarella_pwm2 = {
.pwm_id = 2,
.gpio_id = GPIO(46),
.active_level = 1,
.get_clock = get_pwm_freq_hz,
.enable = {
.addr = PWM_B1_ENABLE_REG,
.msb = 0,
.lsb = 0,
},
.divider = {
.addr = PWM_B1_ENABLE_REG,
.msb = 10,
.lsb = 1,
},
.high = {
.addr = PWM_B1_DATA1_REG,
.msb = 19,
.lsb = 10,
},
.low = {
.addr = PWM_B1_DATA1_REG,
.msb = 9,
.lsb = 0,
},
.optional_clk_src = 0,
.optional_inversion = 1,
.inversion_enable = {
.addr = PWM_B1_ENABLE_REG,
.msb = 31,
.lsb = 31,
},
.use_count = 0,
.label = NULL,
};
static struct pwm_device ambarella_pwm3 = {
.pwm_id = 3,
.gpio_id = GPIO(50),
.active_level = 1,
.get_clock = get_pwm_freq_hz,
.enable = {
.addr = PWM_C0_ENABLE_REG,
.msb = 0,
.lsb = 0,
},
.divider = {
.addr = PWM_C0_ENABLE_REG,
.msb = 10,
.lsb = 1,
},
.high = {
.addr = PWM_C0_DATA1_REG,
.msb = 19,
.lsb = 10,
},
.low = {
.addr = PWM_C0_DATA1_REG,
.msb = 9,
.lsb = 0,
},
.optional_clk_src = 1,
.clock_source = {
.addr = PWM_C0_ENABLE_REG,
.msb = 31,
.lsb = 31,
},
.optional_inversion = 0,
.use_count = 0,
.label = NULL,
};
static struct pwm_device ambarella_pwm4 = {
.pwm_id = 4,
.gpio_id = GPIO(51),
.active_level = 1,
.get_clock = get_pwm_freq_hz,
.enable = {
.addr = PWM_C1_ENABLE_REG,
.msb = 0,
.lsb = 0,
},
.divider = {
.addr = PWM_C1_ENABLE_REG,
.msb = 10,
.lsb = 1,
},
.high = {
.addr = PWM_C1_DATA1_REG,
.msb = 19,
.lsb = 10,
},
.low = {
.addr = PWM_C1_DATA1_REG,
.msb = 9,
.lsb = 0,
},
.optional_clk_src = 0,
.optional_inversion = 1,
.inversion_enable = {
.addr = PWM_C1_ENABLE_REG,
.msb = 31,
.lsb = 31,
},
.use_count = 0,
.label = NULL,
};
/*============================= PWM Backlight Device =========================*/
static int pwm_backlight_init(struct device *dev)
{
int retval = 0;
struct platform_device *pdev;
struct platform_pwm_backlight_data *data;
pdev = container_of(dev, struct platform_device, dev);
data = pdev->dev.platform_data;
switch (pdev->id) {
case 0:
data->max_brightness =
ambarella_board_generic.pwm0_config.max_duty;
data->dft_brightness =
ambarella_board_generic.pwm0_config.default_duty;
data->pwm_period_ns =
ambarella_board_generic.pwm0_config.period_ns;
ambarella_pwm0.active_level =
ambarella_board_generic.pwm0_config.active_level;
break;
case 1:
data->max_brightness =
ambarella_board_generic.pwm1_config.max_duty;
data->dft_brightness =
ambarella_board_generic.pwm1_config.default_duty;
data->pwm_period_ns =
ambarella_board_generic.pwm1_config.period_ns;
ambarella_pwm1.active_level =
ambarella_board_generic.pwm1_config.active_level;
break;
case 2:
data->max_brightness =
ambarella_board_generic.pwm2_config.max_duty;
data->dft_brightness =
ambarella_board_generic.pwm2_config.default_duty;
data->pwm_period_ns =
ambarella_board_generic.pwm2_config.period_ns;
ambarella_pwm2.active_level =
ambarella_board_generic.pwm2_config.active_level;
break;
case 3:
data->max_brightness =
ambarella_board_generic.pwm3_config.max_duty;
data->dft_brightness =
ambarella_board_generic.pwm3_config.default_duty;
data->pwm_period_ns =
ambarella_board_generic.pwm3_config.period_ns;
ambarella_pwm3.active_level =
ambarella_board_generic.pwm3_config.active_level;
break;
case 4:
data->max_brightness =
ambarella_board_generic.pwm4_config.max_duty;
data->dft_brightness =
ambarella_board_generic.pwm4_config.default_duty;
data->pwm_period_ns =
ambarella_board_generic.pwm4_config.period_ns;
ambarella_pwm4.active_level =
ambarella_board_generic.pwm4_config.active_level;
break;
default:
retval = -EINVAL;
break;
}
return retval;
}
static struct platform_pwm_backlight_data amb_pwm0_pdata = {
.pwm_id = 0,
.max_brightness = 255,
.dft_brightness = 255,
.pwm_period_ns = 40000,
.init = pwm_backlight_init,
.notify = NULL,
.exit = NULL,
};
struct platform_device ambarella_pwm_platform_device0 = {
.name = "pwm-backlight",
.id = 0,
.resource = NULL,
.num_resources = 0,
.dev = {
.platform_data = &amb_pwm0_pdata,
.dma_mask = &ambarella_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
static struct platform_pwm_backlight_data amb_pwm1_pdata = {
.pwm_id = 1,
.max_brightness = 100,
.dft_brightness = 100,
.pwm_period_ns = 10000,
.init = pwm_backlight_init,
.notify = NULL,
.exit = NULL,
};
struct platform_device ambarella_pwm_platform_device1 = {
.name = "pwm-backlight",
.id = 1,
.resource = NULL,
.num_resources = 0,
.dev = {
.platform_data = &amb_pwm1_pdata,
.dma_mask = &ambarella_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
static struct platform_pwm_backlight_data amb_pwm2_pdata = {
.pwm_id = 2,
.max_brightness = 100,
.dft_brightness = 100,
.pwm_period_ns = 10000,
.init = pwm_backlight_init,
.notify = NULL,
.exit = NULL,
};
struct platform_device ambarella_pwm_platform_device2 = {
.name = "pwm-backlight",
.id = 2,
.resource = NULL,
.num_resources = 0,
.dev = {
.platform_data = &amb_pwm2_pdata,
.dma_mask = &ambarella_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
static struct platform_pwm_backlight_data amb_pwm3_pdata = {
.pwm_id = 3,
.max_brightness = 100,
.dft_brightness = 100,
.pwm_period_ns = 10000,
.init = pwm_backlight_init,
.notify = NULL,
.exit = NULL,
};
struct platform_device ambarella_pwm_platform_device3 = {
.name = "pwm-backlight",
.id = 3,
.resource = NULL,
.num_resources = 0,
.dev = {
.platform_data = &amb_pwm3_pdata,
.dma_mask = &ambarella_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
static struct platform_pwm_backlight_data amb_pwm4_pdata = {
.pwm_id = 4,
.max_brightness = 100,
.dft_brightness = 100,
.pwm_period_ns = 10000,
.init = pwm_backlight_init,
.notify = NULL,
.exit = NULL,
};
struct platform_device ambarella_pwm_platform_device4 = {
.name = "pwm-backlight",
.id = 4,
.resource = NULL,
.num_resources = 0,
.dev = {
.platform_data = &amb_pwm4_pdata,
.dma_mask = &ambarella_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
#ifdef CONFIG_AMBARELLA_PWM_PROC
static u32 pwm_array[PWM_ARRAY_SIZE];
static const char pwm_proc_name[] = "pwm";
static struct proc_dir_entry *pwm_file;
static int ambarella_pwm_proc_write(struct file *file,
const char __user *buffer, unsigned long count, void *data)
{
int retval = 0;
int cmd_cnt;
int i;
u32 pwm_ch;
u32 enable;
u32 xon;
u32 xoff;
u32 div;
u32 data_reg;
if (count > sizeof(pwm_array)) {
pr_err("%s: count %d out of size!\n", __func__, (u32)count);
retval = -ENOSPC;
goto ambarella_pwm_proc_write_exit;
}
if (copy_from_user(pwm_array, buffer, count)) {
pr_err("%s: copy_from_user fail!\n", __func__);
retval = -EFAULT;
goto ambarella_pwm_proc_write_exit;
}
cmd_cnt = count / (PWM_CMD_SIZE * sizeof(pwm_array[0]));
for (i = 0; i < cmd_cnt; i++) {
pwm_ch = pwm_array[i * PWM_CMD_SIZE];
enable = pwm_array[i * PWM_CMD_SIZE + 1];
xon = pwm_array[i * PWM_CMD_SIZE + 2];
xoff = pwm_array[i * PWM_CMD_SIZE + 3];
div = pwm_array[i * PWM_CMD_SIZE + 4];
if (pwm_ch == 0)
data_reg = ((xon - 1) << 16) + (xoff - 1);
else
data_reg = ((xon - 1) << 10) + (xoff - 1);
switch (pwm_ch) {
case 0:
amba_writel(PWM_CONTROL_REG, data_reg);
amba_writel(PWM_ENABLE_REG, enable);
amba_writel(PWM_MODE_REG, div << 1);
break;
case 1:
amba_writel(PWM_B0_DATA1_REG, data_reg);
amba_writel(PWM_B0_ENABLE_REG, enable + (div << 1));
break;
case 2:
amba_writel(PWM_B1_DATA1_REG, data_reg);
amba_writel(PWM_B1_ENABLE_REG, enable + (div << 1));
break;
case 3:
amba_writel(PWM_C0_DATA1_REG, data_reg);
amba_writel(PWM_C0_ENABLE_REG, enable + (div << 1));
break;
case 4:
amba_writel(PWM_C1_DATA1_REG, data_reg);
amba_writel(PWM_C1_ENABLE_REG, enable + (div << 1));
break;
default:
pr_warning("%s: invalid pwm channel id %d!\n",
__func__, pwm_ch);
break;
}
}
retval = count;
ambarella_pwm_proc_write_exit:
return retval;
}
#endif
int __init ambarella_init_pwm(void)
{
int retval = 0;
#ifdef CONFIG_AMBARELLA_PWM_PROC
pwm_file = create_proc_entry(pwm_proc_name, S_IRUGO | S_IWUSR,
get_ambarella_proc_dir());
if (pwm_file == NULL) {
retval = -ENOMEM;
pr_err("%s: %s fail!\n", __func__, pwm_proc_name);
} else {
pwm_file->read_proc = NULL;
pwm_file->write_proc = ambarella_pwm_proc_write;
}
#endif
rct_set_pwm_freq_hz(PWM_DEFAULT_FREQUENCY);
add_pwm_device(&ambarella_pwm0);
if (AMBARELLA_BOARD_TYPE(system_rev) != AMBARELLA_BOARD_TYPE_BUB)
add_pwm_device(&ambarella_pwm1);
add_pwm_device(&ambarella_pwm2);
add_pwm_device(&ambarella_pwm3);
add_pwm_device(&ambarella_pwm4);
return retval;
}