blob: 02e5a4d0c73fe6ea20a0d20764347d4f13aa8112 [file] [log] [blame]
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#define GALOIS_PWMCH_NUM 4
#define ENABLE_REG_OFFSET 0x0
#define CONTROL_REG_OFFSET 0x4
#define DUTY_REG_OFFSET 0x8
#define TERMCNT_REG_OFFSET 0xC
/*
* ioctl command
*/
#define PWM_IOCTL_READ 0x1234
#define PWM_IOCTL_WRITE 0x4567
typedef struct galois_pwm_rw {
uint offset; /* the offset of register in PWM */
uint data; /* the value will be read/write from/to register */
} galois_pwm_rw_t;
static int pwmch_major;
static struct class *pwmch_class;
static unsigned long pwmch_base;
static long galois_pwmch_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
galois_pwm_rw_t pwm_rw;
unsigned int pwmch = iminor(file->f_path.dentry->d_inode);
switch(cmd) {
case PWM_IOCTL_READ:
if (copy_from_user(&pwm_rw, (void __user *)arg, sizeof(galois_pwm_rw_t))) {
printk("PWM: copy_from_user failed\n");
return -EFAULT;
}
if (((pwm_rw.offset & 0x3) != 0) || (pwm_rw.offset > 0xC)) {
printk("PWM: error register offset 0x%x.\n", pwm_rw.offset);
return -ENOMEM;
}
pwm_rw.data = __raw_readl(pwmch_base + (pwmch*0x10) + pwm_rw.offset);
if (copy_to_user((void __user *)arg, &pwm_rw, sizeof(galois_pwm_rw_t))) {
printk("PWM: copy_to_user failed\n");
return -EFAULT;
}
break;
case PWM_IOCTL_WRITE:
if (copy_from_user(&pwm_rw, (void __user *)arg, sizeof(galois_pwm_rw_t))) {
printk("PWM: copy_from_user failed\n");
return -EFAULT;
}
if (((pwm_rw.offset & 0x3) != 0) || (pwm_rw.offset > 0xC)) {
printk("PWM: error register offset 0x%x.\n", pwm_rw.offset);
return -ENOMEM;
}
__raw_writel(pwm_rw.data, pwmch_base + (pwmch*0x10) + pwm_rw.offset);
break;
default:
printk("PWM: unknown ioctl command.\n");
return -EINVAL;
}
return 0;
}
static struct file_operations galois_pwmch_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = galois_pwmch_ioctl,
};
static struct {
uint minor;
char *name;
} pwmchdev_list[] = {
{0, "pwmch0"},
{1, "pwmch1"},
{2, "pwmch2"},
{3, "pwmch3"},
};
#ifdef CONFIG_PM
struct saved_pwm_status_t {
u32 enable;
u32 control;
u32 duty;
u32 termcnt;
};
static struct saved_pwm_status_t saved_pwm_status[GALOIS_PWMCH_NUM];
static int pwmch_suspend(struct device *dev)
{
int i;
for (i = 0; i < GALOIS_PWMCH_NUM; i++) {
saved_pwm_status[i].control = __raw_readl(pwmch_base + (i * 0x10) + CONTROL_REG_OFFSET);
saved_pwm_status[i].duty = __raw_readl(pwmch_base + (i * 0x10) + DUTY_REG_OFFSET);
saved_pwm_status[i].termcnt = __raw_readl(pwmch_base + (i * 0x10) + TERMCNT_REG_OFFSET);
saved_pwm_status[i].enable = __raw_readl(pwmch_base + (i * 0x10) + ENABLE_REG_OFFSET);
}
return 0;
}
static int pwmch_resume(struct device *dev)
{
int i;
for (i = 0; i < GALOIS_PWMCH_NUM; i++) {
__raw_writel(saved_pwm_status[i].control, pwmch_base + (i * 0x10) + CONTROL_REG_OFFSET);
__raw_writel(saved_pwm_status[i].duty, pwmch_base + (i * 0x10) + DUTY_REG_OFFSET);
__raw_writel(saved_pwm_status[i].termcnt, pwmch_base + (i * 0x10) + TERMCNT_REG_OFFSET);
__raw_writel(saved_pwm_status[i].enable, pwmch_base + (i * 0x10) + ENABLE_REG_OFFSET);
}
return 0;
}
static struct dev_pm_ops pwmch_pm_ops = {
.suspend = pwmch_suspend,
.resume = pwmch_resume,
};
#endif
static int __devinit pwmch_probe(struct platform_device *pdev)
{
int i;
struct resource *r;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r)
return -EINVAL;
pwmch_base = r->start;
pwmch_major = register_chrdev(0, "pwmch", &galois_pwmch_fops);
if (pwmch_major < 0)
printk("unable to register chrdev for pwmch.\n");
pwmch_class = class_create(THIS_MODULE, "pwmch");
for (i = 0; i < ARRAY_SIZE(pwmchdev_list); i++)
device_create(pwmch_class, NULL,
MKDEV(pwmch_major, pwmchdev_list[i].minor), NULL, pwmchdev_list[i].name);
return 0;
}
static int __devexit pwmch_remove(struct platform_device *pdev)
{
int i;
for (i = 0; i < ARRAY_SIZE(pwmchdev_list); i++)
device_destroy(pwmch_class, MKDEV(pwmch_major, pwmchdev_list[i].minor));
class_destroy(pwmch_class);
unregister_chrdev(pwmch_major, "pwmch");
return 0;
}
static struct of_device_id pwmch_match[] = {
{ .compatible = "mrvl,berlin-pwm", },
{},
};
static struct platform_driver pwmch_driver = {
.probe = pwmch_probe,
.remove = __devexit_p(pwmch_remove),
.driver = {
.name = "berlin-pwm",
.owner = THIS_MODULE,
.of_match_table = pwmch_match,
#ifdef CONFIG_PM
.pm = &pwmch_pm_ops,
#endif
},
};
module_platform_driver(pwmch_driver);
MODULE_AUTHOR("Marvell-Galois");
MODULE_DESCRIPTION("Galois PWMCH Driver");
MODULE_LICENSE("GPL");