/* Copyright (c) 2018 Dell Inc.
 * dell_ich.c - ICH driver for Dell Avoton/Rangeley switches
 *
 * 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.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/pci.h>
#include <linux/gpio.h>
#include <linux/mfd/lpc_ich.h>
#include <linux/acpi.h>

#define DRV_NAME "dell_ich"

#define AVOTON_PCU_DEVICE_ID 0x1f38

// GPIO registers
// GPIO Core Control/Access Registers in I/O Space
#define GPIO_SC_USE_SEL		0x0
#define GPIO_SC_IO_SEL		0x4
#define GPIO_SC_GP_LVL		0x8
#define GPIO_SC_TPE		0xC
#define GPIO_SC_TNE		0x10
#define GPIO_SC_TS		0x14

// GPIO SUS Control/Access Registers in I/O Space
#define GPIO_SUS_USE_SEL	0x80
#define GPIO_SUS_IO_SEL		0x84
#define GPIO_SUS_GP_LVL		0x88
#define GPIO_SUS_TPE		0x8C
#define GPIO_SUS_TNE		0x90
#define GPIO_SUS_TS		0x94
#define GPIO_SUS_WAKE_EN	0x98

static const unsigned char avoton_gpio_regs[] = {
  GPIO_SC_USE_SEL,
  GPIO_SC_IO_SEL,
  GPIO_SC_GP_LVL,
  GPIO_SC_TPE,
  GPIO_SC_TNE,
  GPIO_SC_TS,
  GPIO_SUS_USE_SEL,
  GPIO_SUS_IO_SEL,
  GPIO_SUS_GP_LVL,
  GPIO_SUS_TPE,
  GPIO_SUS_TNE,
  GPIO_SUS_TS,
  GPIO_SUS_WAKE_EN
};

#define GPIO_REG_LEN 0x4

#define GPIOS0_EN  (1 << 0)
#define GPIOS1_EN  (1 << 1)
#define GPIOS2_EN  (1 << 2)
#define GPIOS3_EN  (1 << 3)
#define GPIOS4_EN  (1 << 4)
#define GPIOS5_EN  (1 << 5)
#define GPIOS6_EN  (1 << 6)
#define GPIOS7_EN  (1 << 7)

#define GPIOSUS0_EN  (1 << 0)
#define GPIOSUS1_EN  (1 << 1)
#define GPIOSUS2_EN  (1 << 2)
#define GPIOSUS3_EN  (1 << 3)
// GPIOSUS4_EN : unused
// GPIOSUS5_EN : unused
#define GPIOSUS6_EN  (1 << 6)
#define GPIOSUS7_EN  (1 << 7)


// GPE0a_EN - General Purpose Event 0 Enables
#define GPIO_GPE0a_EN_SUS0	(1 << 16)
#define GPIO_GPE0a_EN_SUS1	(1 << 17)
#define GPIO_GPE0a_EN_SUS2	(1 << 18)
#define GPIO_GPE0a_EN_SUS3	(1 << 19)
// GPIO_GPE0a_EN_SUS4 : unused
// GPIO_GPE0a_EN_SUS5 : unused
#define GPIO_GPE0a_EN_SUS6	(1 << 22)
#define GPIO_GPE0a_EN_SUS7	(1 << 23)

#define GPIO_GPE0a_EN_CORE0	(1 << 24)
#define GPIO_GPE0a_EN_CORE1	(1 << 25)
#define GPIO_GPE0a_EN_CORE2	(1 << 26)
#define GPIO_GPE0a_EN_CORE3	(1 << 27)
#define GPIO_GPE0a_EN_CORE4	(1 << 28)
#define GPIO_GPE0a_EN_CORE5	(1 << 29)
#define GPIO_GPE0a_EN_CORE6	(1 << 30)
#define GPIO_GPE0a_EN_CORE7	(1 << 31)

// GPE0a_STS - General Purpose Event 0 Status
// We're interested in only SUS6 for now
#define GPIO_GPE0a_STS_SUS6	(1 << 22)

// GPIO_ROUT - GPIO_ROUT Register
#define GPIO_ROUT_OFFSET_SUS0	0
#define GPIO_ROUT_OFFSET_SUS1	2
#define GPIO_ROUT_OFFSET_SUS2	4
#define GPIO_ROUT_OFFSET_SUS3	6	
// GPIO_ROUT_OFFSET_SUS4 : unused
// GPIO_ROUT_OFFSET_SUS5 : unused
#define GPIO_ROUT_OFFSET_SUS6	12
#define GPIO_ROUT_OFFSET_SUS7	14

#define GPIO_ROUT_OFFSET_CORE0   16
#define GPIO_ROUT_OFFSET_CORE1   18
#define GPIO_ROUT_OFFSET_CORE2   20
#define GPIO_ROUT_OFFSET_CORE3   22
#define GPIO_ROUT_OFFSET_CORE4   24
#define GPIO_ROUT_OFFSET_CORE5   26
#define GPIO_ROUT_OFFSET_CORE6   28 
#define GPIO_ROUT_OFFSET_CORE7   30

enum GPIO_ROUT {
	GPIO_NO_EFFECT = 0,
	GPIO_SMI,
	GPIO_SCI,
	GPIO_RESERVED
};

/*
 * GPIO resources
 * defined as in drivers/gpio/gpio-ich.c
 */
#define ICH_RES_GPIO    0
#define ICH_RES_GPE0    1
static struct resource gpio_ich_res[] = {
        /* GPIO */
        {
                .flags = IORESOURCE_IO,
        },
        /* ACPI - GPE0 */
        {
                .flags = IORESOURCE_IO,
        },
};

// ACPI registers
#define ACPI_GPE0a_STS 0x20
#define ACPI_GPE0a_EN  0x28

// PMC registers
#define PMC_GPIO_ROUT 0x58
#define PMC_REG_LEN   0x4

// lpc_ich_priv is derived from drivers/mfd/lpc_ich.c
struct lpc_ich_priv {
	int chipset;

	int abase;		/* ACPI base */
	int actrl_pbase;	/* ACPI control or PMC base */
	int gbase;		/* GPIO base */
	int gctrl;		/* GPIO control */

	int abase_save;		/* Cached ACPI base value */
	int actrl_pbase_save;		/* Cached ACPI control or PMC base value */
	int gctrl_save;		/* Cached GPIO control value */
};

#define ICH_RES_MEM_GCS_PMC	0

#define IO_REG_WRITE(val, reg, base_res)	outl(val, (reg) + (base_res)->start)
#define IO_REG_READ(reg, base_res)		inl((reg) + (base_res)->start)

struct resource pmc_res = {.flags = IORESOURCE_MEM,};

static struct kobject *dell_kobj;
static unsigned short force_id;
module_param(force_id, ushort, 0);

#define SMF_REG_ADDR            0x200
#define SIO_REG_DEVID           0x1
#define SIO_Z9100_ID            0x1
#define SIO_S6100_ID            0x2
#define SIO_S4200_ID            0x3
#define SIO_S5148_ID            0x4

enum kinds {
        z9100smf, s6100smf
};

struct dell_ich_data {
    enum kinds kind;
    struct resource *gpio_base, *acpi_base, *pmc_base;
    int gpio_alloc,pmc_alloc;
    unsigned int int_gpio_sus6_count;
};

// GPIO sysfs attributes

static ssize_t get_sc_use_sel(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SC_USE_SEL,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sc_use_sel(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SUS_USE_SEL,ich_data->gpio_base);

    return count;
}

static ssize_t get_sc_io_sel(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SC_IO_SEL,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sc_io_sel(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SC_IO_SEL,ich_data->gpio_base);

    return count;
}

static ssize_t get_sc_gp_lvl(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SC_GP_LVL,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sc_gp_lvl(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SC_GP_LVL,ich_data->gpio_base);

    return count;
}

static ssize_t get_sc_gp_tpe(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SC_TPE,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sc_gp_tpe(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SC_TPE,ich_data->gpio_base);

    return count;
}

static ssize_t get_sc_gp_tne(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SC_TNE,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sc_gp_tne(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SC_TNE,ich_data->gpio_base);

    return count;
}

static ssize_t get_sc_gp_ts(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SC_TS,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sc_gp_ts(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SC_TS,ich_data->gpio_base);

    return count;
}

static ssize_t get_sus_use_sel(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SUS_USE_SEL,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sus_use_sel(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SUS_USE_SEL,ich_data->gpio_base);

    return count;
}

static ssize_t get_sus_io_sel(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SUS_IO_SEL,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sus_io_sel(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SUS_IO_SEL,ich_data->gpio_base);

    return count;
}

static ssize_t get_sus_gp_lvl(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SUS_GP_LVL,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sus_gp_lvl(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SUS_GP_LVL,ich_data->gpio_base);

    return count;
}

static ssize_t get_sus_gp_tpe(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SUS_TPE,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sus_gp_tpe(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SUS_TPE,ich_data->gpio_base);

    return count;
}

static ssize_t get_sus_gp_tne(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SUS_TNE,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sus_gp_tne(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SUS_TNE,ich_data->gpio_base);

    return count;
}

static ssize_t get_sus_gp_ts(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(GPIO_SUS_TS,ich_data->gpio_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sus_gp_ts(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,GPIO_SUS_TS,ich_data->gpio_base);

    return count;
}

static DEVICE_ATTR(sc_use_sel,	S_IRUGO | S_IWUSR, get_sc_use_sel, set_sc_use_sel);
static DEVICE_ATTR(sc_io_sel,	S_IRUGO | S_IWUSR, get_sc_io_sel,  set_sc_io_sel);
static DEVICE_ATTR(sc_gp_lvl,	S_IRUGO | S_IWUSR, get_sc_gp_lvl,  set_sc_gp_lvl);
static DEVICE_ATTR(sc_gp_tpe,   S_IRUGO | S_IWUSR, get_sc_gp_tpe,  set_sc_gp_tpe);
static DEVICE_ATTR(sc_gp_tne,   S_IRUGO | S_IWUSR, get_sc_gp_tne,  set_sc_gp_tne);
static DEVICE_ATTR(sc_gp_ts,    S_IRUGO | S_IWUSR, get_sc_gp_ts,   set_sc_gp_ts);
static DEVICE_ATTR(sus_use_sel,	S_IRUGO | S_IWUSR, get_sus_use_sel,set_sus_use_sel);
static DEVICE_ATTR(sus_io_sel,	S_IRUGO | S_IWUSR, get_sus_io_sel, set_sus_io_sel);
static DEVICE_ATTR(sus_gp_lvl,	S_IRUGO | S_IWUSR, get_sus_gp_lvl, set_sus_gp_lvl);
static DEVICE_ATTR(sus_gp_tpe,	S_IRUGO | S_IWUSR, get_sus_gp_tpe, set_sus_gp_tpe);
static DEVICE_ATTR(sus_gp_tne,	S_IRUGO | S_IWUSR, get_sus_gp_tne, set_sus_gp_tne);
static DEVICE_ATTR(sus_gp_ts,	S_IRUGO | S_IWUSR, get_sus_gp_ts,  set_sus_gp_ts);

static struct attribute *gpio_attrs[] = {
    &dev_attr_sc_use_sel.attr,
    &dev_attr_sc_io_sel.attr,
    &dev_attr_sc_gp_lvl.attr,
    &dev_attr_sc_gp_tpe.attr,
    &dev_attr_sc_gp_tne.attr,
    &dev_attr_sc_gp_ts.attr,
    &dev_attr_sus_use_sel.attr,
    &dev_attr_sus_io_sel.attr,
    &dev_attr_sus_gp_lvl.attr,
    &dev_attr_sus_gp_tpe.attr,
    &dev_attr_sus_gp_tne.attr,
    &dev_attr_sus_gp_ts.attr,
    NULL,
};

static struct attribute_group gpio_attrs_group= {
    .attrs = gpio_attrs,
};

// ACPI sysfs attributes

static ssize_t get_gpe0a_sts(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(ACPI_GPE0a_STS,ich_data->acpi_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_gpe0a_sts(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,ACPI_GPE0a_STS,ich_data->acpi_base);

    return count;
}

static ssize_t get_gpe0a_en(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = IO_REG_READ(ACPI_GPE0a_EN,ich_data->acpi_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_gpe0a_en(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    IO_REG_WRITE(devdata,ACPI_GPE0a_EN,ich_data->acpi_base);

    return count;
}

static DEVICE_ATTR(gpe0a_sts,  S_IRUGO | S_IWUSR, get_gpe0a_sts, set_gpe0a_sts);
static DEVICE_ATTR(gpe0a_en,   S_IRUGO | S_IWUSR, get_gpe0a_en,  set_gpe0a_en);

static struct attribute *acpi_attrs[] = {
    &dev_attr_gpe0a_sts.attr,
    &dev_attr_gpe0a_en.attr,
    NULL,
};

static struct attribute_group acpi_attrs_group= {
    .attrs = acpi_attrs,
};

static ssize_t get_gpio_rout(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = readl(ich_data->pmc_base);
    return sprintf(buf,"0x%08x\n",devdata);
}

// PMC sysfs attributes

static ssize_t set_gpio_rout(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    writel(devdata,ich_data->pmc_base);

    return count;
}

static DEVICE_ATTR(gpio_rout,  S_IRUGO | S_IWUSR, get_gpio_rout, set_gpio_rout);

static struct attribute *pmc_attrs[] = {
    &dev_attr_gpio_rout.attr,
    NULL,
};

static struct attribute_group pmc_attrs_group= {
    .attrs = pmc_attrs,
};

// SCI interrupt sysfs attributes

static ssize_t get_sci_int_gpio_sus6(struct device *dev, struct device_attribute *devattr, char *buf)
{
    u32 devdata=0;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!ich_data) return sprintf(buf, "read error");

    devdata = ich_data->int_gpio_sus6_count;
    return sprintf(buf,"0x%08x\n",devdata);
}

static ssize_t set_sci_int_gpio_sus6(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count)
{
    unsigned long devdata;
    int err;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    err = kstrtoul(buf, 16, &devdata);
    if (err)
        return err;

    ich_data->int_gpio_sus6_count = devdata;

    return count;
}

static DEVICE_ATTR(sci_int_gpio_sus6,  S_IRUGO | S_IWUSR, get_sci_int_gpio_sus6, set_sci_int_gpio_sus6);

static struct attribute *sci_attrs[] = {
    &dev_attr_sci_int_gpio_sus6.attr,
    NULL,
};

static struct attribute_group sci_attrs_group= {
    .attrs = sci_attrs,
};

static u32 dell_ich_sci_handler(void *context)
{
    unsigned int data;
    struct device *dev = (struct device *)context;
    struct dell_ich_data *ich_data = dev_get_platdata(dev);

    if(!dev) return ACPI_INTERRUPT_NOT_HANDLED;

    ich_data = dev_get_platdata(dev);
    if(!ich_data) return ACPI_INTERRUPT_NOT_HANDLED;

    data=IO_REG_READ(ACPI_GPE0a_STS,ich_data->acpi_base);
    if(data & GPIO_GPE0a_STS_SUS6) {
	// Clear the SUS6 status
        IO_REG_WRITE(data,ACPI_GPE0a_STS,ich_data->acpi_base);
        ich_data->int_gpio_sus6_count++;
	// and notify the user space clients
	sysfs_notify(&dev->kobj, NULL, "sci_int_gpio_sus6");
	return ACPI_INTERRUPT_HANDLED;
    }

    return ACPI_INTERRUPT_NOT_HANDLED;
}

/*
 * Setup GPIO SUS6 to generate an SCI interrupt for optics detection
 * This can be alternatively be setup using sysfs
 */
int setup_gpio_sus6_sci_interrupt(struct device *dev)
{
  int ret=0;
  unsigned int data;
  struct resource *acpi_base, *pmc_base, *gpio_base;
  struct dell_ich_data *ich_data = dev_get_platdata(dev);

    gpio_base = ich_data->gpio_base;
    acpi_base = ich_data->acpi_base;
    pmc_base  = ich_data->pmc_base;

    // Enable GPIOSUS6_EN
    data = IO_REG_READ(GPIO_SUS_USE_SEL,gpio_base);
    data |= GPIOSUS6_EN;
    IO_REG_WRITE(data,GPIO_SUS_USE_SEL,gpio_base);

    // GPIOSUS6_EN is input
    data = IO_REG_READ(GPIO_SUS_IO_SEL,gpio_base);
    data |= GPIOSUS6_EN;
    IO_REG_WRITE(data,GPIO_SUS_IO_SEL,gpio_base);

    // Trigger on positive edge for GPIOSUS6_EN
    data = IO_REG_READ(GPIO_SUS_TPE,gpio_base);
    data |= GPIOSUS6_EN;
    IO_REG_WRITE(data,GPIO_SUS_TPE,gpio_base);

    // Enable GPE for SUS6 to generate an SCI
    data=IO_REG_READ(ACPI_GPE0a_EN,acpi_base);
    data|=GPIO_GPE0a_EN_SUS6;
    IO_REG_WRITE(data,ACPI_GPE0a_EN,acpi_base);

    data=readl(pmc_base);
    data=(data & ~(0x3 << GPIO_ROUT_OFFSET_SUS6)) | (GPIO_SCI << GPIO_ROUT_OFFSET_SUS6);
    writel(data,pmc_base);

    ret = acpi_install_sci_handler(dell_ich_sci_handler,(void*)dev);
    if(ret) {
        pr_info("dell_ich acpi_install_sci_handler failed %d\n",ret);
        return ret;
    }

    return ret;
}

static int dell_ich_probe(struct platform_device *pdev)
{
  struct device *dev = &pdev->dev;
  struct dell_ich_data *ich_data = dev_get_platdata(dev);
  struct pci_dev *lpc_ich_dev;
  struct lpc_ich_priv *priv;
  struct resource *res;
  unsigned int base_addr_cfg, base_addr;
  int ret,i;

	// Get the PCU device
        lpc_ich_dev=pci_get_device(PCI_VENDOR_ID_INTEL,AVOTON_PCU_DEVICE_ID,NULL);
        priv=(struct lpc_ich_priv*) pci_get_drvdata(lpc_ich_dev);
        if(!priv) {
		pr_info("dell_ich: Unable to retrieve private data\n");
		return -ENODEV;
        }

	// Retrieve the GPIO Base (that was initialized by lpc-ich)
        pci_read_config_dword(lpc_ich_dev, priv->gbase, &base_addr_cfg);
        base_addr = base_addr_cfg & 0x0000ff80;
        if (!base_addr) {
                pr_info("dell_ich I/O space for GPIO uninitialized\n");
                ret = -ENODEV;
                goto probe_err;
        }

	res = &gpio_ich_res[ICH_RES_GPIO];
        res->start = base_addr;
        res->end = res->start + 0x9c - 1;
        ret = acpi_check_resource_conflict(res);
        if (ret) {
            pr_info("dell_ich gpio resource conflict ret %d\n",ret);
        }

        ich_data->gpio_base=res;
	// Request regions for GPIO registers
        for(i=0; i<ARRAY_SIZE(avoton_gpio_regs); i++) {
		if (!request_region(res->start+avoton_gpio_regs[i],GPIO_REG_LEN, "dell_ich_gpio")) {
			pr_info("dell_ich: request_region failed for GPIO : %x\n",(unsigned int) res->start+avoton_gpio_regs[i]);
			ret = -EBUSY;
                	goto probe_err;
		}
                ich_data->gpio_alloc |= (1<<i);
	}

	/* Register sysfs hooks for gpio */
	ret = sysfs_create_group(&dev->kobj, &gpio_attrs_group);
	if (ret) {
		pr_info("dell_ich cannot create sysfs for GPIO %d\n",ret);
                ret = -ENOMEM;
                goto probe_err;
	}

	// Retrieve the ACPI Base (that was initialized by lpc-ich)
	pci_read_config_dword(lpc_ich_dev, priv->abase, &base_addr_cfg);
	base_addr = base_addr_cfg & 0x0000ff80;
	if (!base_addr) {
		pr_info("dell_ich I/O space for ACPI uninitialized\n");
		ret = -ENODEV;
		goto probe_err;
	}

	res = &gpio_ich_res[ICH_RES_GPE0];
	res->start = base_addr;
	res->end = base_addr + 0x40;
	ret = acpi_check_resource_conflict(res);
	if (ret) {
            pr_info("dell_ich acpi resource conflict ret %d\n",ret);
        }

        // ACPI region is requested by pnp 00:01/ACPI GPE0_BLK
        ich_data->acpi_base=res;

        /* Register sysfs hooks for ACPI */
        ret = sysfs_create_group(&dev->kobj, &acpi_attrs_group);
        if (ret) {
                pr_info("dell_ich cannot create sysfs for ACPI %d\n",ret);
                ret = -ENOMEM;
                goto probe_err;
        }

	// Retrieve the PMC Base (that was initialized by lpc-ich)
	pci_read_config_dword(lpc_ich_dev, priv->actrl_pbase, &base_addr_cfg);
	base_addr = base_addr_cfg & 0xfffffe00;
        pr_info("dell_ich base_addr_cfg %x base_addr %x\n",(int)base_addr_cfg,(int)base_addr);
        if (!base_addr) {
                pr_info("dell_ich PMC space for GPIO uninitialized\n");
                ret = -ENODEV;
                goto probe_err;
        }

        res =  &pmc_res;
        res->start = base_addr + PMC_GPIO_ROUT;
        res->end = base_addr + PMC_GPIO_ROUT + PMC_REG_LEN - 1;
        pr_info("dell_ich pmc res_start:end %x:%x\n",(int)res->start,(int)res->end);

        ret = acpi_check_resource_conflict(res);
        if (ret) {
            pr_info("dell_ich acpi resource conflict ret %d\n",ret);
        }

        if (!request_mem_region(res->start,resource_size(res),"dell_ich_pmc")) {
		pr_info("dell_ich pmc request_region failed\n");
		ret = -EBUSY;
		goto probe_err;
	} else {
		ich_data->pmc_alloc=1;
	}

	ich_data->pmc_base = ioremap(res->start, resource_size(res));
        if(!ich_data->pmc_base) {
		pr_info("dell_ich pmc ioremap failed\n");
		ret = -ENOMEM;	
		goto probe_err;
        }

        /* Register sysfs hooks for pmc */
        ret = sysfs_create_group(&dev->kobj, &pmc_attrs_group);
        if (ret) {
                pr_info("dell_ich cannot create sysfs for PMC %d\n",ret);
                ret = -ENOMEM;
                goto probe_err;
        }

        /* Register sysfs hooks for SCI interrupts*/
        ret = sysfs_create_group(&dev->kobj, &sci_attrs_group);
        if (ret) {
                pr_info("dell_ich cannot create sysfs for SCI %d\n",ret);
                ret = -ENOMEM;
                goto probe_err;
        }

        if((ich_data->kind == z9100smf) || (ich_data->kind == s6100smf)) {
	    ret = setup_gpio_sus6_sci_interrupt(dev);
            if (ret) {
                pr_info("dell_ich unable to setup SCI interrupt %d\n",ret);
                goto probe_err;
            }
        }

	return 0;

probe_err:
        pr_info("dell_ich dell_ich_probe failed with : %d\n",ret);
	return ret;
}

static int dell_ich_remove(struct platform_device *pdev)
{
  struct device *dev = &pdev->dev;
  struct dell_ich_data *ich_data = dev_get_platdata(dev);
  int i,ret;

    // Release GPIO regions
    for(i=0; i<ARRAY_SIZE(avoton_gpio_regs); i++) {
        if(ich_data->gpio_alloc & (1<<i)) {
            release_region(ich_data->gpio_base->start+avoton_gpio_regs[i], GPIO_REG_LEN);
        }
    }

    // Unmap and release PMC regions
    if(ich_data->pmc_base) iounmap(ich_data->pmc_base);
    if(ich_data->pmc_alloc) release_region(pmc_res.start, PMC_REG_LEN);

    ret = acpi_remove_sci_handler(dell_ich_sci_handler);
    if(ret) {
        pr_info("dell_ich acpi_remove_sci_handler failed %d\n",ret);
        return ret;
    }

    pr_info("dell_ich : dell_ich_remove done.\n");

    return 0;
}

static struct platform_driver dell_ich_driver= {
	.driver		= {
		.name	= DRV_NAME,
	},
	.probe		= dell_ich_probe,
	.remove		= dell_ich_remove,
};

int __init
init_ich_data(int smf_address, struct dell_ich_data *ich_data)
{
        int val;

	memset(ich_data, 0, sizeof(struct dell_ich_data));

        if (force_id)
                val = force_id;
        else
                val = inb(smf_address + SIO_REG_DEVID);

        switch (val) {
                case SIO_Z9100_ID:
                        ich_data->kind = z9100smf;
                        break;
                case SIO_S6100_ID:
                        ich_data->kind = s6100smf;
                        break;
                default:
                        if (val != 0xffff)
                                pr_debug("unsupported chip ID: 0x%04x\n", val);
                        return -ENODEV;
        }

        pr_info("dell_ich: found SMF for ID %#x\n", ich_data->kind);

        return (0);
}

static struct platform_device *pdev;

static int __init dell_ich_init(void)
{
        int err;
        struct dell_ich_data ich_data;

        if (init_ich_data(SMF_REG_ADDR, &ich_data))
                return -ENODEV;

        err = platform_driver_register(&dell_ich_driver);
        if (err)
                goto exit;

        pdev = platform_device_alloc(DRV_NAME, 0);
        if (!pdev) {
                err = -ENOMEM;
                pr_err("dell_ich: Device allocation failed\n");
                goto exit_unregister;
        }

        err = platform_device_add_data(pdev, &ich_data,
                        sizeof(struct dell_ich_data));
        if (err) {
                pr_err("dell_ich: Platform data allocation failed\n");
                goto exit_device_put;
        }

        /* platform_device_add calls probe() */
        err = platform_device_add(pdev);
        if (err) {
                pr_err("dell_ich: Device addition failed (%d)\n", err);
                goto exit_device_put;
        }

        return 0;

exit_device_put:
        platform_device_put(pdev);
exit_unregister:
        platform_driver_unregister(&dell_ich_driver);
exit:
	pr_err("dell_ich: dell_ich_init failed (%d)\n", err);
        return err;
}

static void __exit dell_ich_exit(void)
{
        platform_device_unregister(pdev);
        platform_driver_unregister(&dell_ich_driver);

        /*Remove sysfs dell_kobj*/
        kobject_put(dell_kobj);
}

MODULE_AUTHOR("Padmanabhan Narayanan");
MODULE_DESCRIPTION("ICH driver for Dell Avoton/Rangeley switches");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:"DRV_NAME);
MODULE_PARM_DESC(force_id, "Override the detected device ID");

MODULE_SOFTDEP("pre: lpc_ich");
module_init(dell_ich_init);
module_exit(dell_ich_exit);