diff options
Diffstat (limited to 'drivers')
33 files changed, 5019 insertions, 88 deletions
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 56bd16e77ae3..c871365458e2 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_ARCH_BCM2835) += bcm2835_timer.o obj-$(CONFIG_ARCH_CLPS711X) += clps711x-timer.o obj-$(CONFIG_ARCH_ATLAS7) += timer-atlas7.o obj-$(CONFIG_ARCH_MOXART) += moxart_timer.o +obj-$(CONFIG_ARCH_ASPEED) += moxart_timer.o obj-$(CONFIG_ARCH_MXS) += mxs_timer.o obj-$(CONFIG_CLKSRC_PXA) += pxa_timer.o obj-$(CONFIG_ARCH_PRIMA2) += timer-prima2.o diff --git a/drivers/clocksource/moxart_timer.c b/drivers/clocksource/moxart_timer.c index 19857af651c1..bd8dbf388e24 100644 --- a/drivers/clocksource/moxart_timer.c +++ b/drivers/clocksource/moxart_timer.c @@ -36,45 +36,66 @@ #define TIMER_INTR_MASK 0x38 /* - * TIMER_CR flags: + * Moxart TIMER_CR flags: * * TIMEREG_CR_*_CLOCK 0: PCLK, 1: EXT1CLK * TIMEREG_CR_*_INT overflow interrupt enable bit */ -#define TIMEREG_CR_1_ENABLE BIT(0) -#define TIMEREG_CR_1_CLOCK BIT(1) -#define TIMEREG_CR_1_INT BIT(2) -#define TIMEREG_CR_2_ENABLE BIT(3) -#define TIMEREG_CR_2_CLOCK BIT(4) -#define TIMEREG_CR_2_INT BIT(5) -#define TIMEREG_CR_3_ENABLE BIT(6) -#define TIMEREG_CR_3_CLOCK BIT(7) -#define TIMEREG_CR_3_INT BIT(8) -#define TIMEREG_CR_COUNT_UP BIT(9) - -#define TIMER1_ENABLE (TIMEREG_CR_2_ENABLE | TIMEREG_CR_1_ENABLE) -#define TIMER1_DISABLE (TIMEREG_CR_2_ENABLE) +#define MOXART_CR_1_ENABLE BIT(0) +#define MOXART_CR_1_CLOCK BIT(1) +#define MOXART_CR_1_INT BIT(2) +#define MOXART_CR_2_ENABLE BIT(3) +#define MOXART_CR_2_CLOCK BIT(4) +#define MOXART_CR_2_INT BIT(5) +#define MOXART_CR_3_ENABLE BIT(6) +#define MOXART_CR_3_CLOCK BIT(7) +#define MOXART_CR_3_INT BIT(8) +#define MOXART_CR_COUNT_UP BIT(9) + +#define MOXART_TIMER1_ENABLE (MOXART_CR_2_ENABLE | MOXART_CR_1_ENABLE) +#define MOXART_TIMER1_DISABLE (MOXART_CR_2_ENABLE) + +/* + * The ASpeed variant of the IP block has a different layout + * for the control register + */ +#define ASPEED_CR_1_ENABLE BIT(0) +#define ASPEED_CR_1_CLOCK BIT(1) +#define ASPEED_CR_1_INT BIT(2) +#define ASPEED_CR_2_ENABLE BIT(4) +#define ASPEED_CR_2_CLOCK BIT(5) +#define ASPEED_CR_2_INT BIT(6) +#define ASPEED_CR_3_ENABLE BIT(8) +#define ASPEED_CR_3_CLOCK BIT(9) +#define ASPEED_CR_3_INT BIT(10) + +#define ASPEED_TIMER1_ENABLE (ASPEED_CR_2_ENABLE | ASPEED_CR_1_ENABLE) +#define ASPEED_TIMER1_DISABLE (ASPEED_CR_2_ENABLE) static void __iomem *base; static unsigned int clock_count_per_tick; +static unsigned int t1_disable_val, t1_enable_val; static int moxart_shutdown(struct clock_event_device *evt) { - writel(TIMER1_DISABLE, base + TIMER_CR); + writel(t1_disable_val, base + TIMER_CR); return 0; } static int moxart_set_oneshot(struct clock_event_device *evt) { - writel(TIMER1_DISABLE, base + TIMER_CR); + writel(t1_disable_val, base + TIMER_CR); writel(~0, base + TIMER1_BASE + REG_LOAD); return 0; } static int moxart_set_periodic(struct clock_event_device *evt) { + writel(t1_disable_val, base + TIMER_CR); writel(clock_count_per_tick, base + TIMER1_BASE + REG_LOAD); - writel(TIMER1_ENABLE, base + TIMER_CR); + writel(0, base + TIMER1_BASE + REG_MATCH1); + writel(t1_enable_val, base + TIMER_CR); + return 0; } @@ -83,12 +104,12 @@ static int moxart_clkevt_next_event(unsigned long cycles, { u32 u; - writel(TIMER1_DISABLE, base + TIMER_CR); + writel(t1_disable_val, base + TIMER_CR); u = readl(base + TIMER1_BASE + REG_COUNT) - cycles; writel(u, base + TIMER1_BASE + REG_MATCH1); - writel(TIMER1_ENABLE, base + TIMER_CR); + writel(t1_enable_val, base + TIMER_CR); return 0; } @@ -119,7 +140,7 @@ static struct irqaction moxart_timer_irq = { .dev_id = &moxart_clockevent, }; -static void __init moxart_timer_init(struct device_node *node) +static void __init __moxart_timer_init(struct device_node *node) { int ret, irq; unsigned long pclk; @@ -150,8 +171,21 @@ static void __init moxart_timer_init(struct device_node *node) clock_count_per_tick = DIV_ROUND_CLOSEST(pclk, HZ); + /* Clean up match registers we will still get an occasional interrupt + * from timer 2 but I haven't enabled it for now + */ + writel(0, base + TIMER1_BASE + REG_MATCH1); + writel(0, base + TIMER1_BASE + REG_MATCH2); + writel(0, base + TIMER2_BASE + REG_MATCH1); + writel(0, base + TIMER2_BASE + REG_MATCH2); + + /* Start timer 2 rolling as our main wall clock source, keep timer 1 + * disabled + */ + writel(0, base + TIMER_CR); writel(~0, base + TIMER2_BASE + REG_LOAD); - writel(TIMEREG_CR_2_ENABLE, base + TIMER_CR); + writel(t1_disable_val, base + TIMER_CR); + moxart_clockevent.cpumask = cpumask_of(0); moxart_clockevent.irq = irq; @@ -165,4 +199,20 @@ static void __init moxart_timer_init(struct device_node *node) clockevents_config_and_register(&moxart_clockevent, pclk, 0x4, 0xfffffffe); } + +static void __init moxart_timer_init(struct device_node *node) +{ + t1_enable_val = MOXART_TIMER1_ENABLE; + t1_disable_val = MOXART_TIMER1_DISABLE; + __moxart_timer_init(node); +} + +static void __init aspeed_timer_init(struct device_node *node) +{ + t1_enable_val = ASPEED_TIMER1_ENABLE; + t1_disable_val = ASPEED_TIMER1_DISABLE; + __moxart_timer_init(node); +} + CLOCKSOURCE_OF_DECLARE(moxart, "moxa,moxart-timer", moxart_timer_init); +CLOCKSOURCE_OF_DECLARE(aspeed, "aspeed,timer", aspeed_timer_init); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 469dc378adeb..1225d92ab0d6 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -127,6 +127,13 @@ config GPIO_AMDPT driver for GPIO functionality on Promontory IOHub Require ACPI ASL code to enumerate as a platform device. +config GPIO_ASPEED + bool "Aspeed AST2400 GPIO support" + depends on (ARCH_ASPEED || COMPILE_TEST) && OF + select GENERIC_IRQ_CHIP + help + Say Y here to support Aspeed AST2400 GPIO. + config GPIO_BCM_KONA bool "Broadcom Kona GPIO" depends on OF_GPIO && (ARCH_BCM_MOBILE || COMPILE_TEST) @@ -983,7 +990,6 @@ config GPIO_SODAVILLE select GENERIC_IRQ_CHIP help Say Y here to support Intel Sodaville GPIO. - endmenu menu "SPI GPIO expanders" diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 986dbd838cea..9eb249a6822b 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o obj-$(CONFIG_GPIO_AMDPT) += gpio-amdpt.o obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o obj-$(CONFIG_ATH79) += gpio-ath79.o +obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c new file mode 100644 index 000000000000..87dfcdf6bc6f --- /dev/null +++ b/drivers/gpio/gpio-aspeed.c @@ -0,0 +1,448 @@ +/* + * Copyright 2015 IBM Corp. + * + * Joel Stanley <joel@jms.id.au> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/gpio/driver.h> + +struct aspeed_gpio { + struct gpio_chip chip; + spinlock_t lock; + void __iomem *base; + int irq; + struct irq_chip irq_chip; + struct irq_domain *irq_domain; +}; + +struct aspeed_gpio_bank { + uint16_t val_regs; + uint16_t irq_regs; + const char names[4]; +}; + +static const struct aspeed_gpio_bank aspeed_gpio_banks[] = { + { + .val_regs = 0x0000, + .irq_regs = 0x0008, + .names = { 'A', 'B', 'C', 'D' }, + }, + { + .val_regs = 0x0020, + .irq_regs = 0x0028, + .names = { 'E', 'F', 'G', 'H' }, + }, + { + .val_regs = 0x0070, + .irq_regs = 0x0098, + .names = { 'I', 'J', 'K', 'L' }, + }, + { + .val_regs = 0x0078, + .irq_regs = 0x00e8, + .names = { 'M', 'N', 'O', 'P' }, + }, + { + .val_regs = 0x0080, + .irq_regs = 0x0118, + .names = { 'Q', 'R', 'S', 'T' }, + }, + { + .val_regs = 0x0088, + .irq_regs = 0x0148, + .names = { 'U', 'V', 'W', 'X' }, + }, +}; + +#define GPIO_BANK(x) ((x) >> 5) +#define GPIO_OFFSET(x) ((x) & 0x1f) +#define GPIO_BIT(x) BIT(GPIO_OFFSET(x)) + +#define GPIO_DATA 0x00 +#define GPIO_DIR 0x04 + +#define GPIO_IRQ_ENABLE 0x00 +#define GPIO_IRQ_TYPE0 0x04 +#define GPIO_IRQ_TYPE1 0x08 +#define GPIO_IRQ_TYPE2 0x0c +#define GPIO_IRQ_STATUS 0x10 + +static inline struct aspeed_gpio *to_aspeed_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct aspeed_gpio, chip); +} + +static const struct aspeed_gpio_bank *to_bank(unsigned int offset) +{ + unsigned int bank = GPIO_BANK(offset); + WARN_ON(bank > ARRAY_SIZE(aspeed_gpio_banks)); + return &aspeed_gpio_banks[bank]; +} + +static void *bank_val_reg(struct aspeed_gpio *gpio, + const struct aspeed_gpio_bank *bank, + unsigned int reg) +{ + return gpio->base + bank->val_regs + reg; +} + +static void *bank_irq_reg(struct aspeed_gpio *gpio, + const struct aspeed_gpio_bank *bank, + unsigned int reg) +{ + return gpio->base + bank->irq_regs + reg; +} + +static int aspeed_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct aspeed_gpio *gpio = to_aspeed_gpio(gc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + + return !!(ioread32(bank_val_reg(gpio, bank, GPIO_DATA)) + & GPIO_BIT(offset)); +} + +static void __aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset, int val) +{ + u32 reg; + void __iomem *addr; + struct aspeed_gpio *gpio = to_aspeed_gpio(gc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + + addr = bank_val_reg(gpio, bank, GPIO_DATA); + + reg = ioread32(addr); + if (val) + reg |= GPIO_BIT(offset); + else + reg &= ~GPIO_BIT(offset); + + iowrite32(reg, addr); +} + +static void aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset, + int val) +{ + struct aspeed_gpio *gpio = to_aspeed_gpio(gc); + unsigned long flags; + + spin_lock_irqsave(&gpio->lock, flags); + + __aspeed_gpio_set(gc, offset, val); + + spin_unlock_irqrestore(&gpio->lock, flags); +} + +static int aspeed_gpio_dir_in(struct gpio_chip *gc, unsigned int offset) +{ + struct aspeed_gpio *gpio = to_aspeed_gpio(gc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&gpio->lock, flags); + + reg = ioread32(bank_val_reg(gpio, bank, GPIO_DIR)); + iowrite32(reg & ~GPIO_BIT(offset), bank_val_reg(gpio, bank, GPIO_DIR)); + + spin_unlock_irqrestore(&gpio->lock, flags); + + return 0; +} + +static int aspeed_gpio_dir_out(struct gpio_chip *gc, + unsigned int offset, int val) +{ + struct aspeed_gpio *gpio = to_aspeed_gpio(gc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&gpio->lock, flags); + + reg = ioread32(bank_val_reg(gpio, bank, GPIO_DIR)); + iowrite32(reg | GPIO_BIT(offset), bank_val_reg(gpio, bank, GPIO_DIR)); + + __aspeed_gpio_set(gc, offset, val); + + spin_unlock_irqrestore(&gpio->lock, flags); + + return 0; +} + +static inline int irqd_to_aspeed_gpio_data(struct irq_data *d, + struct aspeed_gpio **gpio, + const struct aspeed_gpio_bank **bank, + u32 *bit) +{ + int offset; + + offset = irqd_to_hwirq(d); + + *gpio = irq_data_get_irq_chip_data(d); + *bank = to_bank(offset); + *bit = GPIO_BIT(offset); + + return 0; +} + +static void aspeed_gpio_irq_ack(struct irq_data *d) +{ + const struct aspeed_gpio_bank *bank; + struct aspeed_gpio *gpio; + unsigned long flags; + void *status_addr; + u32 bit; + int rc; + + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + if (rc) + return; + + status_addr = bank_irq_reg(gpio, bank, GPIO_IRQ_STATUS); + + spin_lock_irqsave(&gpio->lock, flags); + iowrite32(bit, status_addr); + spin_unlock_irqrestore(&gpio->lock, flags); +} + +static void __aspeed_gpio_irq_set_mask(struct irq_data *d, bool set) +{ + const struct aspeed_gpio_bank *bank; + struct aspeed_gpio *gpio; + unsigned long flags; + u32 reg, bit; + void *addr; + int rc; + + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + if (rc) + return; + + addr = bank_irq_reg(gpio, bank, GPIO_IRQ_ENABLE); + + spin_lock_irqsave(&gpio->lock, flags); + + reg = ioread32(addr); + if (set) + reg |= bit; + else + reg &= bit; + iowrite32(reg, addr); + + spin_unlock_irqrestore(&gpio->lock, flags); +} + +static void aspeed_gpio_irq_mask(struct irq_data *d) +{ + __aspeed_gpio_irq_set_mask(d, false); +} + +static void aspeed_gpio_irq_unmask(struct irq_data *d) +{ + __aspeed_gpio_irq_set_mask(d, true); +} + +static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type) +{ + u32 type0, type1, type2, bit, reg; + const struct aspeed_gpio_bank *bank; + irq_flow_handler_t handler; + struct aspeed_gpio *gpio; + unsigned long flags; + void *addr; + int rc; + + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + if (rc) + return -EINVAL; + + type0 = type1 = type2 = 0; + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_BOTH: + type2 |= bit; + case IRQ_TYPE_EDGE_RISING: + type0 |= bit; + case IRQ_TYPE_EDGE_FALLING: + handler = handle_edge_irq; + break; + case IRQ_TYPE_LEVEL_HIGH: + type0 |= bit; + case IRQ_TYPE_LEVEL_LOW: + type1 |= bit; + handler = handle_level_irq; + break; + default: + return -EINVAL; + } + + spin_lock_irqsave(&gpio->lock, flags); + + addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE0); + reg = ioread32(addr); + reg = (reg & ~bit) | type0; + iowrite32(reg, addr); + + addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE1); + reg = ioread32(addr); + reg = (reg & ~bit) | type1; + iowrite32(reg, addr); + + addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE2); + reg = ioread32(addr); + reg = (reg & ~bit) | type2; + iowrite32(reg, addr); + + spin_unlock_irqrestore(&gpio->lock, flags); + + irq_set_handler_locked(d, handler); + + return 0; +} + +static void aspeed_gpio_irq_handler(struct irq_desc *desc) +{ + struct aspeed_gpio *gpio = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned int i, p, girq; + unsigned long reg; + + chained_irq_enter(chip, desc); + + for (i = 0; i < ARRAY_SIZE(aspeed_gpio_banks); i++) { + const struct aspeed_gpio_bank *bank = &aspeed_gpio_banks[i]; + + reg = ioread32(bank_irq_reg(gpio, bank, GPIO_IRQ_STATUS)); + + for_each_set_bit(p, ®, 32) { + girq = irq_find_mapping(gpio->irq_domain, i * 32 + p); + generic_handle_irq(girq); + } + + } + + chained_irq_exit(chip, desc); +} + +static struct irq_chip aspeed_gpio_irqchip = { + .name = "aspeed-gpio", + .irq_ack = aspeed_gpio_irq_ack, + .irq_mask = aspeed_gpio_irq_mask, + .irq_unmask = aspeed_gpio_irq_unmask, + .irq_set_type = aspeed_gpio_set_type, +}; + +static int aspeed_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct aspeed_gpio *gpio = to_aspeed_gpio(chip); + return irq_find_mapping(gpio->irq_domain, offset); +} + +static void aspeed_gpio_setup_irqs(struct aspeed_gpio *gpio, + struct platform_device *pdev) +{ + int i, irq; + + /* request our upstream IRQ */ + gpio->irq = platform_get_irq(pdev, 0); + if (gpio->irq < 0) + return; + + /* establish our irq domain to provide IRQs for each extended bank */ + gpio->irq_domain = irq_domain_add_linear(pdev->dev.of_node, + gpio->chip.ngpio, &irq_domain_simple_ops, NULL); + if (!gpio->irq_domain) + return; + + for (i = 0; i < gpio->chip.ngpio; i++) { + irq = irq_create_mapping(gpio->irq_domain, i); + irq_set_chip_data(irq, gpio); + irq_set_chip_and_handler(irq, &aspeed_gpio_irqchip, + handle_simple_irq); + irq_set_probe(irq); + } + + irq_set_chained_handler_and_data(gpio->irq, + aspeed_gpio_irq_handler, gpio); +} + + +static int __init aspeed_gpio_probe(struct platform_device *pdev) +{ + struct aspeed_gpio *gpio; + struct resource *res; + int rc; + + gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + gpio->base = devm_ioremap_resource(&pdev->dev, res); + if (!gpio->base) + return -ENOMEM; + + spin_lock_init(&gpio->lock); + + gpio->chip.ngpio = ARRAY_SIZE(aspeed_gpio_banks) * 32; + + gpio->chip.dev = &pdev->dev; + gpio->chip.direction_input = aspeed_gpio_dir_in; + gpio->chip.direction_output = aspeed_gpio_dir_out; + gpio->chip.get = aspeed_gpio_get; + gpio->chip.set = aspeed_gpio_set; + gpio->chip.to_irq = aspeed_gpio_to_irq; + gpio->chip.label = dev_name(&pdev->dev); + gpio->chip.base = -1; + + platform_set_drvdata(pdev, gpio); + + rc = gpiochip_add(&gpio->chip); + if (rc < 0) + return rc; + + aspeed_gpio_setup_irqs(gpio, pdev); + + return 0; +} + +static int aspeed_gpio_remove(struct platform_device *pdev) +{ + struct aspeed_gpio *gpio = platform_get_drvdata(pdev); + + gpiochip_remove(&gpio->chip); + return 0; +} + +static const struct of_device_id aspeed_gpio_of_table[] = { + { .compatible = "aspeed,ast2400-gpio" }, + {} +}; +MODULE_DEVICE_TABLE(of, aspeed_gpio_of_table); + +static struct platform_driver aspeed_gpio_driver = { + .remove = aspeed_gpio_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = aspeed_gpio_of_table, + }, +}; + +module_platform_driver_probe(aspeed_gpio_driver, aspeed_gpio_probe); + +MODULE_DESCRIPTION("Aspeed AST2400 GPIO Driver"); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 80a73bfc1a65..399ff70347a0 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1186,6 +1186,19 @@ config SENSORS_NCT7904 This driver can also be built as a module. If so, the module will be called nct7904. +config SENSORS_POWER8_OCC_I2C + tristate "Power8 On Chip Controller i2c driver" + depends on I2C + help + If you say yes here you get support to monitor Power8 CPU + sensors via the On Chip Controller (OCC) over the i2c bus. + + Generally this is used by management controllers such as a BMC + on an OpenPower system. + + This driver can also be built as a module. If so, the module + will be called power8_occ_i2c. + config SENSORS_PCF8591 tristate "Philips PCF8591 ADC/DAC" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 12a32398fdcc..8ee6a7a4dcb6 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -125,6 +125,7 @@ obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o +obj-$(CONFIG_SENSORS_POWER8_OCC_I2C) += power8_occ_i2c.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index df6ebb2b8f0f..ea8a50f46f2e 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -31,8 +31,8 @@ config SENSORS_ADM1275 default n help If you say yes here you get hardware monitoring support for Analog - Devices ADM1075, ADM1275, ADM1276, ADM1293, and ADM1294 Hot-Swap - Controller and Digital Power Monitors. + Devices ADM1075, ADM1275, ADM1276, ADM1278, ADM1293, and ADM1294 + Hot-Swap Controller and Digital Power Monitors. This driver can also be built as a module. If so, the module will be called adm1275. diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index 188af4c89f40..006b1eff758c 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -22,9 +22,10 @@ #include <linux/slab.h> #include <linux/i2c.h> #include <linux/bitops.h> +#include <linux/of.h> #include "pmbus.h" -enum chips { adm1075, adm1275, adm1276, adm1293, adm1294 }; +enum chips { adm1075, adm1275, adm1276, adm1278, adm1293, adm1294 }; #define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) #define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) @@ -41,6 +42,10 @@ enum chips { adm1075, adm1275, adm1276, adm1293, adm1294 }; #define ADM1075_IRANGE_25 BIT(3) #define ADM1075_IRANGE_MASK (BIT(3) | BIT(4)) +#define ADM1278_TEMP1_EN BIT(3) +#define ADM1278_VIN_EN BIT(2) +#define ADM1278_VOUT_EN BIT(1) + #define ADM1293_IRANGE_25 0 #define ADM1293_IRANGE_50 BIT(6) #define ADM1293_IRANGE_100 BIT(7) @@ -54,6 +59,7 @@ enum chips { adm1075, adm1275, adm1276, adm1293, adm1294 }; #define ADM1293_VAUX_EN BIT(1) +#define ADM1278_PEAK_TEMP 0xd7 #define ADM1275_IOUT_WARN2_LIMIT 0xd7 #define ADM1275_DEVICE_CONFIG 0xd8 @@ -80,6 +86,7 @@ struct adm1275_data { bool have_iout_min; bool have_pin_min; bool have_pin_max; + bool have_temp_max; struct pmbus_driver_info info; }; @@ -113,6 +120,13 @@ static const struct coefficients adm1276_coefficients[] = { [4] = { 2115, 0, -1 }, /* power, vrange not set */ }; +static const struct coefficients adm1278_coefficients[] = { + [0] = { 19599, 0, -2 }, /* voltage */ + [1] = { 800, 20475, -1 }, /* current */ + [2] = { 6123, 0, -2 }, /* power */ + [3] = { 42, 31880, -1 }, /* temperature */ +}; + static const struct coefficients adm1293_coefficients[] = { [0] = { 3333, -1, 0 }, /* voltage, vrange 1.2V */ [1] = { 5552, -5, -1 }, /* voltage, vrange 7.4V */ @@ -196,6 +210,11 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg) return -ENXIO; ret = pmbus_read_word_data(client, 0, ADM1276_PEAK_PIN); break; + case PMBUS_VIRT_READ_TEMP_MAX: + if (!data->have_temp_max) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, ADM1278_PEAK_TEMP); + break; case PMBUS_VIRT_RESET_IOUT_HISTORY: case PMBUS_VIRT_RESET_VOUT_HISTORY: case PMBUS_VIRT_RESET_VIN_HISTORY: @@ -204,6 +223,10 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg) if (!data->have_pin_max) return -ENXIO; break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + if (!data->have_temp_max) + return -ENXIO; + break; default: ret = -ENODATA; break; @@ -245,6 +268,9 @@ static int adm1275_write_word_data(struct i2c_client *client, int page, int reg, ret = pmbus_write_word_data(client, 0, ADM1293_PIN_MIN, 0); break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1278_PEAK_TEMP, 0); + break; default: ret = -ENODATA; break; @@ -312,12 +338,26 @@ static const struct i2c_device_id adm1275_id[] = { { "adm1075", adm1075 }, { "adm1275", adm1275 }, { "adm1276", adm1276 }, + { "adm1278", adm1278 }, { "adm1293", adm1293 }, { "adm1294", adm1294 }, { } }; MODULE_DEVICE_TABLE(i2c, adm1275_id); +#ifdef CONFIG_OF +static const struct of_device_id adm1275_of_match[] = { + { .compatible = "adi,adm1075" }, + { .compatible = "adi,adm1275" }, + { .compatible = "adi,adm1276" }, + { .compatible = "adi,adm1278" }, + { .compatible = "adi,adm1293" }, + { .compatible = "adi,adm1294" }, + { } +}; +MODULE_DEVICE_TABLE(of, adm1275_of_match); +#endif + static int adm1275_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -329,6 +369,7 @@ static int adm1275_probe(struct i2c_client *client, const struct i2c_device_id *mid; const struct coefficients *coefficients; int vindex = -1, voindex = -1, cindex = -1, pindex = -1; + int tindex = -1; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA @@ -386,6 +427,7 @@ static int adm1275_probe(struct i2c_client *client, info->format[PSC_VOLTAGE_OUT] = direct; info->format[PSC_CURRENT_OUT] = direct; info->format[PSC_POWER] = direct; + info->format[PSC_TEMPERATURE] = direct; info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; info->read_word_data = adm1275_read_word_data; @@ -460,6 +502,40 @@ static int adm1275_probe(struct i2c_client *client, info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; break; + case adm1278: + data->have_vout = true; + data->have_pin_max = true; + data->have_temp_max = true; + + coefficients = adm1278_coefficients; + vindex = 0; + cindex = 1; + pindex = 2; + tindex = 3; + + info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT; + + /* By default when reset VOUT is not enabled */ + if (!(config & ADM1278_VOUT_EN)) { + config |= ADM1278_VOUT_EN; + ret = i2c_smbus_write_byte_data(client, + ADM1275_PMON_CONFIG, (u8)config); + if (ret < 0) { + dev_err(&client->dev, + "Fail to write ADM1275_PMON_CONFIG\n"); + return ret; + } + } + + if (config & ADM1278_TEMP1_EN) + info->func[0] |= + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + if (config & ADM1278_VOUT_EN) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + break; case adm1293: case adm1294: data->have_iout_min = true; @@ -537,6 +613,11 @@ static int adm1275_probe(struct i2c_client *client, info->b[PSC_POWER] = coefficients[pindex].b; info->R[PSC_POWER] = coefficients[pindex].R; } + if (tindex >= 0) { + info->m[PSC_TEMPERATURE] = coefficients[tindex].m; + info->b[PSC_TEMPERATURE] = coefficients[tindex].b; + info->R[PSC_TEMPERATURE] = coefficients[tindex].R; + } return pmbus_do_probe(client, id, info); } @@ -544,6 +625,7 @@ static int adm1275_probe(struct i2c_client *client, static struct i2c_driver adm1275_driver = { .driver = { .name = "adm1275", + .of_match_table = of_match_ptr(adm1275_of_match), }, .probe = adm1275_probe, .remove = pmbus_do_remove, diff --git a/drivers/hwmon/power8_occ_i2c.c b/drivers/hwmon/power8_occ_i2c.c new file mode 100644 index 000000000000..6de0e76ae21b --- /dev/null +++ b/drivers/hwmon/power8_occ_i2c.c @@ -0,0 +1,1254 @@ +/* + * OCC HWMON driver - read IBM Power8 On Chip Controller sensor data via + * i2c. + * + * Copyright 2015 IBM Corp. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/device.h> + +#define OCC_I2C_ADDR 0x50 +#define OCC_I2C_NAME "occ-i2c" + +#define OCC_DATA_MAX 4096 /* 4KB at most */ +/* i2c read and write occ sensors */ +#define I2C_READ_ERROR 1 +#define I2C_WRITE_ERROR 2 + +/* Defined in POWER8 Processor Registers Specification */ +/* To generate attn to OCC */ +#define ATTN_DATA 0x0006B035 +/* For BMC to read/write SRAM */ +#define OCB_ADDRESS 0x0006B070 +#define OCB_DATA 0x0006B075 +#define OCB_STATUS_CONTROL_AND 0x0006B072 +#define OCB_STATUS_CONTROL_OR 0x0006B073 +/* See definition in: + * https://github.com/open-power/docs/blob/master/occ/OCC_OpenPwr_FW_Interfaces.pdf + */ +#define OCC_COMMAND_ADDR 0xFFFF6000 +#define OCC_RESPONSE_ADDR 0xFFFF7000 + +#define MAX_SENSOR_ATTR_LEN 32 + +enum sensor_t { + freq, + temp, + power, + caps, + MAX_OCC_SENSOR_TYPE +}; + +/* OCC sensor data format */ +struct occ_sensor { + uint16_t sensor_id; + uint16_t value; +}; + +struct power_sensor { + uint16_t sensor_id; + uint32_t update_tag; + uint32_t accumulator; + uint16_t value; +}; + +struct caps_sensor { + uint16_t curr_powercap; + uint16_t curr_powerreading; + uint16_t norm_powercap; + uint16_t max_powercap; + uint16_t min_powercap; + uint16_t user_powerlimit; +}; + +struct sensor_data_block { + uint8_t sensor_type[4]; + uint8_t reserved0; + uint8_t sensor_format; + uint8_t sensor_length; + uint8_t sensor_num; + struct occ_sensor *sensor; + struct power_sensor *power; + struct caps_sensor *caps; +}; + +struct occ_poll_header { + uint8_t status; + uint8_t ext_status; + uint8_t occs_present; + uint8_t config; + uint8_t occ_state; + uint8_t reserved0; + uint8_t reserved1; + uint8_t error_log_id; + uint32_t error_log_addr_start; + uint16_t error_log_length; + uint8_t reserved2; + uint8_t reserved3; + uint8_t occ_code_level[16]; + uint8_t sensor_eye_catcher[6]; + uint8_t sensor_block_num; + uint8_t sensor_data_version; +}; + +struct occ_response { + uint8_t sequence_num; + uint8_t cmd_type; + uint8_t rtn_status; + uint16_t data_length; + struct occ_poll_header header; + struct sensor_data_block *blocks; + uint16_t chk_sum; + int sensor_block_id[MAX_OCC_SENSOR_TYPE]; +}; + +struct sensor_attr_data { + enum sensor_t type; + uint32_t hwmon_index; + uint32_t attr_id; + char name[MAX_SENSOR_ATTR_LEN]; + struct device_attribute dev_attr; +}; + +struct sensor_group { + char *name; + struct sensor_attr_data *sattr; + struct attribute_group group; +}; + +/* data private to each client */ +struct occ_drv_data { + struct i2c_client *client; + struct device *hwmon_dev; + struct mutex update_lock; + bool valid; + unsigned long last_updated; + /* Minimum timer interval for sampling In jiffies */ + unsigned long update_interval; + unsigned long occ_online; + uint16_t user_powercap; + struct occ_response occ_resp; + struct sensor_group sensor_groups[MAX_OCC_SENSOR_TYPE]; +}; + +static void deinit_occ_resp_buf(struct occ_response *p) +{ + int i; + + if (!p) + return; + + if (!p->blocks) + return; + + for (i = 0; i < p->header.sensor_block_num; i++) { + kfree(p->blocks[i].sensor); + kfree(p->blocks[i].power); + kfree(p->blocks[i].caps); + } + + kfree(p->blocks); + + memset(p, 0, sizeof(*p)); + + for (i = 0; i < ARRAY_SIZE(p->sensor_block_id); i++) + p->sensor_block_id[i] = -1; +} + +static ssize_t occ_i2c_read(struct i2c_client *client, void *buf, size_t count) +{ + WARN_ON(count > OCC_DATA_MAX); + + dev_dbg(&client->dev, "i2c_read: reading %zu bytes @0x%x.\n", + count, client->addr); + return i2c_master_recv(client, buf, count); +} + +static ssize_t occ_i2c_write(struct i2c_client *client, const void *buf, + size_t count) +{ + WARN_ON(count > OCC_DATA_MAX); + + dev_dbg(&client->dev, "i2c_write: writing %zu bytes @0x%x.\n", + count, client->addr); + return i2c_master_send(client, buf, count); +} + +/* read 8-byte value and put into data[offset] */ +static int occ_getscomb(struct i2c_client *client, uint32_t address, + uint8_t *data, int offset) +{ + uint32_t ret; + char buf[8]; + int i; + + /* P8 i2c slave requires address to be shifted by 1 */ + address = address << 1; + + ret = occ_i2c_write(client, &address, + sizeof(address)); + + if (ret != sizeof(address)) + return -I2C_WRITE_ERROR; + + ret = occ_i2c_read(client, buf, sizeof(buf)); + if (ret != sizeof(buf)) + return -I2C_READ_ERROR; + + for (i = 0; i < 8; i++) + data[offset + i] = buf[7 - i]; + + return 0; +} + +static int occ_putscom(struct i2c_client *client, uint32_t address, + uint32_t data0, uint32_t data1) +{ + uint32_t buf[3]; + uint32_t ret; + + /* P8 i2c slave requires address to be shifted by 1 */ + address = address << 1; + + buf[0] = address; + buf[1] = data1; + buf[2] = data0; + + ret = occ_i2c_write(client, buf, sizeof(buf)); + if (ret != sizeof(buf)) + return I2C_WRITE_ERROR; + + return 0; +} + +static void *occ_get_sensor_by_type(struct occ_response *resp, enum sensor_t t) +{ + void *sensor; + + if (!resp->blocks) + return NULL; + + if (resp->sensor_block_id[t] == -1) + return NULL; + + switch (t) { + case temp: + case freq: + sensor = resp->blocks[resp->sensor_block_id[t]].sensor; + break; + case power: + sensor = resp->blocks[resp->sensor_block_id[t]].power; + break; + case caps: + sensor = resp->blocks[resp->sensor_block_id[t]].caps; + break; + default: + sensor = NULL; + } + + return sensor; +} + +static int occ_renew_sensor(struct occ_response *resp, uint8_t sensor_length, + uint8_t sensor_num, enum sensor_t t, int block) +{ + void *sensor; + int ret; + + sensor = occ_get_sensor_by_type(resp, t); + + /* empty sensor block, release older sensor data */ + if (sensor_num == 0 || sensor_length == 0) { + kfree(sensor); + return -1; + } + + if (!sensor || sensor_num != + resp->blocks[resp->sensor_block_id[t]].sensor_num) { + kfree(sensor); + switch (t) { + case temp: + case freq: + resp->blocks[block].sensor = + kcalloc(sensor_num, + sizeof(struct occ_sensor), GFP_KERNEL); + if (!resp->blocks[block].sensor) { + ret = -ENOMEM; + goto err; + } + break; + case power: + resp->blocks[block].power = + kcalloc(sensor_num, + sizeof(struct power_sensor), + GFP_KERNEL); + if (!resp->blocks[block].power) { + ret = -ENOMEM; + goto err; + } + break; + case caps: + resp->blocks[block].caps = + kcalloc(sensor_num, + sizeof(struct caps_sensor), GFP_KERNEL); + if (!resp->blocks[block].caps) { + ret = -ENOMEM; + goto err; + } + break; + default: + ret = -ENOMEM; + goto err; + } + } + + return 0; +err: + deinit_occ_resp_buf(resp); + return ret; +} + +#define RESP_DATA_LENGTH 3 +#define RESP_HEADER_OFFSET 5 +#define SENSOR_STR_OFFSET 37 +#define SENSOR_BLOCK_NUM_OFFSET 43 +#define SENSOR_BLOCK_OFFSET 45 + +static inline uint16_t get_occdata_length(uint8_t *data) +{ + return be16_to_cpup((const __be16 *)&data[RESP_DATA_LENGTH]); +} + +static int parse_occ_response(struct i2c_client *client, + uint8_t *data, struct occ_response *resp) +{ + int b; + int s; + int ret; + int dnum = SENSOR_BLOCK_OFFSET; + struct occ_sensor *f_sensor; + struct occ_sensor *t_sensor; + struct power_sensor *p_sensor; + struct caps_sensor *c_sensor; + uint8_t sensor_block_num; + uint8_t sensor_type[4]; + uint8_t sensor_format; + uint8_t sensor_length; + uint8_t sensor_num; + + /* check if the data is valid */ + if (strncmp(&data[SENSOR_STR_OFFSET], "SENSOR", 6) != 0) { + dev_dbg(&client->dev, + "ERROR: no SENSOR String in response\n"); + ret = -1; + goto err; + } + + sensor_block_num = data[SENSOR_BLOCK_NUM_OFFSET]; + if (sensor_block_num == 0) { + dev_dbg(&client->dev, "ERROR: SENSOR block num is 0\n"); + ret = -1; + goto err; + } + + /* if sensor block has changed, re-malloc */ + if (sensor_block_num != resp->header.sensor_block_num) { + deinit_occ_resp_buf(resp); + resp->blocks = kcalloc(sensor_block_num, + sizeof(struct sensor_data_block), GFP_KERNEL); + if (!resp->blocks) + return -ENOMEM; + } + + memcpy(&resp->header, &data[RESP_HEADER_OFFSET], sizeof(resp->header)); + resp->header.error_log_addr_start = + be32_to_cpu(resp->header.error_log_addr_start); + resp->header.error_log_length = + be16_to_cpu(resp->header.error_log_length); + + dev_dbg(&client->dev, "Reading %d sensor blocks\n", + resp->header.sensor_block_num); + for (b = 0; b < sensor_block_num; b++) { + /* 8-byte sensor block head */ + strncpy(sensor_type, &data[dnum], 4); + sensor_format = data[dnum+5]; + sensor_length = data[dnum+6]; + sensor_num = data[dnum+7]; + dnum = dnum + 8; + + dev_dbg(&client->dev, + "sensor block[%d]: type: %s, sensor_num: %d\n", + b, sensor_type, sensor_num); + + if (strncmp(sensor_type, "FREQ", 4) == 0) { + ret = occ_renew_sensor(resp, sensor_length, + sensor_num, freq, b); + if (ret) + continue; + + resp->sensor_block_id[freq] = b; + for (s = 0; s < sensor_num; s++) { + f_sensor = &resp->blocks[b].sensor[s]; + f_sensor->sensor_id = + be16_to_cpup((const __be16 *) + &data[dnum]); + f_sensor->value = be16_to_cpup((const __be16 *) + &data[dnum+2]); + dev_dbg(&client->dev, + "sensor[%d]-[%d]: id: %u, value: %u\n", + b, s, f_sensor->sensor_id, + f_sensor->value); + dnum = dnum + sensor_length; + } + } else if (strncmp(sensor_type, "TEMP", 4) == 0) { + ret = occ_renew_sensor(resp, sensor_length, + sensor_num, temp, b); + if (ret) + continue; + + resp->sensor_block_id[temp] = b; + for (s = 0; s < sensor_num; s++) { + t_sensor = &resp->blocks[b].sensor[s]; + t_sensor->sensor_id = + be16_to_cpup((const __be16 *) + &data[dnum]); + t_sensor->value = be16_to_cpup((const __be16 *) + &data[dnum+2]); + dev_dbg(&client->dev, + "sensor[%d]-[%d]: id: %u, value: %u\n", + b, s, t_sensor->sensor_id, + t_sensor->value); + dnum = dnum + sensor_length; + } + } else if (strncmp(sensor_type, "POWR", 4) == 0) { + ret = occ_renew_sensor(resp, sensor_length, + sensor_num, power, b); + if (ret) + continue; + + resp->sensor_block_id[power] = b; + for (s = 0; s < sensor_num; s++) { + p_sensor = &resp->blocks[b].power[s]; + p_sensor->sensor_id = + be16_to_cpup((const __be16 *) + &data[dnum]); + p_sensor->update_tag = + be32_to_cpup((const __be32 *) + &data[dnum+2]); + p_sensor->accumulator = + be32_to_cpup((const __be32 *) + &data[dnum+6]); + p_sensor->value = be16_to_cpup((const __be16 *) + &data[dnum+10]); + + dev_dbg(&client->dev, + "sensor[%d]-[%d]: id: %u, value: %u\n", + b, s, p_sensor->sensor_id, + p_sensor->value); + + dnum = dnum + sensor_length; + } + } else if (strncmp(sensor_type, "CAPS", 4) == 0) { + ret = occ_renew_sensor(resp, sensor_length, + sensor_num, caps, b); + if (ret) + continue; + + resp->sensor_block_id[caps] = b; + for (s = 0; s < sensor_num; s++) { + c_sensor = &resp->blocks[b].caps[s]; + c_sensor->curr_powercap = + be16_to_cpup((const __be16 *) + &data[dnum]); + c_sensor->curr_powerreading = + be16_to_cpup((const __be16 *) + &data[dnum+2]); + c_sensor->norm_powercap = + be16_to_cpup((const __be16 *) + &data[dnum+4]); + c_sensor->max_powercap = + be16_to_cpup((const __be16 *) + &data[dnum+6]); + c_sensor->min_powercap = + be16_to_cpup((const __be16 *) + &data[dnum+8]); + c_sensor->user_powerlimit = + be16_to_cpup((const __be16 *) + &data[dnum+10]); + + dnum = dnum + sensor_length; + dev_dbg(&client->dev, "CAPS sensor #%d:\n", s); + dev_dbg(&client->dev, "curr_powercap is %x\n", + c_sensor->curr_powercap); + dev_dbg(&client->dev, + "curr_powerreading is %x\n", + c_sensor->curr_powerreading); + dev_dbg(&client->dev, "norm_powercap is %x\n", + c_sensor->norm_powercap); + dev_dbg(&client->dev, "max_powercap is %x\n", + c_sensor->max_powercap); + dev_dbg(&client->dev, "min_powercap is %x\n", + c_sensor->min_powercap); + dev_dbg(&client->dev, "user_powerlimit is %x\n", + c_sensor->user_powerlimit); + } + + } else { + dev_dbg(&client->dev, + "ERROR: sensor type %s not supported\n", + resp->blocks[b].sensor_type); + ret = -1; + goto err; + } + + strncpy(resp->blocks[b].sensor_type, sensor_type, 4); + resp->blocks[b].sensor_format = sensor_format; + resp->blocks[b].sensor_length = sensor_length; + resp->blocks[b].sensor_num = sensor_num; + } + + return 0; +err: + deinit_occ_resp_buf(resp); + return ret; +} + + +/* Refer to OCC interface document for OCC command format + * https://github.com/open-power/docs/blob/master/occ/OCC_OpenPwr_FW_Interfaces.pdf + */ +static uint8_t occ_send_cmd(struct i2c_client *client, uint8_t seq, + uint8_t type, uint16_t length, uint8_t *data, uint8_t *resp) +{ + uint32_t cmd1, cmd2; + uint16_t checksum; + int i; + + length = cpu_to_le16(length); + cmd1 = (seq << 24) | (type << 16) | length; + memcpy(&cmd2, data, length); + cmd2 <<= ((4 - length) * 8); + + /* checksum: sum of every bytes of cmd1, cmd2 */ + checksum = 0; + for (i = 0; i < 4; i++) + checksum += (cmd1 >> (i * 8)) & 0xFF; + for (i = 0; i < 4; i++) + checksum += (cmd2 >> (i * 8)) & 0xFF; + cmd2 |= checksum << ((2 - length) * 8); + + /* Init OCB */ + occ_putscom(client, OCB_STATUS_CONTROL_OR, 0x08000000, 0x00000000); + occ_putscom(client, OCB_STATUS_CONTROL_AND, 0xFBFFFFFF, 0xFFFFFFFF); + + /* Send command */ + occ_putscom(client, OCB_ADDRESS, OCC_COMMAND_ADDR, 0x00000000); + occ_putscom(client, OCB_ADDRESS, OCC_COMMAND_ADDR, 0x00000000); + occ_putscom(client, OCB_DATA, cmd1, cmd2); + + /* Trigger attention */ + occ_putscom(client, ATTN_DATA, 0x01010000, 0x00000000); + + /* Get response data */ + occ_putscom(client, OCB_ADDRESS, OCC_RESPONSE_ADDR, 0x00000000); + occ_getscomb(client, OCB_DATA, resp, 0); + + /* return status */ + return resp[2]; +} + +static int occ_get_all(struct i2c_client *client, struct occ_response *occ_resp) +{ + uint8_t *occ_data; + uint16_t num_bytes; + int i; + int ret; + uint8_t poll_cmd_data; + + poll_cmd_data = 0x10; + + /* + * TODO: fetch header, and then allocate the rest of the buffer based + * on the header size. Assuming the OCC has a fixed sized header + */ + occ_data = devm_kzalloc(&client->dev, OCC_DATA_MAX, GFP_KERNEL); + + ret = occ_send_cmd(client, 0, 0, 1, &poll_cmd_data, occ_data); + if (ret) { + dev_err(&client->dev, "ERROR: OCC Poll: 0x%x\n", ret); + ret = -EINVAL; + goto out; + } + + num_bytes = get_occdata_length(occ_data); + + dev_dbg(&client->dev, "OCC data length: %d\n", num_bytes); + + if (num_bytes > OCC_DATA_MAX) { + dev_dbg(&client->dev, "ERROR: OCC data length must be < 4KB\n"); + ret = -EINVAL; + goto out; + } + + if (num_bytes <= 0) { + dev_dbg(&client->dev, "ERROR: OCC data length is zero\n"); + ret = -EINVAL; + goto out; + } + + /* read remaining data */ + for (i = 8; i < num_bytes + 8; i = i + 8) + occ_getscomb(client, OCB_DATA, occ_data, i); + + ret = parse_occ_response(client, occ_data, occ_resp); + +out: + devm_kfree(&client->dev, occ_data); + return ret; +} + + +static int occ_update_device(struct device *dev) +{ + struct occ_drv_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret = 0; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + data->update_interval) + || !data->valid) { + data->valid = 1; + ret = occ_get_all(client, &data->occ_resp); + if (ret) + data->valid = 0; + data->last_updated = jiffies; + } + mutex_unlock(&data->update_lock); + + return ret; +} + + +static void *occ_get_sensor(struct device *hwmon_dev, enum sensor_t t) +{ + struct device *dev = hwmon_dev->parent; + struct occ_drv_data *data = dev_get_drvdata(dev); + int ret; + + ret = occ_update_device(dev); + if (ret != 0) { + dev_dbg(dev, "ERROR: cannot get occ sensor data: %d\n", ret); + return NULL; + } + + return occ_get_sensor_by_type(&data->occ_resp, t); +} + +static int occ_get_sensor_value(struct device *hwmon_dev, enum sensor_t t, + int index) +{ + void *sensor; + + if (t == caps) + return -1; + + sensor = occ_get_sensor(hwmon_dev, t); + + if (!sensor) + return -1; + + if (t == power) + return ((struct power_sensor *)sensor)[index].value; + + return ((struct occ_sensor *)sensor)[index].value; +} + +static int occ_get_sensor_id(struct device *hwmon_dev, enum sensor_t t, + int index) +{ + void *sensor; + + if (t == caps) + return -1; + + sensor = occ_get_sensor(hwmon_dev, t); + + if (!sensor) + return -1; + + if (t == power) + return ((struct power_sensor *)sensor)[index].sensor_id; + + return ((struct occ_sensor *)sensor)[index].sensor_id; +} + +/* sysfs attributes for occ hwmon device */ + +static ssize_t show_input(struct device *hwmon_dev, + struct device_attribute *da, char *buf) +{ + struct sensor_attr_data *sdata = container_of(da, + struct sensor_attr_data, dev_attr); + int val; + + val = occ_get_sensor_value(hwmon_dev, sdata->type, + sdata->hwmon_index - 1); + if (sdata->type == temp) + /* in millidegree Celsius */ + val *= 1000; + + return snprintf(buf, PAGE_SIZE - 1, "%d\n", val); +} + +static ssize_t show_label(struct device *hwmon_dev, + struct device_attribute *da, char *buf) +{ + struct sensor_attr_data *sdata = container_of(da, + struct sensor_attr_data, dev_attr); + int val; + + val = occ_get_sensor_id(hwmon_dev, sdata->type, + sdata->hwmon_index - 1); + + return snprintf(buf, PAGE_SIZE - 1, "%d\n", val); +} + +static ssize_t show_caps(struct device *hwmon_dev, + struct device_attribute *da, char *buf) +{ + struct sensor_attr_data *sdata = container_of(da, + struct sensor_attr_data, dev_attr); + int nr = sdata->attr_id; + int n = sdata->hwmon_index - 1; + struct caps_sensor *sensor; + int val; + + sensor = occ_get_sensor(hwmon_dev, caps); + if (!sensor) { + val = -1; + return snprintf(buf, PAGE_SIZE - 1, "%d\n", val); + } + + switch (nr) { + case 0: + val = sensor[n].curr_powercap; + break; + case 1: + val = sensor[n].curr_powerreading; + break; + case 2: + val = sensor[n].norm_powercap; + break; + case 3: + val = sensor[n].max_powercap; + break; + case 4: + val = sensor[n].min_powercap; + break; + case 5: + val = sensor[n].user_powerlimit; + break; + default: + val = -1; + } + + return snprintf(buf, PAGE_SIZE - 1, "%d\n", val); +} + +static ssize_t show_update_interval(struct device *hwmon_dev, + struct device_attribute *attr, char *buf) +{ + struct device *dev = hwmon_dev->parent; + struct occ_drv_data *data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", + jiffies_to_msecs(data->update_interval)); +} + +static ssize_t set_update_interval(struct device *hwmon_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct device *dev = hwmon_dev->parent; + struct occ_drv_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + data->update_interval = msecs_to_jiffies(val); + return count; +} +static DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO, + show_update_interval, set_update_interval); + +static ssize_t show_name(struct device *hwmon_dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE - 1, "%s\n", OCC_I2C_NAME); +} +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static ssize_t show_user_powercap(struct device *hwmon_dev, + struct device_attribute *attr, char *buf) +{ + struct device *dev = hwmon_dev->parent; + struct occ_drv_data *data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", data->user_powercap); +} + + +static ssize_t set_user_powercap(struct device *hwmon_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct device *dev = hwmon_dev->parent; + struct occ_drv_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + uint16_t val; + uint8_t resp[8]; + int err; + + err = kstrtou16(buf, 10, &val); + if (err) + return err; + + dev_dbg(dev, "set user powercap to: %u\n", val); + val = cpu_to_le16(val); + err = occ_send_cmd(client, 0, 0x22, 2, (uint8_t *)&val, resp); + if (err != 0) { + dev_dbg(dev, + "ERROR: Set User Powercap: wrong return status: %x\n", + err); + if (err == 0x13) + dev_info(dev, + "ERROR: set invalid powercap value: %x\n", val); + return -EINVAL; + } + data->user_powercap = val; + return count; +} +static DEVICE_ATTR(user_powercap, S_IWUSR | S_IRUGO, + show_user_powercap, set_user_powercap); + +static void deinit_sensor_groups(struct device *hwmon_dev, + struct sensor_group *sensor_groups) +{ + int cnt; + + for (cnt = 0; cnt < MAX_OCC_SENSOR_TYPE; cnt++) { + if (sensor_groups[cnt].group.attrs) + devm_kfree(hwmon_dev, sensor_groups[cnt].group.attrs); + if (sensor_groups[cnt].sattr) + devm_kfree(hwmon_dev, sensor_groups[cnt].sattr); + sensor_groups[cnt].group.attrs = NULL; + sensor_groups[cnt].sattr = NULL; + } +} + +static void occ_remove_hwmon_attrs(struct device *hwmon_dev) +{ + struct occ_drv_data *data = dev_get_drvdata(hwmon_dev->parent); + struct sensor_group *sensor_groups = data->sensor_groups; + int i; + + if (!hwmon_dev) + return; + + device_remove_file(hwmon_dev, &dev_attr_update_interval); + device_remove_file(hwmon_dev, &dev_attr_name); + device_remove_file(hwmon_dev, &dev_attr_user_powercap); + + for (i = 0; i < MAX_OCC_SENSOR_TYPE; i++) + sysfs_remove_group(&hwmon_dev->kobj, &sensor_groups[i].group); + + deinit_sensor_groups(hwmon_dev, sensor_groups); +} + +static void sensor_attr_init(struct sensor_attr_data *sdata, + char *sensor_group_name, + char *attr_name, + ssize_t (*show)(struct device *dev, + struct device_attribute *attr, + char *buf)) +{ + sysfs_attr_init(&sdata->dev_attr.attr); + + snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s%d_%s", + sensor_group_name, sdata->hwmon_index, attr_name); + sdata->dev_attr.attr.name = sdata->name; + sdata->dev_attr.attr.mode = S_IRUGO; + sdata->dev_attr.show = show; +} + +/* create hwmon sensor sysfs attributes */ +static int create_sensor_group(struct device *hwmon_dev, enum sensor_t type, + int sensor_num) +{ + struct occ_drv_data *data = dev_get_drvdata(hwmon_dev->parent); + struct sensor_group *sensor_groups = data->sensor_groups; + struct sensor_attr_data *sdata; + int ret; + int cnt; + + /* each sensor has 'label' and 'input' attributes */ + sensor_groups[type].group.attrs = devm_kzalloc(hwmon_dev, + sizeof(struct attribute *) * + sensor_num * 2 + 1, GFP_KERNEL); + if (!sensor_groups[type].group.attrs) { + ret = -ENOMEM; + goto err; + } + + sensor_groups[type].sattr = devm_kzalloc(hwmon_dev, + sizeof(struct sensor_attr_data) * + sensor_num * 2, GFP_KERNEL); + if (!sensor_groups[type].sattr) { + ret = -ENOMEM; + goto err; + } + + for (cnt = 0; cnt < sensor_num; cnt++) { + sdata = &sensor_groups[type].sattr[cnt]; + /* hwomon attributes index starts from 1 */ + sdata->hwmon_index = cnt + 1; + sdata->type = type; + sensor_attr_init(sdata, sensor_groups[type].name, "input", + show_input); + sensor_groups[type].group.attrs[cnt] = &sdata->dev_attr.attr; + + sdata = &sensor_groups[type].sattr[cnt + sensor_num]; + sdata->hwmon_index = cnt + 1; + sdata->type = type; + sensor_attr_init(sdata, sensor_groups[type].name, "label", + show_label); + sensor_groups[type].group.attrs[cnt + sensor_num] = + &sdata->dev_attr.attr; + } + + ret = sysfs_create_group(&hwmon_dev->kobj, &sensor_groups[type].group); + if (ret) + goto err; + + return ret; +err: + deinit_sensor_groups(hwmon_dev, sensor_groups); + return ret; +} + +static void caps_sensor_attr_init(struct sensor_attr_data *sdata, + char *attr_name, uint32_t hwmon_index, + uint32_t attr_id) +{ + sdata->type = caps; + sdata->hwmon_index = hwmon_index; + sdata->attr_id = attr_id; + + /* FIXME, to be compatible with user space app, we do not + * generate caps1_* attributes. + */ + if (sdata->hwmon_index == 1) + snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s_%s", + "caps", attr_name); + else + snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s%d_%s", + "caps", sdata->hwmon_index, attr_name); + + sysfs_attr_init(&sdata->dev_attr.attr); + sdata->dev_attr.attr.name = sdata->name; + sdata->dev_attr.attr.mode = S_IRUGO; + sdata->dev_attr.show = show_caps; +} + +static char *caps_sensor_name[] = { + "curr_powercap", + "curr_powerreading", + "norm_powercap", + "max_powercap", + "min_powercap", + "user_powerlimit", +}; + +static int create_caps_sensor_group(struct device *hwmon_dev, int sensor_num) +{ + struct occ_drv_data *data = dev_get_drvdata(hwmon_dev->parent); + struct sensor_group *sensor_groups = data->sensor_groups; + int field_num = ARRAY_SIZE(caps_sensor_name); + struct sensor_attr_data *sdata; + int ret; + int cnt; + int i; + + sensor_groups[caps].group.attrs = devm_kzalloc(hwmon_dev, + sizeof(struct attribute *) * + sensor_num * field_num + 1, + GFP_KERNEL); + if (!sensor_groups[caps].group.attrs) { + ret = -ENOMEM; + goto err; + } + + sensor_groups[caps].sattr = devm_kzalloc(hwmon_dev, + sizeof(struct sensor_attr_data) * + sensor_num * field_num, + GFP_KERNEL); + if (!sensor_groups[caps].sattr) { + ret = -ENOMEM; + goto err; + } + + for (cnt = 0; cnt < sensor_num; cnt++) { + for (i = 0; i < field_num; i++) { + sdata = &sensor_groups[caps].sattr[cnt * field_num + i]; + caps_sensor_attr_init(sdata, caps_sensor_name[i], + cnt + 1, i); + sensor_groups[caps].group.attrs[cnt * field_num + i] = + &sdata->dev_attr.attr; + } + } + + ret = sysfs_create_group(&hwmon_dev->kobj, &sensor_groups[caps].group); + if (ret) + goto err; + + return ret; +err: + deinit_sensor_groups(hwmon_dev, sensor_groups); + return ret; +} + +static int occ_create_hwmon_attrs(struct device *dev) +{ + struct occ_drv_data *drv_data = dev_get_drvdata(dev); + struct device *hwmon_dev = drv_data->hwmon_dev; + struct sensor_group *sensor_groups = drv_data->sensor_groups; + int i; + int sensor_num; + int ret; + struct occ_response *rsp; + enum sensor_t t; + + rsp = &drv_data->occ_resp; + + for (i = 0; i < ARRAY_SIZE(rsp->sensor_block_id); i++) + rsp->sensor_block_id[i] = -1; + + /* read sensor data from occ. */ + ret = occ_update_device(dev); + if (ret != 0) { + dev_dbg(dev, "ERROR: cannot get occ sensor data: %d\n", ret); + return ret; + } + if (!rsp->blocks) + return -1; + + ret = device_create_file(hwmon_dev, &dev_attr_name); + if (ret) + goto error; + + ret = device_create_file(hwmon_dev, &dev_attr_update_interval); + if (ret) + goto error; + + if (rsp->sensor_block_id[caps] >= 0) { + /* user powercap: only for master OCC */ + ret = device_create_file(hwmon_dev, &dev_attr_user_powercap); + if (ret) + goto error; + } + + sensor_groups[freq].name = "freq"; + sensor_groups[temp].name = "temp"; + sensor_groups[power].name = "power"; + sensor_groups[caps].name = "caps"; + + for (t = 0; t < MAX_OCC_SENSOR_TYPE; t++) { + if (rsp->sensor_block_id[t] < 0) + continue; + + sensor_num = + rsp->blocks[rsp->sensor_block_id[t]].sensor_num; + if (t == caps) + ret = create_caps_sensor_group(hwmon_dev, sensor_num); + else + ret = create_sensor_group(hwmon_dev, t, sensor_num); + if (ret) + goto error; + } + + return 0; +error: + dev_err(dev, "ERROR: cannot create hwmon attributes\n"); + occ_remove_hwmon_attrs(drv_data->hwmon_dev); + return ret; +} + +static ssize_t show_occ_online(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct occ_drv_data *data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE - 1, "%lu\n", data->occ_online); +} + +static ssize_t set_occ_online(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct occ_drv_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val == 1) { + if (data->occ_online == 1) + return count; + + /* populate hwmon sysfs attr using sensor data */ + dev_dbg(dev, "occ register hwmon @0x%x\n", data->client->addr); + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) + return PTR_ERR(data->hwmon_dev); + + err = occ_create_hwmon_attrs(dev); + if (err) { + hwmon_device_unregister(data->hwmon_dev); + return err; + } + data->hwmon_dev->parent = dev; + } else if (val == 0) { + if (data->occ_online == 0) + return count; + + occ_remove_hwmon_attrs(data->hwmon_dev); + hwmon_device_unregister(data->hwmon_dev); + data->hwmon_dev = NULL; + } else + return -EINVAL; + + data->occ_online = val; + return count; +} + +static DEVICE_ATTR(online, S_IWUSR | S_IRUGO, + show_occ_online, set_occ_online); + +static int occ_create_i2c_sysfs_attr(struct device *dev) +{ + /* create an i2c sysfs attribute, to indicate whether OCC is active */ + return device_create_file(dev, &dev_attr_online); +} + + +/* device probe and removal */ + +enum occ_type { + occ_id, +}; + +static int occ_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct occ_drv_data *data; + + data = devm_kzalloc(dev, sizeof(struct occ_drv_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + data->update_interval = HZ; + + occ_create_i2c_sysfs_attr(dev); + + dev_info(dev, "occ i2c driver ready: i2c addr@0x%x\n", client->addr); + + return 0; +} + +static int occ_remove(struct i2c_client *client) +{ + struct occ_drv_data *data = i2c_get_clientdata(client); + + /* free allocated sensor memory */ + deinit_occ_resp_buf(&data->occ_resp); + + device_remove_file(&client->dev, &dev_attr_online); + + if (!data->hwmon_dev) + return 0; + + occ_remove_hwmon_attrs(data->hwmon_dev); + hwmon_device_unregister(data->hwmon_dev); + return 0; +} + +/* used by old-style board info. */ +static const struct i2c_device_id occ_ids[] = { + { OCC_I2C_NAME, occ_id, }, + { /* LIST END */ } +}; +MODULE_DEVICE_TABLE(i2c, occ_ids); + +/* use by device table */ +static const struct of_device_id i2c_occ_of_match[] = { + {.compatible = "ibm,occ-i2c"}, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_occ_of_match); + +/* i2c-core uses i2c-detect() to detect device in bellow address list. + * If exists, address will be assigned to client. + * It is also possible to read address from device table. + */ +static const unsigned short normal_i2c[] = {0x50, 0x51, I2C_CLIENT_END }; + +static struct i2c_driver occ_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = OCC_I2C_NAME, + .pm = NULL, + .of_match_table = i2c_occ_of_match, + }, + .probe = occ_probe, + .remove = occ_remove, + .id_table = occ_ids, + .address_list = normal_i2c, +}; + +module_i2c_driver(occ_driver); + +MODULE_AUTHOR("Li Yi <shliyi@cn.ibm.com>"); +MODULE_DESCRIPTION("BMC OCC hwmon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 7b0aa82ea38b..0a15bf25c534 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -993,6 +993,17 @@ config I2C_RCAR This driver can also be built as a module. If so, the module will be called i2c-rcar. +config I2C_ASPEED + tristate "Aspeed AST2xxx SoC I2C Controller" + depends on (ARCH_ASPEED || COMPILE_TEST) && OF + select I2C_SLAVE + help + If you say yes to this option, support will be included for the + Aspeed AST2xxx SoC I2C controller. + + This driver can also be built as a module. If so, the module + will be called i2c-aspeed. + comment "External I2C/SMBus adapter drivers" config I2C_DIOLAN_U2C diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 37f2819b4560..49631cdf5ea5 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -96,6 +96,7 @@ obj-$(CONFIG_I2C_XILINX) += i2c-xiic.o obj-$(CONFIG_I2C_XLR) += i2c-xlr.o obj-$(CONFIG_I2C_XLP9XX) += i2c-xlp9xx.o obj-$(CONFIG_I2C_RCAR) += i2c-rcar.o +obj-$(CONFIG_I2C_ASPEED) += i2c-aspeed.o # External I2C/SMBus adapter drivers obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c new file mode 100644 index 000000000000..d0d8e7ff803a --- /dev/null +++ b/drivers/i2c/busses/i2c-aspeed.c @@ -0,0 +1,905 @@ +/* + * I2C adapter for the ASPEED I2C bus access. + * + * Copyright (C) 2012-2020 ASPEED Technology Inc. + * Copyright 2015 IBM Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * History: + * 2012.07.26: Initial version [Ryan Chen] + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/completion.h> +#include <linux/slab.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> + +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> + +#include <linux/dma-mapping.h> + +#include <asm/irq.h> +#include <asm/io.h> + +#if defined(CONFIG_COLDFIRE) +#include <asm/arch/regs-iic.h> +#include <asm/arch/ast_i2c.h> +#else +//#include <plat/regs-iic.h> +//#include <plat/ast_i2c.h> +#endif + +#define BYTE_MODE 0 +#define BUFF_MODE 1 +#define DEC_DMA_MODE 2 +#define INC_DMA_MODE 3 + +/* I2C Register */ +#define I2C_FUN_CTRL_REG 0x00 +#define I2C_AC_TIMING_REG1 0x04 +#define I2C_AC_TIMING_REG2 0x08 +#define I2C_INTR_CTRL_REG 0x0c +#define I2C_INTR_STS_REG 0x10 +#define I2C_CMD_REG 0x14 +#define I2C_DEV_ADDR_REG 0x18 +#define I2C_BUF_CTRL_REG 0x1c +#define I2C_BYTE_BUF_REG 0x20 +#define I2C_DMA_BASE_REG 0x24 +#define I2C_DMA_LEN_REG 0x28 + +#define AST_I2C_DMA_SIZE 0 +#define AST_I2C_PAGE_SIZE 256 +#define MASTER_XFER_MODE BUFF_MODE +#define SLAVE_XFER_MODE BYTE_MODE +#define NUM_BUS 14 + +/*AST I2C Register Definition */ +// if defined(AST_SOC_G4) +#define AST_I2C_POOL_BUFF_2048 +#define AST_I2C_GLOBAL_REG 0x00 +#define AST_I2C_DEVICE1 0x40 +#define AST_I2C_DEVICE2 0x80 +#define AST_I2C_DEVICE3 0xc0 +#define AST_I2C_DEVICE4 0x100 +#define AST_I2C_DEVICE5 0x140 +#define AST_I2C_DEVICE6 0x180 +#define AST_I2C_DEVICE7 0x1c0 +#define AST_I2C_BUFFER_POOL2 0x200 +#define AST_I2C_DEVICE8 0x300 +#define AST_I2C_DEVICE9 0x340 +#define AST_I2C_DEVICE10 0x380 +#define AST_I2C_DEVICE11 0x3c0 +#define AST_I2C_DEVICE12 0x400 +#define AST_I2C_DEVICE13 0x440 +#define AST_I2C_DEVICE14 0x480 +#define AST_I2C_BUFFER_POOL1 0x800 + +/* Gloable Register Definition */ +/* 0x00 : I2C Interrupt Status Register */ +/* 0x08 : I2C Interrupt Target Assignment */ + +/* Device Register Definition */ +/* 0x00 : I2CD Function Control Register */ +#define AST_I2CD_BUFF_SEL_MASK (0x7 << 20) +#define AST_I2CD_BUFF_SEL(x) (x << 20) // page 0 ~ 7 +#define AST_I2CD_M_SDA_LOCK_EN (0x1 << 16) +#define AST_I2CD_MULTI_MASTER_DIS (0x1 << 15) +#define AST_I2CD_M_SCL_DRIVE_EN (0x1 << 14) +#define AST_I2CD_MSB_STS (0x1 << 9) +#define AST_I2CD_SDA_DRIVE_1T_EN (0x1 << 8) +#define AST_I2CD_M_SDA_DRIVE_1T_EN (0x1 << 7) +#define AST_I2CD_M_HIGH_SPEED_EN (0x1 << 6) +#define AST_I2CD_DEF_ADDR_EN (0x1 << 5) +#define AST_I2CD_DEF_ALERT_EN (0x1 << 4) +#define AST_I2CD_DEF_ARP_EN (0x1 << 3) +#define AST_I2CD_DEF_GCALL_EN (0x1 << 2) +#define AST_I2CD_SLAVE_EN (0x1 << 1) +#define AST_I2CD_MASTER_EN (0x1 ) + +/* 0x04 : I2CD Clock and AC Timing Control Register #1 */ +#define AST_I2CD_tBUF (0x1 << 28) // 0~7 +#define AST_I2CD_tHDSTA (0x1 << 24) // 0~7 +#define AST_I2CD_tACST (0x1 << 20) // 0~7 +#define AST_I2CD_tCKHIGH (0x1 << 16) // 0~7 +#define AST_I2CD_tCKLOW (0x1 << 12) // 0~7 +#define AST_I2CD_tHDDAT (0x1 << 10) // 0~7 +#define AST_I2CD_CLK_TO_BASE_DIV (0x1 << 8) // 0~3 +#define AST_I2CD_CLK_BASE_DIV (0x1 ) // 0~0xf + +/* 0x08 : I2CD Clock and AC Timing Control Register #2 */ +#define AST_I2CD_tTIMEOUT (0x1 ) // 0~7 +#define AST_NO_TIMEOUT_CTRL 0x0 + + +/* 0x0c : I2CD Interrupt Control Register & + * 0x10 : I2CD Interrupt Status Register + * + * These share bit definitions, so use the same values for the enable & + * status bits. + */ +#define AST_I2CD_INTR_SDA_DL_TIMEOUT (0x1 << 14) +#define AST_I2CD_INTR_BUS_RECOVER_DONE (0x1 << 13) +#define AST_I2CD_INTR_SMBUS_ALERT (0x1 << 12) +#define AST_I2CD_INTR_SMBUS_ARP_ADDR (0x1 << 11) +#define AST_I2CD_INTR_SMBUS_DEV_ALERT_ADDR (0x1 << 10) +#define AST_I2CD_INTR_SMBUS_DEF_ADDR (0x1 << 9) +#define AST_I2CD_INTR_GCALL_ADDR (0x1 << 8) +#define AST_I2CD_INTR_SLAVE_MATCH (0x1 << 7) +#define AST_I2CD_INTR_SCL_TIMEOUT (0x1 << 6) +#define AST_I2CD_INTR_ABNORMAL (0x1 << 5) +#define AST_I2CD_INTR_NORMAL_STOP (0x1 << 4) +#define AST_I2CD_INTR_ARBIT_LOSS (0x1 << 3) +#define AST_I2CD_INTR_RX_DONE (0x1 << 2) +#define AST_I2CD_INTR_TX_NAK (0x1 << 1) +#define AST_I2CD_INTR_TX_ACK (0x1 << 0) + +/* 0x14 : I2CD Command/Status Register */ +#define AST_I2CD_SDA_OE (0x1 << 28) +#define AST_I2CD_SDA_O (0x1 << 27) +#define AST_I2CD_SCL_OE (0x1 << 26) +#define AST_I2CD_SCL_O (0x1 << 25) +#define AST_I2CD_TX_TIMING (0x1 << 24) // 0 ~3 +#define AST_I2CD_TX_STATUS (0x1 << 23) +// Tx State Machine +#define AST_I2CD_IDLE 0x0 +#define AST_I2CD_MACTIVE 0x8 +#define AST_I2CD_MSTART 0x9 +#define AST_I2CD_MSTARTR 0xa +#define AST_I2CD_MSTOP 0xb +#define AST_I2CD_MTXD 0xc +#define AST_I2CD_MRXACK 0xd +#define AST_I2CD_MRXD 0xe +#define AST_I2CD_MTXACK 0xf +#define AST_I2CD_SWAIT 0x1 +#define AST_I2CD_SRXD 0x4 +#define AST_I2CD_STXACK 0x5 +#define AST_I2CD_STXD 0x6 +#define AST_I2CD_SRXACK 0x7 +#define AST_I2CD_RECOVER 0x3 + +#define AST_I2CD_SCL_LINE_STS (0x1 << 18) +#define AST_I2CD_SDA_LINE_STS (0x1 << 17) +#define AST_I2CD_BUS_BUSY_STS (0x1 << 16) +#define AST_I2CD_SDA_OE_OUT_DIR (0x1 << 15) +#define AST_I2CD_SDA_O_OUT_DIR (0x1 << 14) +#define AST_I2CD_SCL_OE_OUT_DIR (0x1 << 13) +#define AST_I2CD_SCL_O_OUT_DIR (0x1 << 12) +#define AST_I2CD_BUS_RECOVER_CMD_EN (0x1 << 11) +#define AST_I2CD_S_ALT_EN (0x1 << 10) +// 0 : DMA Buffer, 1: Pool Buffer +//AST1070 DMA register +#define AST_I2CD_RX_DMA_ENABLE (0x1 << 9) +#define AST_I2CD_TX_DMA_ENABLE (0x1 << 8) + +/* Command Bit */ +#define AST_I2CD_RX_BUFF_ENABLE (0x1 << 7) +#define AST_I2CD_TX_BUFF_ENABLE (0x1 << 6) +#define AST_I2CD_M_STOP_CMD (0x1 << 5) +#define AST_I2CD_M_S_RX_CMD_LAST (0x1 << 4) +#define AST_I2CD_M_RX_CMD (0x1 << 3) +#define AST_I2CD_S_TX_CMD (0x1 << 2) +#define AST_I2CD_M_TX_CMD (0x1 << 1) +#define AST_I2CD_M_START_CMD (0x1 ) + +/* 0x18 : I2CD Slave Device Address Register */ + +/* 0x1C : I2CD Pool Buffer Control Register */ +#define AST_I2CD_RX_BUF_ADDR_GET(x) ((x>> 24)& 0xff) +#define AST_I2CD_RX_BUF_END_ADDR_SET(x) (x << 16) +#define AST_I2CD_TX_DATA_BUF_END_SET(x) ((x&0xff) << 8) +#define AST_I2CD_TX_DATA_BUF_GET(x) ((x >>8) & 0xff) +#define AST_I2CD_BUF_BASE_ADDR_SET(x) (x & 0x3f) + +/* 0x20 : I2CD Transmit/Receive Byte Buffer Register */ +#define AST_I2CD_GET_MODE(x) ((x >> 8) & 0x1) + +#define AST_I2CD_RX_BYTE_BUFFER (0xff << 8) +#define AST_I2CD_TX_BYTE_BUFFER (0xff ) + +//1. usage flag , 2 size, 3. request address +/* Use platform_data instead of module parameters */ +/* Fast Mode = 400 kHz, Standard = 100 kHz */ +//static int clock = 100; /* Default: 100 kHz */ + +/* bitmask of commands that we wait for, in the cmd_pending mask */ +#define AST_I2CD_CMDS (AST_I2CD_BUS_RECOVER_CMD_EN | \ + AST_I2CD_M_STOP_CMD | \ + AST_I2CD_M_RX_CMD | \ + AST_I2CD_M_TX_CMD) + +static const int ast_i2c_n_busses = 14; + +struct ast_i2c_bus { + /* TODO: find a better way to do this */ + struct ast_i2c_dev *i2c_dev; + struct device *dev; + + void __iomem *base; /* virtual */ + u32 state; //I2C xfer mode state matchine + struct i2c_adapter adap; + u32 bus_clk; + struct clk *pclk; + int irq; + + /* i2c transfer state. this is accessed from both process and IRQ + * context, so is protected by cmd_lock */ + spinlock_t cmd_lock; + bool send_start; + bool send_stop; /* last message of an xfer? */ + bool query_len; + struct i2c_msg *msg; /* current tx/rx message */ + int msg_pos; /* current byte position in message */ + + struct completion cmd_complete; + u32 cmd_sent; + u32 cmd_pending; + u32 cmd_err; +}; + +struct ast_i2c_controller { + struct device *dev; + void __iomem *base; + int irq; + struct irq_domain *irq_domain; +}; + +static inline void ast_i2c_write(struct ast_i2c_bus *bus, u32 val, u32 reg) +{ + writel(val, bus->base + reg); +} + +static inline u32 ast_i2c_read(struct ast_i2c_bus *bus, u32 reg) +{ + return readl(bus->base + reg); +} + +static u32 select_i2c_clock(struct ast_i2c_bus *bus) +{ + unsigned int inc = 0, div, divider_ratio; + u32 SCL_Low, SCL_High, data; + + divider_ratio = clk_get_rate(bus->pclk) / bus->bus_clk; + for (div = 0; divider_ratio >= 16; div++) { + inc |= (divider_ratio & 1); + divider_ratio >>= 1; + } + divider_ratio += inc; + SCL_Low = (divider_ratio >> 1) - 1; + SCL_High = divider_ratio - SCL_Low - 2; + data = 0x77700300 | (SCL_High << 16) | (SCL_Low << 12) | div; + return data; +} + +static void ast_i2c_dev_init(struct ast_i2c_bus *bus) +{ + /* reset device: disable master & slave functions */ + ast_i2c_write(bus, 0, I2C_FUN_CTRL_REG); + + dev_dbg(bus->dev, "bus_clk %u, pclk %lu\n", + bus->bus_clk, clk_get_rate(bus->pclk)); + + /* Set AC Timing */ + if(bus->bus_clk / 1000 > 400) { + ast_i2c_write(bus, ast_i2c_read(bus, I2C_FUN_CTRL_REG) | + AST_I2CD_M_HIGH_SPEED_EN | + AST_I2CD_M_SDA_DRIVE_1T_EN | + AST_I2CD_SDA_DRIVE_1T_EN, + I2C_FUN_CTRL_REG); + + ast_i2c_write(bus, 0x3, I2C_AC_TIMING_REG2); + ast_i2c_write(bus, select_i2c_clock(bus), I2C_AC_TIMING_REG1); + } else { + ast_i2c_write(bus, select_i2c_clock(bus), I2C_AC_TIMING_REG1); + ast_i2c_write(bus, AST_NO_TIMEOUT_CTRL, I2C_AC_TIMING_REG2); + } + + dev_dbg(bus->dev, "reg1: %x, reg2: %x, fun_ctrl: %x\n", + ast_i2c_read(bus, I2C_AC_TIMING_REG1), + ast_i2c_read(bus, I2C_AC_TIMING_REG2), + ast_i2c_read(bus, I2C_FUN_CTRL_REG)); + + /* Enable Master Mode */ + ast_i2c_write(bus, ast_i2c_read(bus, I2C_FUN_CTRL_REG) + | AST_I2CD_MASTER_EN, I2C_FUN_CTRL_REG); + + + /* Set interrupt generation of I2C controller */ + ast_i2c_write(bus, AST_I2CD_INTR_SDA_DL_TIMEOUT | + AST_I2CD_INTR_BUS_RECOVER_DONE | + AST_I2CD_INTR_SMBUS_ALERT | + AST_I2CD_INTR_SCL_TIMEOUT | + AST_I2CD_INTR_ABNORMAL | + AST_I2CD_INTR_NORMAL_STOP | + AST_I2CD_INTR_ARBIT_LOSS | + AST_I2CD_INTR_RX_DONE | + AST_I2CD_INTR_TX_NAK | + AST_I2CD_INTR_TX_ACK, + I2C_INTR_CTRL_REG); + +} + +static void ast_i2c_issue_cmd(struct ast_i2c_bus *bus, u32 cmd) +{ + dev_dbg(bus->dev, "issuing cmd: %x\n", cmd); + bus->cmd_err = 0; + bus->cmd_sent = cmd; + bus->cmd_pending = cmd & AST_I2CD_CMDS; + ast_i2c_write(bus, cmd, I2C_CMD_REG); +} + +static int ast_i2c_issue_oob_command(struct ast_i2c_bus *bus, u32 cmd) +{ + spin_lock_irq(&bus->cmd_lock); + init_completion(&bus->cmd_complete); + ast_i2c_issue_cmd(bus, cmd); + spin_unlock_irq(&bus->cmd_lock); + return wait_for_completion_interruptible_timeout(&bus->cmd_complete, + bus->adap.timeout*HZ); +} + +static u8 ast_i2c_bus_error_recover(struct ast_i2c_bus *bus) +{ + u32 sts, i; + int r; + + //Check 0x14's SDA and SCL status + sts = ast_i2c_read(bus,I2C_CMD_REG); + + if ((sts & AST_I2CD_SDA_LINE_STS) && (sts & AST_I2CD_SCL_LINE_STS)) { + //Means bus is idle. + dev_dbg(bus->dev, + "I2C bus is idle. I2C slave doesn't exist?!\n"); + return -1; + } + + dev_dbg(bus->dev, "I2C bus hung (status %x), attempting recovery\n", + sts); + + if ((sts & AST_I2CD_SDA_LINE_STS) && !(sts & AST_I2CD_SCL_LINE_STS)) { + //if SDA == 1 and SCL == 0, it means the master is locking the bus. + //Send a stop command to unlock the bus. + dev_dbg(bus->dev, "I2C's master is locking the bus, try to stop it.\n"); + + init_completion(&bus->cmd_complete); + + ast_i2c_write(bus, AST_I2CD_M_STOP_CMD, I2C_CMD_REG); + + r = wait_for_completion_interruptible_timeout(&bus->cmd_complete, + bus->adap.timeout*HZ); + + if (bus->cmd_err) { + dev_dbg(bus->dev, "recovery error \n"); + return -1; + } + + if (r == 0) { + dev_dbg(bus->dev, "recovery timed out\n"); + return -1; + } else { + dev_dbg(bus->dev, "Recovery successfully\n"); + return 0; + } + + } else if (!(sts & AST_I2CD_SDA_LINE_STS)) { + //else if SDA == 0, the device is dead. We need to reset the bus + //And do the recovery command. + dev_dbg(bus->dev, "I2C's slave is dead, try to recover it\n"); + for (i = 0; i < 2; i++) { + ast_i2c_dev_init(bus); + ast_i2c_issue_oob_command(bus, + AST_I2CD_BUS_RECOVER_CMD_EN); + if (bus->cmd_err != 0) { + dev_dbg(bus->dev, "ERROR!! Failed to do recovery command(0x%08x)\n", bus->cmd_err); + return -1; + } + //Check 0x14's SDA and SCL status + sts = ast_i2c_read(bus,I2C_CMD_REG); + if (sts & AST_I2CD_SDA_LINE_STS) //Recover OK + break; + } + if (i == 2) { + dev_dbg(bus->dev, "ERROR!! recover failed\n"); + return -1; + } + } else { + dev_dbg(bus->dev, "Don't know how to handle this case?!\n"); + return -1; + } + dev_dbg(bus->dev, "Recovery successfully\n"); + return 0; +} + +static int ast_i2c_wait_bus_not_busy(struct ast_i2c_bus *bus) +{ + int timeout = 2; //TODO number + + while (ast_i2c_read(bus, I2C_CMD_REG) & AST_I2CD_BUS_BUSY_STS) { + ast_i2c_bus_error_recover(bus); + if(timeout <= 0) + break; + timeout--; + msleep(2); + } + + return timeout <= 0 ? EAGAIN : 0; +} + +static bool ast_i2c_do_byte_xfer(struct ast_i2c_bus *bus) +{ + u32 cmd, data; + + if (bus->send_start) { + dev_dbg(bus->dev, "%s %c: addr %x start, len %d\n", __func__, + bus->msg->flags & I2C_M_RD ? 'R' : 'W', + bus->msg->addr, bus->msg->len); + + data = bus->msg->addr << 1; + if (bus->msg->flags & I2C_M_RD) + data |= 0x1; + + cmd = AST_I2CD_M_TX_CMD | AST_I2CD_M_START_CMD; + if (bus->send_stop && bus->msg->len == 0) + cmd |= AST_I2CD_M_STOP_CMD; + + ast_i2c_write(bus, data, I2C_BYTE_BUF_REG); + ast_i2c_issue_cmd(bus, cmd); + + } else if (bus->msg_pos < bus->msg->len){ + bool is_last = bus->msg_pos + 1 == bus->msg->len; + + dev_dbg(bus->dev, "%s %c%c: addr %x xfer %d, len %d\n", + __func__, + bus->msg->flags & I2C_M_RD ? 'R' : 'W', + bus->send_stop && is_last ? 'T' : '-', + bus->msg->addr, + bus->msg_pos, bus->msg->len); + + if (bus->msg->flags & I2C_M_RD) { + cmd = AST_I2CD_M_RX_CMD; + if (bus->send_stop && is_last && !bus->query_len) + cmd |= AST_I2CD_M_S_RX_CMD_LAST | + AST_I2CD_M_STOP_CMD; + + } else { + cmd = AST_I2CD_M_TX_CMD; + ast_i2c_write(bus, bus->msg->buf[bus->msg_pos], + I2C_BYTE_BUF_REG); + + if (bus->send_stop && is_last) + cmd |= AST_I2CD_M_STOP_CMD; + } + ast_i2c_issue_cmd(bus, cmd); + + } else { + return false; + } + + return true; +} + +//TX/Rx Done +static void ast_i2c_master_xfer_done(struct ast_i2c_bus *bus) +{ + bool next_msg_queued; + + dev_dbg(bus->dev, "%s xfer %d%c\n", __func__, + bus->msg_pos, + bus->send_start ? 'S' : ' '); + + if (bus->send_start) { + bus->send_start = false; + } else { + + if (bus->msg->flags & I2C_M_RD) { + uint8_t data; + + data = (ast_i2c_read(bus, I2C_BYTE_BUF_REG) & + AST_I2CD_RX_BYTE_BUFFER) >> 8; + + if (bus->query_len) { + bus->msg->len += data; + bus->query_len = false; + dev_dbg(bus->dev, "got rx len: %d\n", + bus->msg->len -1); + } + bus->msg->buf[bus->msg_pos] = data; + } + bus->msg_pos++; + } + + /* queue the next message. If there's none left, we notify the + * waiter */ + next_msg_queued = ast_i2c_do_byte_xfer(bus); + if (!next_msg_queued) + complete(&bus->cmd_complete); +} + +static irqreturn_t ast_i2c_bus_irq(int irq, void *dev_id) +{ + struct ast_i2c_bus *bus = dev_id; + + const u32 errs = AST_I2CD_INTR_ARBIT_LOSS | + AST_I2CD_INTR_ABNORMAL | + AST_I2CD_INTR_SCL_TIMEOUT | + AST_I2CD_INTR_SDA_DL_TIMEOUT | + AST_I2CD_INTR_TX_NAK; + u32 sts, cmd; + + spin_lock(&bus->cmd_lock); + + cmd = ast_i2c_read(bus, I2C_CMD_REG); + sts = ast_i2c_read(bus, I2C_INTR_STS_REG); + + dev_dbg(bus->dev, "irq! status 0x%08x, cmd 0x%08x\n", sts, cmd); + + sts &= 0x7fff; + bus->state = cmd >> 19 & 0xf; + + /* ack everything */ + ast_i2c_write(bus, sts, I2C_INTR_STS_REG); + + bus->cmd_err |= sts & errs; + + /** + * Mask-out pending commands that this interrupt has indicated are + * complete. These checks need to cover all of the possible bits set + * in the AST_I2CD_CMDS bitmask. + */ + if (sts & AST_I2CD_INTR_TX_ACK) + bus->cmd_pending &= ~AST_I2CD_M_TX_CMD; + + if (sts & AST_I2CD_INTR_RX_DONE) + bus->cmd_pending &= ~AST_I2CD_M_RX_CMD; + + if (sts & AST_I2CD_INTR_NORMAL_STOP) + bus->cmd_pending &= ~AST_I2CD_M_STOP_CMD; + + if (sts & AST_I2CD_INTR_BUS_RECOVER_DONE) + bus->cmd_pending &= ~AST_I2CD_BUS_RECOVER_CMD_EN; + + /* if we've seen an error, notify our waiter */ + if (bus->cmd_err) { + complete(&bus->cmd_complete); + + /* still have work to do? We'll wait for the corresponding IRQ(s) for + * that to complete. */ + } else if (bus->cmd_pending) { + dev_dbg(bus->dev, "cmds pending: 0x%x\n", bus->cmd_pending); + + /* message transfer complete */ + } else if (bus->msg) { + ast_i2c_master_xfer_done(bus); + + /* other (non-message) command complete: recovery, error stop. Notify + * waiters. */ + } else if (bus->cmd_sent) { + complete(&bus->cmd_complete); + + } else { + dev_err(bus->dev, "Invalid state (msg %p, pending %x)?", + bus->msg, bus->cmd_pending); + } + + spin_unlock(&bus->cmd_lock); + + return IRQ_HANDLED; +} + +static int ast_i2c_do_msgs_xfer(struct ast_i2c_bus *bus, + struct i2c_msg *msgs, int num) +{ + unsigned long flags; + int i, ret = 0; + u32 err, cmd; + + for (i = 0; i < num; i++) { + + spin_lock_irqsave(&bus->cmd_lock, flags); + bus->msg = &msgs[i]; + bus->msg_pos = 0; + bus->query_len = bus->msg->flags & I2C_M_RECV_LEN; + bus->send_start = !(bus->msg->flags & I2C_M_NOSTART); + bus->send_stop = !!(num == i+1); + init_completion(&bus->cmd_complete); + + ast_i2c_do_byte_xfer(bus); + spin_unlock_irqrestore(&bus->cmd_lock, flags); + + ret = wait_for_completion_interruptible_timeout( + &bus->cmd_complete, + bus->adap.timeout * HZ); + + spin_lock_irqsave(&bus->cmd_lock, flags); + err = bus->cmd_err; + cmd = bus->cmd_sent; + bus->cmd_sent = 0; + bus->msg = NULL; + spin_unlock_irqrestore(&bus->cmd_lock, flags); + + if (!ret) { + dev_dbg(bus->dev, "controller timed out\n"); + return -EIO; + } + + if (err != 0) { + if (cmd & AST_I2CD_M_STOP_CMD) { + return -ETIMEDOUT; + } else { + dev_dbg(bus->dev, "send stop\n"); + ast_i2c_issue_oob_command(bus, + AST_I2CD_M_STOP_CMD); + return -EAGAIN; + } + } + } + + return num; +} + +static int ast_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct ast_i2c_bus *bus = adap->algo_data; + int ret, i; + int sts; + + sts = ast_i2c_read(bus, I2C_CMD_REG); + dev_dbg(bus->dev, "state[%x], SCL[%d], SDA[%d], BUS[%d]\n", + (sts >> 19) & 0xf, + (sts >> 18) & 0x1, + (sts >> 17) & 0x1, + (sts >> 16) & 1); + /* + * Wait for the bus to become free. + */ + + ret = ast_i2c_wait_bus_not_busy(bus); + if (ret) { + dev_err(&adap->dev, "i2c_ast: timeout waiting for bus free\n"); + goto out; + } + + for (i = adap->retries; i >= 0; i--) { + if (i != 0) + dev_dbg(&adap->dev, "Do retrying transmission [%d]\n",i); + + ret = ast_i2c_do_msgs_xfer(bus, msgs, num); + if (ret != -EAGAIN) + goto out; + + udelay(100); + } + + ret = -EREMOTEIO; +out: + + return ret; +} + +static u32 ast_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm i2c_ast_algorithm = { + .master_xfer = ast_i2c_xfer, + .functionality = ast_i2c_functionality, +}; + +static int ast_i2c_probe_bus(struct platform_device *pdev) +{ + struct ast_i2c_bus *bus; + struct resource *res; + int ret, bus_num; + + bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); + if (!bus) + return -ENOMEM; + + ret = of_property_read_u32(pdev->dev.of_node, "bus", &bus_num); + if (ret) + return -ENXIO; + + bus->pclk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(bus->pclk)) { + dev_err(&pdev->dev, "clk_get failed\n"); + return PTR_ERR(bus->pclk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + bus->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(bus->base)) + return PTR_ERR(bus->base); + + bus->irq = platform_get_irq(pdev, 0); + if (bus->irq < 0) { + dev_err(&pdev->dev, "platform_get_irq failed\n"); + return -ENXIO; + } + + ret = devm_request_irq(&pdev->dev, bus->irq, ast_i2c_bus_irq, + 0, dev_name(&pdev->dev), bus); + if (ret) { + dev_err(&pdev->dev, "devm_request_irq failed\n"); + return -ENXIO; + } + + /* Initialize the I2C adapter */ + spin_lock_init(&bus->cmd_lock); + bus->adap.nr = bus_num; + bus->adap.owner = THIS_MODULE; + bus->adap.retries = 0; + bus->adap.timeout = 5; + bus->adap.algo = &i2c_ast_algorithm; + bus->adap.algo_data = bus; + bus->adap.dev.parent = &pdev->dev; + bus->adap.dev.of_node = pdev->dev.of_node; + snprintf(bus->adap.name, sizeof(bus->adap.name), "Aspeed i2c-%d", + bus_num); + + bus->dev = &pdev->dev; + + ret = of_property_read_u32(pdev->dev.of_node, + "clock-frequency", &bus->bus_clk); + if (ret < 0) { + dev_err(&pdev->dev, + "Could not read clock-frequency property\n"); + bus->bus_clk = 100000; + } + + ast_i2c_dev_init(bus); + + ret = i2c_add_numbered_adapter(&bus->adap); + if (ret < 0) + return -ENXIO; + + dev_info(bus->dev, "i2c bus %d registered, irq %d\n", + bus->adap.nr, bus->irq); + + return 0; +} + +static void noop(struct irq_data *data) { } + +static struct irq_chip ast_i2c_irqchip = { + .name = "ast-i2c", + .irq_unmask = noop, + .irq_mask = noop, +}; + +static void ast_i2c_controller_irq(struct irq_desc *desc) +{ + struct ast_i2c_controller *c = irq_desc_get_handler_data(desc); + unsigned long p, status; + unsigned int bus_irq; + + status = readl(c->base); + for_each_set_bit(p, &status, ast_i2c_n_busses) { + bus_irq = irq_find_mapping(c->irq_domain, p); + generic_handle_irq(bus_irq); + } +} + +static int ast_i2c_probe_controller(struct platform_device *pdev) +{ + struct ast_i2c_controller *controller; + struct device_node *np; + struct resource *res; + int i, irq; + + controller = kzalloc(sizeof(*controller), GFP_KERNEL); + if (!controller) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + controller->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(controller->base)) + return PTR_ERR(controller->base); + + controller->irq = platform_get_irq(pdev, 0); + if (controller->irq < 0) { + dev_err(&pdev->dev, "no platform IRQ\n"); + return -ENXIO; + } + + controller->irq_domain = irq_domain_add_linear(pdev->dev.of_node, + ast_i2c_n_busses, &irq_domain_simple_ops, NULL); + if (!controller->irq_domain) { + dev_err(&pdev->dev, "no IRQ domain\n"); + return -ENXIO; + } + controller->irq_domain->name = "ast-i2c-domain"; + + for (i = 0; i < ast_i2c_n_busses; i++) { + irq = irq_create_mapping(controller->irq_domain, i); + irq_set_chip_data(irq, controller); + irq_set_chip_and_handler(irq, &ast_i2c_irqchip, + handle_simple_irq); + } + + irq_set_chained_handler_and_data(controller->irq, + ast_i2c_controller_irq, controller); + + controller->dev = &pdev->dev; + + platform_set_drvdata(pdev, controller); + + dev_info(controller->dev, "i2c controller registered, irq %d\n", + controller->irq); + + for_each_child_of_node(pdev->dev.of_node, np) { + int ret; + u32 bus_num; + char bus_id[sizeof("i2c-12345")]; + + /* + * Set a useful name derived from the bus number; the device + * tree should provide us with one that corresponds to the + * hardware numbering. If the property is missing the + * probe would fail so just skip it here. + */ + + ret = of_property_read_u32(np, "bus", &bus_num); + if (ret) + continue; + + ret = snprintf(bus_id, sizeof(bus_id), "i2c-%u", bus_num); + if (ret >= sizeof(bus_id)) + continue; + + of_platform_device_create(np, bus_id, &pdev->dev); + of_node_put(np); + } + + return 0; +} + +static int ast_i2c_probe(struct platform_device *pdev) +{ + if (of_device_is_compatible(pdev->dev.of_node, + "aspeed,ast2400-i2c-controller")) + return ast_i2c_probe_controller(pdev); + + if (of_device_is_compatible(pdev->dev.of_node, + "aspeed,ast2400-i2c-bus")) + return ast_i2c_probe_bus(pdev); + + return -ENODEV; +} + +static const struct of_device_id ast_i2c_of_table[] = { + { .compatible = "aspeed,ast2400-i2c-controller", }, + { .compatible = "aspeed,ast2400-i2c-bus", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ast_i2c_of_table); + +static struct platform_driver i2c_ast_driver = { + .probe = ast_i2c_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ast_i2c_of_table, + }, +}; + +module_platform_driver(i2c_ast_driver); + +MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>"); +MODULE_DESCRIPTION("ASPEED AST I2C Bus Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ast_i2c"); diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index d625167357cc..58dee76cacc4 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -537,7 +537,6 @@ static int i2c_device_uevent(struct device *dev, struct kobj_uevent_env *env) if (add_uevent_var(env, "MODALIAS=%s%s", I2C_MODULE_PREFIX, client->name)) return -ENOMEM; - dev_dbg(dev, "uevent\n"); return 0; } diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 177f78f6e6d6..b3aa8940c823 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -55,3 +55,4 @@ obj-$(CONFIG_RENESAS_H8S_INTC) += irq-renesas-h8s.o obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o +obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o diff --git a/drivers/irqchip/irq-aspeed-vic.c b/drivers/irqchip/irq-aspeed-vic.c new file mode 100644 index 000000000000..ce029cc75177 --- /dev/null +++ b/drivers/irqchip/irq-aspeed-vic.c @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2015 - Ben Herrenschmidt, IBM Corp. + * + * Driver for Aspeed "new" VIC as found in SoC generation 3 and later + * + * Based on irq-vic.c: + * + * Copyright (C) 1999 - 2003 ARM Limited + * Copyright (C) 2000 Deep Blue Solutions Ltd + * + * 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/export.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irqdomain.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/syscore_ops.h> +#include <linux/device.h> +#include <linux/slab.h> + +#include <asm/exception.h> +#include <asm/irq.h> + + +//#define DBG(fmt...) do { printk("AVIC " fmt); } while(0) +#define DBG(fmt...) do { } while(0) + +/* These definitions correspond to the "new mapping" of the + * register set that interleaves "high" and "low". The offsets + * below are for the "low" register, add 4 to get to the high one + */ +#define AVIC_IRQ_STATUS 0x00 +#define AVIC_FIQ_STATUS 0x08 +#define AVIC_RAW_STATUS 0x10 +#define AVIC_INT_SELECT 0x18 +#define AVIC_INT_ENABLE 0x20 +#define AVIC_INT_ENABLE_CLR 0x28 +#define AVIC_INT_TRIGGER 0x30 +#define AVIC_INT_TRIGGER_CLR 0x38 +#define AVIC_INT_SENSE 0x40 +#define AVIC_INT_DUAL_EDGE 0x48 +#define AVIC_INT_EVENT 0x50 +#define AVIC_EDGE_CLR 0x58 +#define AVIC_EDGE_STATUS 0x60 + +struct aspeed_vic { + void __iomem *base; + u32 valid_sources[2]; + u32 edge_sources[2]; + struct irq_domain *dom; +}; +static struct aspeed_vic *system_avic; + +static void vic_init_hw(struct aspeed_vic *vic) +{ + u32 sense; + + /* Disable all interrupts */ + writel(0xffffffff, vic->base + AVIC_INT_ENABLE_CLR); + writel(0xffffffff, vic->base + AVIC_INT_ENABLE_CLR + 4); + + /* Make sure no soft trigger is on */ + writel(0xffffffff, vic->base + AVIC_INT_TRIGGER_CLR); + writel(0xffffffff, vic->base + AVIC_INT_TRIGGER_CLR + 4); + + /* Set everything to be IRQ */ + writel(0, vic->base + AVIC_INT_SELECT); + writel(0, vic->base + AVIC_INT_SELECT + 4); + + /* Some interrupts have a programable high/low level trigger + * (4 GPIO direct inputs), for now we assume this was configured + * by firmware. We read which ones are edge now. + */ + sense = readl(vic->base + AVIC_INT_SENSE); + vic->edge_sources[0] = ~sense; + sense = readl(vic->base + AVIC_INT_SENSE + 4); + vic->edge_sources[1] = ~sense; + + /* Clear edge detection latches */ + writel(0xffffffff, vic->base + AVIC_EDGE_CLR); + writel(0xffffffff, vic->base + AVIC_EDGE_CLR + 4); +} + +static void __exception_irq_entry avic_handle_irq(struct pt_regs *regs) +{ + struct aspeed_vic *vic = system_avic; + u32 stat, irq; + u32 loops = 0; + + /* We handle interrupts in a loop, is that necessary ? TBD */ + for (;;) { + irq = 0; + stat = readl_relaxed(vic->base + AVIC_IRQ_STATUS); + if (!stat) { + stat = readl_relaxed(vic->base + AVIC_IRQ_STATUS + 4); + irq = 32; + } + if (stat == 0) + break; + irq += ffs(stat) - 1; + if (irq != 16) + DBG("irq=%d\n", irq); + handle_domain_irq(vic->dom, irq, regs); + loops++; + } + if (loops == 0) + DBG("S!\n"); +} + +static void avic_ack_irq(struct irq_data *d) +{ + struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); + unsigned int sidx = d->hwirq >> 5; + unsigned int sbit = 1u << (d->hwirq & 0x1f); + + if (d->hwirq != 16) + DBG("ACK %ld\n", d->hwirq); + /* Clear edge latch for edge interrupts, nop for level */ + if (vic->edge_sources[sidx] & sbit) + writel(sbit, vic->base + AVIC_EDGE_CLR + sidx * 4); +} + +static void avic_mask_irq(struct irq_data *d) +{ + struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); + unsigned int sidx = d->hwirq >> 5; + unsigned int sbit = 1u << (d->hwirq & 0x1f); + + if (d->hwirq != 16) + DBG("MASK %ld\n", d->hwirq); + writel(sbit, vic->base + AVIC_INT_ENABLE_CLR + sidx * 4); +} + +static void avic_unmask_irq(struct irq_data *d) +{ + struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); + unsigned int sidx = d->hwirq >> 5; + unsigned int sbit = 1u << (d->hwirq & 0x1f); + + if (d->hwirq != 16) + DBG("UNMASK %ld\n", d->hwirq); + writel(sbit, vic->base + AVIC_INT_ENABLE + sidx * 4); +} + +/* For level irq, faster than going through a nop "ack" and mask */ +static void avic_mask_ack_irq(struct irq_data *d) +{ + struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); + unsigned int sidx = d->hwirq >> 5; + unsigned int sbit = 1u << (d->hwirq & 0x1f); + + if (d->hwirq != 16) + DBG("MASK_ACK %ld\n", d->hwirq); + + /* First mask */ + writel(sbit, vic->base + AVIC_INT_ENABLE_CLR + sidx * 4); + + /* Then clear edge latch for edge interrupts */ + if (vic->edge_sources[sidx] & sbit) + writel(sbit, vic->base + AVIC_EDGE_CLR + sidx * 4); +} + +static struct irq_chip avic_chip = { + .name = "AVIC", + .irq_ack = avic_ack_irq, + .irq_mask = avic_mask_irq, + .irq_unmask = avic_unmask_irq, + .irq_mask_ack = avic_mask_ack_irq, +}; + +static int avic_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct aspeed_vic *vic = d->host_data; + unsigned int sidx = hwirq >> 5; + unsigned int sbit = 1u << (hwirq & 0x1f); + + /* Check if interrupt exists */ + if (sidx > 1 || !(vic->valid_sources[sidx] & sbit)) + return -EPERM; + DBG("MAP %d edge %d\n", hwirq, !!(vic->edge_sources[sidx] & sbit)); + if (vic->edge_sources[sidx] & sbit) + irq_set_chip_and_handler(irq, &avic_chip, handle_edge_irq); + else + irq_set_chip_and_handler(irq, &avic_chip, handle_level_irq); + irq_set_chip_data(irq, vic); + irq_set_probe(irq); + return 0; +} + +static struct irq_domain_ops avic_dom_ops = { + .map = avic_map, + .xlate = irq_domain_xlate_onetwocell, +}; + +static int __init avic_of_init(struct device_node *node, + struct device_node *parent) +{ + void __iomem *regs; + struct aspeed_vic *vic; + + if (WARN(parent, "non-root Aspeed VIC not supported")) + return -EINVAL; + if (WARN(system_avic, "duplicate Aspeed VIC not supported")) + return -EINVAL; + + regs = of_iomap(node, 0); + if (WARN_ON(!regs)) + return -EIO; + + vic = kzalloc(sizeof(struct aspeed_vic), GFP_KERNEL); + if (WARN_ON(!vic)) { + iounmap(regs); + return -ENOMEM; + } + vic->base = regs; + + of_property_read_u32_index(node, "valid-sources", 0, + &vic->valid_sources[0]); + of_property_read_u32_index(node, "valid-sources", 1, + &vic->valid_sources[1]); + + /* Initialize soures, all masked */ + vic_init_hw(vic); + + /* Ready to receive interrupts */ + system_avic = vic; + set_handle_irq(avic_handle_irq); + + /* Register our domain. XXX Count valid sources */ + vic->dom = irq_domain_add_simple(node, 64, 0, + &avic_dom_ops, vic); + + pr_info("Aspeed VIC Initiallized\n"); + + return 0; +} + +IRQCHIP_DECLARE(aspeed_new_vic, "aspeed,new-vic", avic_of_init); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 4bf7d50b1bc7..d09106d0b5d5 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -525,6 +525,11 @@ config VEXPRESS_SYSCFG bus. System Configuration interface is one of the possible means of generating transactions on this bus. +config ASPEED_BT_IPMI_HOST + tristate "BT IPMI host driver" + help + Support for the Aspeed BT ipmi host. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 537d7f3b78da..019bb2601e02 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -56,3 +56,4 @@ obj-$(CONFIG_GENWQE) += genwqe/ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ +obj-$(CONFIG_ASPEED_BT_IPMI_HOST) += bt-host.o diff --git a/drivers/misc/bt-host.c b/drivers/misc/bt-host.c new file mode 100644 index 000000000000..105d3fc664a4 --- /dev/null +++ b/drivers/misc/bt-host.c @@ -0,0 +1,420 @@ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/errno.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/miscdevice.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/bt-host.h> + +#define DEVICE_NAME "bt-host" + +#define BT_IO_BASE 0xe4 +#define BT_IRQ 10 + +#define BT_CR0 0x0 +#define BT_CR0_IO_BASE 16 +#define BT_CR0_IRQ 12 +#define BT_CR0_EN_CLR_SLV_RDP 0x8 +#define BT_CR0_EN_CLR_SLV_WRP 0x4 +#define BT_CR0_ENABLE_IBT 0x1 +#define BT_CR1 0x4 +#define BT_CR1_IRQ_H2B 0x01 +#define BT_CR1_IRQ_HBUSY 0x40 +#define BT_CR2 0x8 +#define BT_CR2_IRQ_H2B 0x01 +#define BT_CR2_IRQ_HBUSY 0x40 +#define BT_CR3 0xc +#define BT_CTRL 0x10 +#define BT_CTRL_B_BUSY 0x80 +#define BT_CTRL_H_BUSY 0x40 +#define BT_CTRL_OEM0 0x20 +#define BT_CTRL_SMS_ATN 0x10 +#define BT_CTRL_B2H_ATN 0x08 +#define BT_CTRL_H2B_ATN 0x04 +#define BT_CTRL_CLR_RD_PTR 0x02 +#define BT_CTRL_CLR_WR_PTR 0x01 +#define BT_BMC2HOST 0x14 +#define BT_INTMASK 0x18 +#define BT_INTMASK_B2H_IRQEN 0x01 +#define BT_INTMASK_B2H_IRQ 0x02 +#define BT_INTMASK_BMC_HWRST 0x80 + +struct bt_host { + struct device dev; + struct miscdevice miscdev; + void *base; + int open_count; + int irq; + wait_queue_head_t queue; + struct timer_list poll_timer; +}; + +static u8 bt_inb(struct bt_host *bt_host, int reg) +{ + return ioread8(bt_host->base + reg); +} + +static void bt_outb(struct bt_host *bt_host, u8 data, int reg) +{ + iowrite8(data, bt_host->base + reg); +} + +static void clr_rd_ptr(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_CLR_RD_PTR, BT_CTRL); +} + +static void clr_wr_ptr(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_CLR_WR_PTR, BT_CTRL); +} + +static void clr_h2b_atn(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_H2B_ATN, BT_CTRL); +} + +static void set_b_busy(struct bt_host *bt_host) +{ + if (!(bt_inb(bt_host, BT_CTRL) & BT_CTRL_B_BUSY)) + bt_outb(bt_host, BT_CTRL_B_BUSY, BT_CTRL); +} + +static void clr_b_busy(struct bt_host *bt_host) +{ + if (bt_inb(bt_host, BT_CTRL) & BT_CTRL_B_BUSY) + bt_outb(bt_host, BT_CTRL_B_BUSY, BT_CTRL); +} + +static void set_b2h_atn(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_B2H_ATN, BT_CTRL); +} + +static u8 bt_read(struct bt_host *bt_host) +{ + return bt_inb(bt_host, BT_BMC2HOST); +} + +static void bt_write(struct bt_host *bt_host, u8 c) +{ + bt_outb(bt_host, c, BT_BMC2HOST); +} + +static void set_sms_atn(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_SMS_ATN, BT_CTRL); +} + +static struct bt_host *file_bt_host(struct file *file) +{ + return container_of(file->private_data, struct bt_host, miscdev); +} + +static int bt_host_open(struct inode *inode, struct file *file) +{ + struct bt_host *bt_host = file_bt_host(file); + + clr_b_busy(bt_host); + + return 0; +} + +static ssize_t bt_host_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct bt_host *bt_host = file_bt_host(file); + char __user *p = buf; + u8 len; + + if (!access_ok(VERIFY_WRITE, buf, count)) + return -EFAULT; + + WARN_ON(*ppos); + + if (wait_event_interruptible(bt_host->queue, + bt_inb(bt_host, BT_CTRL) & BT_CTRL_H2B_ATN)) + return -ERESTARTSYS; + + set_b_busy(bt_host); + clr_h2b_atn(bt_host); + clr_rd_ptr(bt_host); + + len = bt_read(bt_host); + __put_user(len, p++); + + /* We pass the length back as well */ + if (len + 1 > count) + len = count - 1; + + while(len) { + if (__put_user(bt_read(bt_host), p)) + return -EFAULT; + len--; p++; + } + + clr_b_busy(bt_host); + + return p - buf; +} + +static ssize_t bt_host_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct bt_host *bt_host = file_bt_host(file); + const char __user *p = buf; + u8 c; + + if (!access_ok(VERIFY_READ, buf, count)) + return -EFAULT; + + WARN_ON(*ppos); + + /* There's no interrupt for clearing host busy so we have to + * poll */ + if (wait_event_interruptible(bt_host->queue, + !(bt_inb(bt_host, BT_CTRL) & + (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN)))) + return -ERESTARTSYS; + + clr_wr_ptr(bt_host); + + while (count) { + if (__get_user(c, p)) + return -EFAULT; + + bt_write(bt_host, c); + count--; p++; + } + + set_b2h_atn(bt_host); + + return p - buf; +} + +static long bt_host_ioctl(struct file *file, unsigned int cmd, + unsigned long param) +{ + struct bt_host *bt_host = file_bt_host(file); + switch (cmd) { + case BT_HOST_IOCTL_SMS_ATN: + set_sms_atn(bt_host); + return 0; + } + return -EINVAL; +} + +static int bt_host_release(struct inode *inode, struct file *file) +{ + struct bt_host *bt_host = file_bt_host(file); + set_b_busy(bt_host); + return 0; +} + +static unsigned int bt_host_poll(struct file *file, poll_table *wait) +{ + struct bt_host *bt_host = file_bt_host(file); + unsigned int mask = 0; + uint8_t ctrl; + + poll_wait(file, &bt_host->queue, wait); + + ctrl = bt_inb(bt_host, BT_CTRL); + + if (ctrl & BT_CTRL_H2B_ATN) + mask |= POLLIN; + + if (!(ctrl & (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN))) + mask |= POLLOUT; + + return mask; +} + +static const struct file_operations bt_host_fops = { + .owner = THIS_MODULE, + .open = bt_host_open, + .read = bt_host_read, + .write = bt_host_write, + .release = bt_host_release, + .poll = bt_host_poll, + .unlocked_ioctl = bt_host_ioctl, +}; + +static void poll_timer(unsigned long data) +{ + struct bt_host *bt_host = (void *)data; + bt_host->poll_timer.expires += msecs_to_jiffies(500); + wake_up(&bt_host->queue); + add_timer(&bt_host->poll_timer); +} + +irqreturn_t bt_host_irq(int irq, void *arg) +{ + struct bt_host *bt_host = arg; + uint32_t reg; + + reg = ioread32(bt_host->base + BT_CR2); + reg &= BT_CR2_IRQ_H2B | BT_CR2_IRQ_HBUSY; + if (!reg) + return IRQ_NONE; + + /* ack pending IRQs */ + iowrite32(reg, bt_host->base + BT_CR2); + + wake_up(&bt_host->queue); + return IRQ_HANDLED; +} + +static int bt_host_config_irq(struct bt_host *bt_host, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + uint32_t reg; + int rc; + + bt_host->irq = irq_of_parse_and_map(dev->of_node, 0); + if (!bt_host->irq) + return -ENODEV; + + rc = devm_request_irq(dev, bt_host->irq, bt_host_irq, IRQF_SHARED, + DEVICE_NAME, bt_host); + if (rc < 0) { + dev_warn(dev, "Unable to request IRQ %d\n", bt_host->irq); + bt_host->irq = 0; + return rc; + } + + /* Configure IRQs on the host clearing the H2B and HBUSY bits; + * H2B will be asserted when the host has data for us; HBUSY + * will be cleared (along with B2H) when we can write the next + * message to the BT buffer */ + reg = ioread32(bt_host->base + BT_CR1); + reg |= BT_CR1_IRQ_H2B | BT_CR1_IRQ_HBUSY; + iowrite32(reg, bt_host->base + BT_CR1); + + return 0; +} + +static int bt_host_probe(struct platform_device *pdev) +{ + struct bt_host *bt_host; + struct device *dev; + struct resource *res; + int rc; + + if (!pdev || !pdev->dev.of_node) + return -ENODEV; + + dev = &pdev->dev; + dev_info(dev, "Found bt host device\n"); + + bt_host = devm_kzalloc(dev, sizeof(*bt_host), GFP_KERNEL); + if (!bt_host) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, bt_host); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "Unable to find resources\n"); + rc = -ENXIO; + goto out_free; + } + + bt_host->base = devm_ioremap_resource(&pdev->dev, res); + if (!bt_host->base) { + rc = -ENOMEM; + goto out_free; + } + + init_waitqueue_head(&bt_host->queue); + + bt_host->miscdev.minor = MISC_DYNAMIC_MINOR, + bt_host->miscdev.name = DEVICE_NAME, + bt_host->miscdev.fops = &bt_host_fops, + bt_host->miscdev.parent = dev; + rc = misc_register(&bt_host->miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); + goto out_unmap; + } + + bt_host_config_irq(bt_host, pdev); + + if (bt_host->irq) { + dev_info(dev, "Using IRQ %d\n", bt_host->irq); + } else { + dev_info(dev, "No IRQ; using timer\n"); + init_timer(&bt_host->poll_timer); + bt_host->poll_timer.function = poll_timer; + bt_host->poll_timer.data = (unsigned long)bt_host; + bt_host->poll_timer.expires = jiffies + msecs_to_jiffies(10); + add_timer(&bt_host->poll_timer); + } + + iowrite32((BT_IO_BASE << BT_CR0_IO_BASE) | + (BT_IRQ << BT_CR0_IRQ) | + BT_CR0_EN_CLR_SLV_RDP | + BT_CR0_EN_CLR_SLV_WRP | + BT_CR0_ENABLE_IBT, + bt_host->base + BT_CR0); + + clr_b_busy(bt_host); + + return 0; + +out_unmap: + devm_iounmap(&pdev->dev, bt_host->base); + +out_free: + devm_kfree(dev, bt_host); + return rc; + +} + +static int bt_host_remove(struct platform_device *pdev) +{ + struct bt_host *bt_host = dev_get_drvdata(&pdev->dev); + misc_deregister(&bt_host->miscdev); + if (!bt_host->irq) + del_timer_sync(&bt_host->poll_timer); + devm_iounmap(&pdev->dev, bt_host->base); + devm_kfree(&pdev->dev, bt_host); + bt_host = NULL; + + return 0; +} + +static const struct of_device_id bt_host_match[] = { + { .compatible = "aspeed,bt-host" }, + { }, +}; + +static struct platform_driver bt_host_driver = { + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, + .of_match_table = bt_host_match, + }, + .probe = bt_host_probe, + .remove = bt_host_remove, +}; + +module_platform_driver(bt_host_driver); + +MODULE_DEVICE_TABLE(of, bt_host_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alistair Popple <alistair@popple.id.au>"); +MODULE_DESCRIPTION("Linux device interface to the BT interface"); diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index 2fe2a7e90fa9..4a4465465d60 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -41,4 +41,15 @@ config SPI_NXP_SPIFI Flash. Enable this option if you have a device with a SPIFI controller and want to access the Flash as a mtd device. +config ASPEED_FLASH_SPI + tristate "Aspeed flash controllers in SPI mode" + depends on HAS_IOMEM && OF + depends on ARCH_ASPEED || COMPILE_TEST + # IO_SPACE_LIMIT must be equivalent to (~0UL) + depends on !NEED_MACH_IO_H + help + This enables support for the New Static Memory Controller (FMC) + in the AST2400 when attached to SPI nor chips, and support for + the SPI Memory controller (SPI) for the BIOS. + endif # MTD_SPI_NOR diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index e53333ef8582..ec286105333b 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o +obj-$(CONFIG_ASPEED_FLASH_SPI) += aspeed-smc.o obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o diff --git a/drivers/mtd/spi-nor/aspeed-smc.c b/drivers/mtd/spi-nor/aspeed-smc.c new file mode 100644 index 000000000000..ed55f283b619 --- /dev/null +++ b/drivers/mtd/spi-nor/aspeed-smc.c @@ -0,0 +1,563 @@ +/* + * ASPEED Static Memory Controller driver + * Copyright 2016 IBM Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +/* See comment by aspeed_smc_from_fifo */ +#ifdef CONFIG_ARM +#define IO_SPACE_LIMIT (~0UL) +#endif + +#include <linux/bug.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/spi-nor.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/sysfs.h> + +/* + * On the arm architecture, as of Linux version 4.3, memcpy_fromio + * stutters discarding some of the bytes read if the destination is + * unaligned, so we can't use it for reading from a fifo to a buffer + * of unknown alignment. Instead use the ins (l, w, b) family + * to read from the fifo. However, ARM tries to hide io port + * accesses from drivers unless there is a PCMCIA or PCI device, so + * we define the limit before all include files. There is a + * check in probe to make sure this will work, as long as the + * architecture uses an identity iomap. + */ + +static void aspeed_smc_from_fifo(void *buf, const void __iomem *iop, size_t len) +{ + unsigned long io = (__force unsigned long)iop; + + if (!len) + return; + + /* Expect a 4 byte input port. Otherwise just read bytes */ + if (unlikely(io & 3)) { + insb(io, buf, len); + return; + } + + /* Align target to word: first byte then half word */ + if ((unsigned long)buf & 1) { + *(u8 *)buf = inb(io); + buf++; + len--; + } + if (((unsigned long)buf & 2) && (len >= 2)) { + *(u16 *)buf = inw(io); + buf += 2; + len -= 2; + } + /* Transfer words, then remaining halfword and remaining byte */ + if (len >= 4) { + insl(io, buf, len >> 2); + buf += len & ~3; + } + if (len & 2) { + *(u16 *)buf = inw(io); + buf += 2; + } + if (len & 1) { + *(u8 *)buf = inb(io); + } +} + +static void aspeed_smc_to_fifo(void __iomem *iop, const void *buf, size_t len) +{ + unsigned long io = (__force unsigned long)iop; + + if (!len) + return; + + /* Expect a 4 byte output port. Otherwise just write bytes */ + if (io & 3) { + outsb(io, buf, len); + return; + } + + /* Align target to word: first byte then half word */ + if ((unsigned long)buf & 1) { + outb(*(u8 *)buf, io); + buf++; + len--; + } + if (((unsigned long)buf & 2) && (len >= 2)) { + outw(*(u16 *)buf, io); + buf += 2; + len -= 2; + } + /* Transfer words, then remaining halfword and remaining byte */ + if (len >= 4) { + outsl(io, buf, len >> 2); + buf += len & ~(size_t)3; + } + if (len & 2) { + outw(*(u16 *)buf, io); + buf += 2; + } + if (len & 1) { + outb(*(u8 *)buf, io); + } +} + +enum smc_flash_type { + smc_type_nor = 0, /* controller connected to nor flash */ + smc_type_nand = 1, /* controller connected to nand flash */ + smc_type_spi = 2, /* controller connected to spi flash */ +}; + +struct aspeed_smc_info { + u8 nce; /* number of chip enables */ + u8 maxwidth; /* max width of spi bus */ + bool hasdma; /* has dma engine */ + bool hastype; /* type shift for ce 0 in cfg reg */ + u8 we0; /* we shift for ce 0 in cfg reg */ + u8 ctl0; /* offset in regs of ctl for ce 0 */ + u8 cfg; /* offset in regs of cfg */ + u8 time; /* offset in regs of timing */ + u8 misc; /* offset in regs of misc settings */ +}; + +static struct aspeed_smc_info fmc_info = { + .nce = 5, + .maxwidth = 4, + .hasdma = true, + .hastype = true, + .we0 = 16, + .ctl0 = 0x10, + .cfg = 0x00, + .time = 0x54, + .misc = 0x50, +}; + +static struct aspeed_smc_info smc_info = { + .nce = 1, + .maxwidth = 2, + .hasdma = false, + .hastype = false, + .we0 = 0, + .ctl0 = 0x04, + .cfg = 0x00, + .time = 0x14, + .misc = 0x10, +}; + +enum smc_ctl_reg_value { + smc_base, /* base value without mode for other commands */ + smc_read, /* command reg for (maybe fast) reads */ + smc_write, /* command reg for writes with timings */ + smc_num_ctl_reg_values /* last value to get count of commands */ +}; + +struct aspeed_smc_controller; + +struct aspeed_smc_chip { + struct aspeed_smc_controller *controller; + __le32 __iomem *ctl; /* control register */ + void __iomem *base; /* base of chip window */ + __le32 ctl_val[smc_num_ctl_reg_values]; /* controls with timing */ + enum smc_flash_type type; /* what type of flash */ + struct spi_nor nor; +}; + +struct aspeed_smc_controller { + struct mutex mutex; /* controller access mutex */ + const struct aspeed_smc_info *info; /* type info of controller */ + void __iomem *regs; /* controller registers */ + struct aspeed_smc_chip *chips[0]; /* attached chips */ +}; + +#define CONTROL_SPI_AAF_MODE BIT(31) +#define CONTROL_SPI_IO_MODE_MASK GENMASK(30, 28) +#define CONTROL_SPI_IO_DUAL_DATA BIT(29) +#define CONTROL_SPI_IO_DUAL_ADDR_DATA (BIT(29) | BIT(28)) +#define CONTROL_SPI_IO_QUAD_DATA BIT(30) +#define CONTROL_SPI_IO_QUAD_ADDR_DATA (BIT(30) | BIT(28)) +#define CONTROL_SPI_CE_INACTIVE_SHIFT 24 +#define CONTROL_SPI_CE_INACTIVE_MASK GENMASK(27, CONTROL_SPI_CE_INACTIVE_SHIFT) +/* 0 = 16T ... 15 = 1T T=HCLK */ +#define CONTROL_SPI_COMMAND_SHIFT 16 +#define CONTROL_SPI_DUMMY_CYCLE_COMMAND_OUTPUT BIT(15) +#define CONTROL_SPI_IO_DUMMY_CYCLES_HI BIT(14) +#define CONTROL_SPI_IO_DUMMY_CYCLES_HI_SHIFT (14 - 2) +#define CONTROL_SPI_IO_ADDRESS_4B BIT(13) /* FMC, LEGACY */ +#define CONTROL_SPI_CLK_DIV4 BIT(13) /* BIOS */ +#define CONTROL_SPI_RW_MERGE BIT(12) +#define CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT 6 +#define CONTROL_SPI_IO_DUMMY_CYCLES_LO GENMASK(7, CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT) +#define CONTROL_SPI_IO_DUMMY_CYCLES_MASK (CONTROL_SPI_IO_DUMMY_CYCLES_HI | \ + CONTROL_SPI_IO_DUMMY_CYCLES_LO) +#define CONTROL_SPI_CLOCK_FREQ_SEL_SHIFT 8 +#define CONTROL_SPI_CLOCK_FREQ_SEL_MASK GENMASK(11, CONTROL_SPI_CLOCK_FREQ_SEL_SHIFT) +#define CONTROL_SPI_LSB_FIRST BIT(5) +#define CONTROL_SPI_CLOCK_MODE_3 BIT(4) +#define CONTROL_SPI_IN_DUAL_DATA BIT(3) +#define CONTROL_SPI_CE_STOP_ACTIVE_CONTROL BIT(2) +#define CONTROL_SPI_COMMAND_MODE_MASK GENMASK(1, 0) +#define CONTROL_SPI_COMMAND_MODE_NORMAL (0) +#define CONTROL_SPI_COMMAND_MODE_FREAD (1) +#define CONTROL_SPI_COMMAND_MODE_WRITE (2) +#define CONTROL_SPI_COMMAND_MODE_USER (3) + +#define CONTROL_SPI_KEEP_MASK (CONTROL_SPI_AAF_MODE | \ + CONTROL_SPI_CE_INACTIVE_MASK | CONTROL_SPI_IO_ADDRESS_4B | \ + CONTROL_SPI_IO_DUMMY_CYCLES_MASK | CONTROL_SPI_CLOCK_FREQ_SEL_MASK | \ + CONTROL_SPI_LSB_FIRST | CONTROL_SPI_CLOCK_MODE_3) + +#define CONTROL_SPI_CLK_DIV4 BIT(13) /* BIOS */ + +static u32 spi_control_fill_opcode(u8 opcode) +{ + return ((u32)(opcode)) << CONTROL_SPI_COMMAND_SHIFT; +} + +static void aspeed_smc_start_user(struct spi_nor *nor) +{ + struct aspeed_smc_chip *chip = nor->priv; + u32 ctl = chip->ctl_val[smc_base]; + + mutex_lock(&chip->controller->mutex); + + ctl |= CONTROL_SPI_COMMAND_MODE_USER | + CONTROL_SPI_CE_STOP_ACTIVE_CONTROL; + writel(ctl, chip->ctl); + + ctl &= ~CONTROL_SPI_CE_STOP_ACTIVE_CONTROL; + writel(ctl, chip->ctl); +} + +static void aspeed_smc_stop_user(struct spi_nor *nor) +{ + struct aspeed_smc_chip *chip = nor->priv; + + u32 ctl = chip->ctl_val[smc_read]; + u32 ctl2 = ctl | CONTROL_SPI_COMMAND_MODE_USER | + CONTROL_SPI_CE_STOP_ACTIVE_CONTROL; + + writel(ctl2, chip->ctl); /* stop user CE control */ + writel(ctl, chip->ctl); /* default to fread or read */ + + mutex_unlock(&chip->controller->mutex); +} + +static int aspeed_smc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len) +{ + struct aspeed_smc_chip *chip = nor->priv; + + aspeed_smc_start_user(nor); + aspeed_smc_to_fifo(chip->base, &opcode, 1); + aspeed_smc_from_fifo(buf, chip->base, len); + aspeed_smc_stop_user(nor); + + return 0; +} + +static int aspeed_smc_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, + int len) +{ + struct aspeed_smc_chip *chip = nor->priv; + + aspeed_smc_start_user(nor); + aspeed_smc_to_fifo(chip->base, &opcode, 1); + aspeed_smc_to_fifo(chip->base, buf, len); + aspeed_smc_stop_user(nor); + + return 0; +} + +static void aspeed_smc_send_cmd_addr(struct spi_nor *nor, u8 cmd, u32 addr) +{ + struct aspeed_smc_chip *chip = nor->priv; + __be32 temp; + u32 cmdaddr; + + switch (nor->addr_width) { + default: + WARN_ONCE(1, "Unexpected address width %u, defaulting to 3\n", + nor->addr_width); + /* FALLTHROUGH */ + case 3: + cmdaddr = addr & 0xFFFFFF; + + cmdaddr |= (u32)cmd << 24; + + temp = cpu_to_be32(cmdaddr); + aspeed_smc_to_fifo(chip->base, &temp, 4); + break; + case 4: + temp = cpu_to_be32(addr); + aspeed_smc_to_fifo(chip->base, &cmd, 1); + aspeed_smc_to_fifo(chip->base, &temp, 4); + break; + } +} + +static int aspeed_smc_read_user(struct spi_nor *nor, loff_t from, size_t len, + size_t *retlen, u_char *read_buf) +{ + struct aspeed_smc_chip *chip = nor->priv; + + aspeed_smc_start_user(nor); + aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from); + aspeed_smc_from_fifo(read_buf, chip->base, len); + *retlen += len; + aspeed_smc_stop_user(nor); + + return 0; +} + +static void aspeed_smc_write_user(struct spi_nor *nor, loff_t to, size_t len, + size_t *retlen, const u_char *write_buf) +{ + struct aspeed_smc_chip *chip = nor->priv; + + aspeed_smc_start_user(nor); + aspeed_smc_send_cmd_addr(nor, nor->program_opcode, to); + aspeed_smc_to_fifo(chip->base, write_buf, len); + *retlen += len; + aspeed_smc_stop_user(nor); +} + +static int aspeed_smc_erase(struct spi_nor *nor, loff_t offs) +{ + aspeed_smc_start_user(nor); + aspeed_smc_send_cmd_addr(nor, nor->erase_opcode, offs); + aspeed_smc_stop_user(nor); + + return 0; +} + +static int aspeed_smc_remove(struct platform_device *dev) +{ + struct aspeed_smc_chip *chip; + struct aspeed_smc_controller *controller = platform_get_drvdata(dev); + int n; + + for (n = 0; n < controller->info->nce; n++) { + chip = controller->chips[n]; + if (chip) + mtd_device_unregister(&chip->nor.mtd); + } + + return 0; +} + +const struct of_device_id aspeed_smc_matches[] = { + { .compatible = "aspeed,fmc", .data = &fmc_info }, + { .compatible = "aspeed,smc", .data = &smc_info }, + { } +}; +MODULE_DEVICE_TABLE(of, aspeed_smc_matches); + +static struct platform_device * +of_platform_device_create_or_find(struct device_node *child, + struct device *parent) +{ + struct platform_device *cdev; + + cdev = of_platform_device_create(child, NULL, parent); + if (!cdev) + cdev = of_find_device_by_node(child); + return cdev; +} + +static int aspeed_smc_probe(struct platform_device *dev) +{ + struct aspeed_smc_controller *controller; + const struct of_device_id *match; + const struct aspeed_smc_info *info; + struct resource *r; + void __iomem *regs; + struct device_node *child; + int err = 0; + unsigned int n; + + /* + * This driver passes ioremap addresses to io port accessors. + * This works on arm if the IO_SPACE_LIMIT does not truncate + * the address. + */ + if (~(unsigned long)IO_SPACE_LIMIT) + return -ENODEV; + + match = of_match_device(aspeed_smc_matches, &dev->dev); + if (!match || !match->data) + return -ENODEV; + info = match->data; + r = platform_get_resource(dev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&dev->dev, r); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + controller = devm_kzalloc(&dev->dev, sizeof(*controller) + + info->nce * sizeof(controller->chips[0]), GFP_KERNEL); + if (!controller) + return -ENOMEM; + platform_set_drvdata(dev, controller); + controller->regs = regs; + controller->info = info; + mutex_init(&controller->mutex); + + /* XXX turn off legacy mode if fmc ? */ + /* XXX handshake to enable access to SMC (bios) controller w/ host? */ + + for_each_available_child_of_node(dev->dev.of_node, child) { + struct mtd_part_parser_data ppdata; + struct platform_device *cdev; + struct aspeed_smc_chip *chip; + u32 reg; + + if (!of_device_is_compatible(child, "jedec,spi-nor")) + continue; /* XXX consider nand, nor children */ + + + /* + * create a platform device from the of node. + * if the device already was created (eg from + * a prior bind/unbind cycle) use it + * + * XXX The child name will become the default mtd + * name in ioctl and /proc/mtd. Should we default + * to node->name (without unit)? The name must be + * unique among all platform devices. (Name would + * replace NULL in create call below). + * ... Or we can just encourage the label attribute. + * + * The only reason to do the child here is to use it in + * dev_err below for duplicate chip id. We could use + * the controller dev. + */ + cdev = of_platform_device_create_or_find(child, &dev->dev); + if (!cdev) + continue; + + err = of_property_read_u32(child, "reg", &n); + if (err == -EINVAL && info->nce == 1) + n = 0; + else if (err || n >= info->nce) + continue; + if (controller->chips[n]) { + dev_err(&cdev->dev, + "chip-id %u already in use in use by %s\n", + n, dev_name(controller->chips[n]->nor.dev)); + continue; + } + chip = devm_kzalloc(&dev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + continue; + + r = platform_get_resource(dev, IORESOURCE_MEM, n + 1); + chip->base = devm_ioremap_resource(&dev->dev, r); + + if (!chip->base) + continue; + chip->controller = controller; + chip->ctl = controller->regs + info->ctl0 + n * 4; + + /* dt said its spi. xxx Set it in controller if has_type */ + chip->type = smc_type_spi; + + /* + * Always turn on write enable bit in config register to + * allow opcodes to be sent in user mode. + */ + mutex_lock(&controller->mutex); + reg = readl(controller->regs + info->cfg); + dev_dbg(&dev->dev, "flash config was %08x\n", reg); + reg |= 1 << (info->we0 + n); /* WEn */ + writel(reg, controller->regs + info->cfg); + mutex_unlock(&controller->mutex); + + /* XXX check resource within fmc CEx Segment Address Register */ + /* XXX -- see dt vs jedec id vs bootloader */ + /* XXX check / program clock phase/polarity, only 0 or 3 */ + + /* + * Read the existing control register to get basic values. + * + * XXX probably need more sanitation. + * XXX do we trust the bootloader or the device tree? + * spi-nor.c trusts jtag id over passed ids. + */ + reg = readl(chip->ctl); + chip->ctl_val[smc_base] = reg & CONTROL_SPI_KEEP_MASK; + + if ((reg & CONTROL_SPI_COMMAND_MODE_MASK) == + CONTROL_SPI_COMMAND_MODE_NORMAL) + chip->ctl_val[smc_read] = reg; + else + chip->ctl_val[smc_read] = chip->ctl_val[smc_base] | + CONTROL_SPI_COMMAND_MODE_NORMAL; + + chip->nor.dev = &cdev->dev; + chip->nor.priv = chip; + chip->nor.flash_node = child; + chip->nor.mtd.name = of_get_property(child, "label", NULL); + chip->nor.erase = aspeed_smc_erase; + chip->nor.read = aspeed_smc_read_user; + chip->nor.write = aspeed_smc_write_user; + chip->nor.read_reg = aspeed_smc_read_reg; + chip->nor.write_reg = aspeed_smc_write_reg; + + /* + * XXX use of property and controller info width to choose + * SPI_NOR_QUAD , SPI_NOR_DUAL + */ + err = spi_nor_scan(&chip->nor, NULL, SPI_NOR_NORMAL); + if (err) + continue; + + chip->ctl_val[smc_write] = chip->ctl_val[smc_base] | + spi_control_fill_opcode(chip->nor.program_opcode) | + CONTROL_SPI_COMMAND_MODE_WRITE; + + /* XXX intrepret nor flags into controller settings */ + /* XXX enable fast read here */ + /* XXX check if resource size big enough for chip */ + + memset(&ppdata, 0, sizeof(ppdata)); + ppdata.of_node = cdev->dev.of_node; + err = mtd_device_parse_register(&chip->nor.mtd, NULL, &ppdata, NULL, 0); + if (err) + continue; + controller->chips[n] = chip; + } + + /* did we register any children? */ + for (n = 0; n < info->nce; n++) + if (controller->chips[n]) + break; + + if (n == info->nce) + return -ENODEV; + + return 0; +} + +static struct platform_driver aspeed_smc_driver = { + .probe = aspeed_smc_probe, + .remove = aspeed_smc_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = aspeed_smc_matches, + } +}; + +module_platform_driver(aspeed_smc_driver); + +MODULE_DESCRIPTION("ASPEED Static Memory Controller Driver"); +MODULE_AUTHOR("Milton Miller"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/ethernet/faraday/ftgmac100.c b/drivers/net/ethernet/faraday/ftgmac100.c index 6d0c5d5eea6d..7eae1978b696 100644 --- a/drivers/net/ethernet/faraday/ftgmac100.c +++ b/drivers/net/ethernet/faraday/ftgmac100.c @@ -31,6 +31,7 @@ #include <linux/phy.h> #include <linux/platform_device.h> #include <net/ip.h> +#include <net/ncsi.h> #include "ftgmac100.h" @@ -68,12 +69,16 @@ struct ftgmac100 { struct net_device *netdev; struct device *dev; + struct ncsi_dev *ndev; struct napi_struct napi; struct mii_bus *mii_bus; int phy_irq[PHY_MAX_ADDR]; struct phy_device *phydev; int old_speed; + + bool use_ncsi; + bool enabled; }; static int ftgmac100_alloc_rx_page(struct ftgmac100 *priv, @@ -86,7 +91,6 @@ static int ftgmac100_alloc_rx_page(struct ftgmac100 *priv, FTGMAC100_INT_XPKT_ETH | \ FTGMAC100_INT_XPKT_LOST | \ FTGMAC100_INT_AHB_ERR | \ - FTGMAC100_INT_PHYSTS_CHG | \ FTGMAC100_INT_RPKT_BUF | \ FTGMAC100_INT_NO_RXBUF) @@ -134,7 +138,7 @@ static int ftgmac100_reset_hw(struct ftgmac100 *priv) return -EIO; } -static void ftgmac100_set_mac(struct ftgmac100 *priv, const unsigned char *mac) +static void ftgmac100_do_set_mac(struct ftgmac100 *priv, const unsigned char *mac) { unsigned int maddr = mac[0] << 8 | mac[1]; unsigned int laddr = mac[2] << 24 | mac[3] << 16 | mac[4] << 8 | mac[5]; @@ -143,6 +147,57 @@ static void ftgmac100_set_mac(struct ftgmac100 *priv, const unsigned char *mac) iowrite32(laddr, priv->base + FTGMAC100_OFFSET_MAC_LADR); } +static void ftgmac100_setup_mac(struct ftgmac100 *priv) +{ + unsigned char mac[6]; + unsigned int m; + unsigned int l; + + /* XXX TODO: Read from device-tree if provided */ + + m = ioread32(priv->base + FTGMAC100_OFFSET_MAC_MADR); + l = ioread32(priv->base + FTGMAC100_OFFSET_MAC_LADR); + + mac[0] = (m >> 8) & 0xff; + mac[1] = (m ) & 0xff; + mac[2] = (l >> 24) & 0xff; + mac[3] = (l >> 16) & 0xff; + mac[4] = (l >> 8) & 0xff; + mac[5] = (l ) & 0xff; + + /* XXX Temp workaround for u-boot garbage */ + if (!is_valid_ether_addr(mac)) { + mac[5] = (m >> 8) & 0xff; + mac[4] = (m ) & 0xff; + mac[3] = (l >> 24) & 0xff; + mac[2] = (l >> 16) & 0xff; + mac[1] = (l >> 8) & 0xff; + mac[0] = (l ) & 0xff; + } + + if (!is_valid_ether_addr(mac)) { + eth_hw_addr_random(priv->netdev); + dev_info(priv->dev, "Generated random MAC address %pM\n", + priv->netdev->dev_addr); + } else { + dev_info(priv->dev, "Read MAC address from chip %pM\n", mac); + memcpy(priv->netdev->dev_addr, mac, 6); + } +} + +static int ftgmac100_set_mac_addr(struct net_device *dev, void *p) +{ + struct ftgmac100 *priv = netdev_priv(dev); + + int ret = eth_prepare_mac_addr_change(dev, p); + if (ret < 0) + return ret; + eth_commit_mac_addr_change(dev, p); + ftgmac100_do_set_mac(priv, dev->dev_addr); + + return 0; +} + static void ftgmac100_init_hw(struct ftgmac100 *priv) { /* setup ring buffer base registers */ @@ -157,7 +212,7 @@ static void ftgmac100_init_hw(struct ftgmac100 *priv) iowrite32(FTGMAC100_APTC_RXPOLL_CNT(1), priv->base + FTGMAC100_OFFSET_APTC); - ftgmac100_set_mac(priv, priv->netdev->dev_addr); + ftgmac100_do_set_mac(priv, priv->netdev->dev_addr); } #define MACCR_ENABLE_ALL (FTGMAC100_MACCR_TXDMA_EN | \ @@ -956,6 +1011,8 @@ static int ftgmac100_get_settings(struct net_device *netdev, { struct ftgmac100 *priv = netdev_priv(netdev); + if (!priv->phydev) + return -EINVAL; return phy_ethtool_gset(priv->phydev, cmd); } @@ -964,6 +1021,8 @@ static int ftgmac100_set_settings(struct net_device *netdev, { struct ftgmac100 *priv = netdev_priv(netdev); + if (!priv->phydev) + return -EINVAL; return phy_ethtool_sset(priv->phydev, cmd); } @@ -982,7 +1041,11 @@ static irqreturn_t ftgmac100_interrupt(int irq, void *dev_id) struct net_device *netdev = dev_id; struct ftgmac100 *priv = netdev_priv(netdev); - if (likely(netif_running(netdev))) { + /* When running in NCSI mode, the interface should be + * ready to receive or transmit NCSI packet before it's + * opened. + */ + if (likely(priv->use_ncsi || netif_running(netdev))) { /* Disable interrupts for polling */ iowrite32(0, priv->base + FTGMAC100_OFFSET_IER); napi_schedule(&priv->napi); @@ -1036,13 +1099,12 @@ static int ftgmac100_poll(struct napi_struct *napi, int budget) } if (status & (FTGMAC100_INT_NO_RXBUF | FTGMAC100_INT_RPKT_LOST | - FTGMAC100_INT_AHB_ERR | FTGMAC100_INT_PHYSTS_CHG)) { + FTGMAC100_INT_AHB_ERR)) { if (net_ratelimit()) - netdev_info(netdev, "[ISR] = 0x%x: %s%s%s%s\n", status, + netdev_info(netdev, "[ISR] = 0x%x: %s%s%s\n", status, status & FTGMAC100_INT_NO_RXBUF ? "NO_RXBUF " : "", status & FTGMAC100_INT_RPKT_LOST ? "RPKT_LOST " : "", - status & FTGMAC100_INT_AHB_ERR ? "AHB_ERR " : "", - status & FTGMAC100_INT_PHYSTS_CHG ? "PHYSTS_CHG" : ""); + status & FTGMAC100_INT_AHB_ERR ? "AHB_ERR " : ""); if (status & FTGMAC100_INT_NO_RXBUF) { /* RX buffer unavailable */ @@ -1095,17 +1157,32 @@ static int ftgmac100_open(struct net_device *netdev) goto err_hw; ftgmac100_init_hw(priv); - ftgmac100_start_hw(priv, 10); + ftgmac100_start_hw(priv, priv->use_ncsi ? 100 : 10); - phy_start(priv->phydev); + if (priv->phydev) + phy_start(priv->phydev); + else if (priv->use_ncsi) + netif_carrier_on(priv->netdev); napi_enable(&priv->napi); netif_start_queue(netdev); /* enable all interrupts */ iowrite32(INT_MASK_ALL_ENABLED, priv->base + FTGMAC100_OFFSET_IER); + /* Start the NCSI device */ + if (priv->use_ncsi){ + err = ncsi_start_dev(priv->ndev); + if (err) + goto err_ncsi; + } + + priv->enabled = true; return 0; +err_ncsi: + napi_disable(&priv->napi); + netif_stop_queue(netdev); + iowrite32(0, priv->base + FTGMAC100_OFFSET_IER); err_hw: free_irq(priv->irq, netdev); err_irq: @@ -1114,16 +1191,21 @@ err_alloc: return err; } -static int ftgmac100_stop(struct net_device *netdev) +static int ftgmac100_stop_dev(struct net_device *netdev) { struct ftgmac100 *priv = netdev_priv(netdev); + if (!priv->enabled) + return 0; + /* disable all interrupts */ + priv->enabled = false; iowrite32(0, priv->base + FTGMAC100_OFFSET_IER); netif_stop_queue(netdev); napi_disable(&priv->napi); - phy_stop(priv->phydev); + if (priv->phydev) + phy_stop(priv->phydev); ftgmac100_stop_hw(priv); free_irq(priv->irq, netdev); @@ -1132,6 +1214,10 @@ static int ftgmac100_stop(struct net_device *netdev) return 0; } +static int ftgmac100_stop(struct net_device *netdev) +{ + return ftgmac100_stop_dev(netdev); +} static int ftgmac100_hard_start_xmit(struct sk_buff *skb, struct net_device *netdev) { @@ -1166,18 +1252,78 @@ static int ftgmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr, int { struct ftgmac100 *priv = netdev_priv(netdev); + if (!priv->phydev) + return -EINVAL; return phy_mii_ioctl(priv->phydev, ifr, cmd); } +static int ftgmac100_setup_mdio(struct ftgmac100 *priv) +{ + int i, err = 0; + + /* initialize mdio bus */ + priv->mii_bus = mdiobus_alloc(); + if (!priv->mii_bus) { + err = -EIO; + goto err_alloc_mdiobus; + } + + priv->mii_bus->name = "ftgmac100_mdio"; + snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "ftgmac100_mii"); + + priv->mii_bus->priv = priv->netdev; + priv->mii_bus->read = ftgmac100_mdiobus_read; + priv->mii_bus->write = ftgmac100_mdiobus_write; + priv->mii_bus->irq = priv->phy_irq; + + for (i = 0; i < PHY_MAX_ADDR; i++) + priv->mii_bus->irq[i] = PHY_POLL; + + err = mdiobus_register(priv->mii_bus); + if (err) { + dev_err(priv->dev, "Cannot register MDIO bus!\n"); + goto err_register_mdiobus; + } + + err = ftgmac100_mii_probe(priv); + if (err) { + dev_err(priv->dev, "MII Probe failed!\n"); + goto err_mii_probe; + } + return 0; + +err_mii_probe: + mdiobus_unregister(priv->mii_bus); +err_register_mdiobus: + mdiobus_free(priv->mii_bus); +err_alloc_mdiobus: + return err; +} + +static void ftgmac100_destroy_mdio(struct ftgmac100 *priv) +{ + if (!priv->use_ncsi) + return; + phy_disconnect(priv->phydev); + mdiobus_unregister(priv->mii_bus); + mdiobus_free(priv->mii_bus); +} + static const struct net_device_ops ftgmac100_netdev_ops = { .ndo_open = ftgmac100_open, .ndo_stop = ftgmac100_stop, .ndo_start_xmit = ftgmac100_hard_start_xmit, - .ndo_set_mac_address = eth_mac_addr, + .ndo_set_mac_address = ftgmac100_set_mac_addr, .ndo_validate_addr = eth_validate_addr, .ndo_do_ioctl = ftgmac100_do_ioctl, }; +static void ftgmac100_ncsi_handler(struct ncsi_dev *nd) +{ + if (nd->nd_state == ncsi_dev_state_functional) + pr_info("NCSI interface %s\n", + nd->nd_link_up ? "up" : "down"); +} /****************************************************************************** * struct platform_driver functions *****************************************************************************/ @@ -1187,8 +1333,7 @@ static int ftgmac100_probe(struct platform_device *pdev) int irq; struct net_device *netdev; struct ftgmac100 *priv; - int err; - int i; + int err = 0; if (!pdev) return -ENODEV; @@ -1208,16 +1353,29 @@ static int ftgmac100_probe(struct platform_device *pdev) goto err_alloc_etherdev; } + /* Check for NCSI mode */ + priv = netdev_priv(netdev); SET_NETDEV_DEV(netdev, &pdev->dev); + if (pdev->dev.of_node && + of_get_property(pdev->dev.of_node, "use-nc-si", NULL)) { + dev_info(&pdev->dev, "Using NCSI interface\n"); + priv->phydev = NULL; + priv->use_ncsi = true; + } else { + priv->use_ncsi = false; + } netdev->ethtool_ops = &ftgmac100_ethtool_ops; netdev->netdev_ops = &ftgmac100_netdev_ops; - netdev->features = NETIF_F_IP_CSUM | NETIF_F_GRO; + if (pdev->dev.of_node && + of_get_property(pdev->dev.of_node, "no-hw-checksum", NULL)) + netdev->features = NETIF_F_GRO; + else + netdev->features = NETIF_F_IP_CSUM | NETIF_F_GRO; platform_set_drvdata(pdev, netdev); /* setup private data */ - priv = netdev_priv(netdev); priv->netdev = netdev; priv->dev = &pdev->dev; @@ -1244,60 +1402,41 @@ static int ftgmac100_probe(struct platform_device *pdev) priv->irq = irq; - /* initialize mdio bus */ - priv->mii_bus = mdiobus_alloc(); - if (!priv->mii_bus) { - err = -EIO; - goto err_alloc_mdiobus; - } - - priv->mii_bus->name = "ftgmac100_mdio"; - snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "ftgmac100_mii"); - - priv->mii_bus->priv = netdev; - priv->mii_bus->read = ftgmac100_mdiobus_read; - priv->mii_bus->write = ftgmac100_mdiobus_write; - priv->mii_bus->irq = priv->phy_irq; - - for (i = 0; i < PHY_MAX_ADDR; i++) - priv->mii_bus->irq[i] = PHY_POLL; + /* Read MAC address or setup a new one */ + ftgmac100_setup_mac(priv); - err = mdiobus_register(priv->mii_bus); - if (err) { - dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); - goto err_register_mdiobus; - } + /* Register NCSI device */ + if (priv->use_ncsi) { + priv->ndev = ncsi_register_dev(netdev, ftgmac100_ncsi_handler); + if (!priv->ndev) + goto err_ncsi_dev; + } else { + err = ftgmac100_setup_mdio(priv); - err = ftgmac100_mii_probe(priv); - if (err) { - dev_err(&pdev->dev, "MII Probe failed!\n"); - goto err_mii_probe; + /* Survive PHY probe failure, chances things will work if the + * PHY was setup by the bootloader + */ + if (err) + dev_warn(&pdev->dev, "Error %d setting up MDIO\n", err); } - /* register network device */ + /* Register network device */ err = register_netdev(netdev); if (err) { dev_err(&pdev->dev, "Failed to register netdev\n"); goto err_register_netdev; } - netdev_info(netdev, "irq %d, mapped at %p\n", priv->irq, priv->base); - - if (!is_valid_ether_addr(netdev->dev_addr)) { - eth_hw_addr_random(netdev); - netdev_info(netdev, "generated random MAC address %pM\n", - netdev->dev_addr); - } + netdev_dbg(netdev, "irq %d, mapped at %p\n", priv->irq, priv->base); return 0; err_register_netdev: - phy_disconnect(priv->phydev); -err_mii_probe: - mdiobus_unregister(priv->mii_bus); -err_register_mdiobus: - mdiobus_free(priv->mii_bus); -err_alloc_mdiobus: + if (!priv->use_ncsi) + ftgmac100_destroy_mdio(priv); + else + ncsi_unregister_dev(priv->ndev); +err_ncsi_dev: iounmap(priv->base); err_ioremap: release_resource(priv->res); @@ -1318,9 +1457,7 @@ static int __exit ftgmac100_remove(struct platform_device *pdev) unregister_netdev(netdev); - phy_disconnect(priv->phydev); - mdiobus_unregister(priv->mii_bus); - mdiobus_free(priv->mii_bus); + ftgmac100_destroy_mdio(priv); iounmap(priv->base); release_resource(priv->res); @@ -1330,11 +1467,18 @@ static int __exit ftgmac100_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id ftgmac100_of_match[] = { + { .compatible = "faraday,ftgmac100" }, + { } +}; +MODULE_DEVICE_TABLE(of, ftgmac100_of_match); + static struct platform_driver ftgmac100_driver = { .probe = ftgmac100_probe, - .remove = __exit_p(ftgmac100_remove), + .remove = ftgmac100_remove, .driver = { - .name = DRV_NAME, + .name = DRV_NAME, + .of_match_table = ftgmac100_of_match, }, }; diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index 3ce5d9514623..95208b0e36ca 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -604,6 +604,18 @@ static struct phy_driver broadcom_drivers[] = { .ack_interrupt = brcm_fet_ack_interrupt, .config_intr = brcm_fet_config_intr, .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM54210E, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM54210E", + .features = PHY_BASIC_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = bcm54xx_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = bcm_phy_ack_intr, + .config_intr = bcm_phy_config_intr, } }; module_phy_driver(broadcom_drivers); @@ -621,6 +633,7 @@ static struct mdio_device_id __maybe_unused broadcom_tbl[] = { { PHY_ID_BCM57780, 0xfffffff0 }, { PHY_ID_BCMAC131, 0xfffffff0 }, { PHY_ID_BCM5241, 0xfffffff0 }, + { PHY_ID_BCM54210E, 0xfffffff0}, { } }; diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 2a524244afec..0979c9c5e8e7 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1612,6 +1612,17 @@ config RTC_DRV_XGENE This driver can also be built as a module, if so, the module will be called "rtc-xgene". +config RTC_DRV_ASPEED + tristate "Aspeed AST24xx RTC" + depends on HAS_IOMEM + depends on ARCH_ASPEED || COMPILE_TEST + help + If you say yes here you get support for the Aspeed AST24xx SoC real + time clocks. + + This driver can also be built as a module, if so, the module + will be called "rtc-aspeed". + comment "HID Sensor RTC drivers" config RTC_DRV_HID_SENSOR_TIME diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 231f76451615..52c9ae3091f2 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_RTC_DRV_ABB5ZES3) += rtc-ab-b5ze-s3.o obj-$(CONFIG_RTC_DRV_ABX80X) += rtc-abx80x.o obj-$(CONFIG_RTC_DRV_ARMADA38X) += rtc-armada38x.o obj-$(CONFIG_RTC_DRV_AS3722) += rtc-as3722.o +obj-$(CONFIG_RTC_DRV_ASPEED) += rtc-aspeed.o obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o diff --git a/drivers/rtc/rtc-aspeed.c b/drivers/rtc/rtc-aspeed.c new file mode 100644 index 000000000000..bc5c30ddddfd --- /dev/null +++ b/drivers/rtc/rtc-aspeed.c @@ -0,0 +1,150 @@ +/* + * RTC driver for the Aspeed 24xx SoCs + * + * Copyright 2015 IBM Corp. + * + * Joel Stanley <joel@jms.id.au > + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/io.h> + +struct aspeed_rtc { + struct rtc_device *rtc_dev; + void __iomem *base; + spinlock_t lock; +}; + +#define RTC_TIME 0x00 +#define RTC_YEAR 0x04 +#define RTC_CTRL 0x10 + +#define RTC_UNLOCK 0x02 +#define RTC_ENABLE 0x01 + +static int aspeed_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct aspeed_rtc *rtc = dev_get_drvdata(dev); + unsigned int cent, year, mon, day, hour, min, sec; + u32 reg1, reg2; + + do { + reg2 = readl(rtc->base + RTC_YEAR); + reg1 = readl(rtc->base + RTC_TIME); + } while (reg2 != readl(rtc->base + RTC_YEAR)); + + day = (reg1 >> 24) & 0x1f; + hour = (reg1 >> 16) & 0x1f; + min = (reg1 >> 8) & 0x3f; + sec = (reg1 >> 0) & 0x3f; + cent = (reg2 >> 16) & 0x1f; + year = (reg2 >> 8) & 0x7f; + /* Month is 1-12 in hardware, and 0-11 in struct rtc_time, however we + * are using mktime64 which is 1-12, so no adjustment is necessary */ + mon = (reg2 >> 0) & 0x0f; + + rtc_time64_to_tm(mktime64(cent*100 + year, mon, day, hour, min, sec), + tm); + + return 0; +} + +static int aspeed_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct aspeed_rtc *rtc = dev_get_drvdata(dev); + unsigned long flags; + u32 reg1, reg2, ctrl; + int year, cent; + + /* tm_year counts from 1900 */ + cent = (tm->tm_year + 1900) / 100; + year = tm->tm_year % 100; + + reg1 = (tm->tm_mday << 24) | (tm->tm_hour << 16) | (tm->tm_min << 8) | + tm->tm_sec; + + /* Month in struct rtc_time is 0-11, but the hardware is 1-12 */ + reg2 = ((cent & 0x1f) << 16) | ((year & 0x7f) << 8) | + ((tm->tm_mon & 0xf) + 1); + + /* TODO: Do we need to lock? */ + spin_lock_irqsave(&rtc->lock, flags); + + ctrl = readl(rtc->base + RTC_CTRL); + writel(ctrl | RTC_UNLOCK, rtc->base + RTC_CTRL); + + writel(reg1, rtc->base + RTC_TIME); + writel(reg2, rtc->base + RTC_YEAR); + + writel(ctrl, rtc->base + RTC_CTRL); + + spin_unlock_irqrestore(&rtc->lock, flags); + + return 0; +} + +static struct rtc_class_ops aspeed_rtc_ops = { + .read_time = aspeed_rtc_read_time, + .set_time = aspeed_rtc_set_time, +}; + +static int aspeed_rtc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct aspeed_rtc *rtc; + + rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rtc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rtc->base)) + return PTR_ERR(rtc->base); + + platform_set_drvdata(pdev, rtc); + + rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name, + &aspeed_rtc_ops, THIS_MODULE); + + if (IS_ERR(rtc->rtc_dev)) { + dev_err(&pdev->dev, "failed to register\n"); + return PTR_ERR(rtc->rtc_dev); + } + + spin_lock_init(&rtc->lock); + + /* Enable RTC and clear the unlock bit */ + writel(RTC_ENABLE, rtc->base + RTC_CTRL); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id aspeed_rtc_of_match_table[] = { + { .compatible = "aspeed,rtc", }, + {} +}; +#endif + +static struct platform_driver aspeed_rtc_driver = { + .driver = { + .name = "aspeed-rtc", + .of_match_table = of_match_ptr(aspeed_rtc_of_match_table), + }, +}; + +module_platform_driver_probe(aspeed_rtc_driver, aspeed_rtc_probe); + +MODULE_DESCRIPTION("Aspeed AST24xx RTC driver"); +MODULE_AUTHOR("Joel Stanley <joel@jms.id.au>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index f38beb28e7ae..79645b0da152 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1094,6 +1094,16 @@ config SERIAL_NETX_CONSOLE If you have enabled the serial port on the Hilscher NetX SoC you can make it the console by answering Y to this option. +config SERIAL_ASPEED_VUART + tristate "Aspeed Virtual UART" + depends on OF + depends on SERIAL_8250 + help + If you want to use the virtual UART (VUART) device on Aspeed + BMC platforms, enable this option. This enables the 16550A- + compatible device on the local LPC bus, giving a UART device + with no physical RS232 connections. + config SERIAL_OF_PLATFORM tristate "Serial port on Open Firmware platform bus" depends on OF diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 5ab41119b3dc..72dc094ba3fa 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_SERIAL_MSM) += msm_serial.o obj-$(CONFIG_SERIAL_NETX) += netx-serial.o obj-$(CONFIG_SERIAL_OF_PLATFORM) += of_serial.o obj-$(CONFIG_SERIAL_OF_PLATFORM_NWPSERIAL) += nwpserial.o +obj-$(CONFIG_SERIAL_ASPEED_VUART) += aspeed-vuart.o obj-$(CONFIG_SERIAL_KGDB_NMI) += kgdb_nmi.o obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o obj-$(CONFIG_SERIAL_OMAP) += omap-serial.o diff --git a/drivers/tty/serial/aspeed-vuart.c b/drivers/tty/serial/aspeed-vuart.c new file mode 100644 index 000000000000..020c8159d25d --- /dev/null +++ b/drivers/tty/serial/aspeed-vuart.c @@ -0,0 +1,333 @@ +/* + * Serial Port driver for Aspeed VUART device + * + * Copyright (C) 2016 Jeremy Kerr <jk@ozlabs.org>, IBM Corp. + * Copyright (C) 2006 Arnd Bergmann <arnd@arndb.de>, IBM Corp. + * + * 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. + * + */ +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/clk.h> + +#include "8250/8250.h" + +#define AST_VUART_GCRA 0x20 +#define AST_VUART_GCRA_VUART_EN 0x01 +#define AST_VUART_GCRA_HOST_TX_DISCARD 0x20 +#define AST_VUART_GCRB 0x24 +#define AST_VUART_GCRB_HOST_SIRQ_MASK 0xf0 +#define AST_VUART_GCRB_HOST_SIRQ_SHIFT 4 +#define AST_VUART_ADDRL 0x28 +#define AST_VUART_ADDRH 0x2c + +struct ast_vuart { + struct platform_device *pdev; + void __iomem *regs; + struct clk *clk; + int line; +}; + +static ssize_t ast_vuart_show_addr(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ast_vuart *vuart = dev_get_drvdata(dev); + u16 addr; + + addr = (readb(vuart->regs + AST_VUART_ADDRH) << 8) | + (readb(vuart->regs + AST_VUART_ADDRL)); + + return snprintf(buf, PAGE_SIZE - 1, "0x%x\n", addr); +} + +static ssize_t ast_vuart_set_addr(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ast_vuart *vuart = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 0, &val); + if (err) + return err; + + writeb((val >> 8) & 0xff, vuart->regs + AST_VUART_ADDRH); + writeb((val >> 0) & 0xff, vuart->regs + AST_VUART_ADDRL); + + return count; +} + +static DEVICE_ATTR(lpc_address, S_IWUSR | S_IRUGO, + ast_vuart_show_addr, ast_vuart_set_addr); + +static ssize_t ast_vuart_show_sirq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ast_vuart *vuart = dev_get_drvdata(dev); + u8 reg; + + reg = readb(vuart->regs + AST_VUART_GCRB); + reg &= AST_VUART_GCRB_HOST_SIRQ_MASK; + reg >>= AST_VUART_GCRB_HOST_SIRQ_SHIFT; + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", reg); +} + +static ssize_t ast_vuart_set_sirq(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ast_vuart *vuart = dev_get_drvdata(dev); + unsigned long val; + int err; + u8 reg; + + err = kstrtoul(buf, 0, &val); + if (err) + return err; + + val <<= AST_VUART_GCRB_HOST_SIRQ_SHIFT; + val &= AST_VUART_GCRB_HOST_SIRQ_MASK; + + reg = readb(vuart->regs + AST_VUART_GCRB); + reg &= ~AST_VUART_GCRB_HOST_SIRQ_MASK; + reg |= val; + writeb(reg, vuart->regs + AST_VUART_GCRB); + + return count; +} + +static DEVICE_ATTR(sirq, S_IWUSR | S_IRUGO, + ast_vuart_show_sirq, ast_vuart_set_sirq); + +static void ast_vuart_set_enabled(struct ast_vuart *vuart, bool enabled) +{ + u8 reg; + + reg = readb(vuart->regs + AST_VUART_GCRA); + reg &= ~AST_VUART_GCRA_VUART_EN; + if (enabled) + reg |= AST_VUART_GCRA_VUART_EN; + writeb(reg, vuart->regs + AST_VUART_GCRA); +} + +static void ast_vuart_set_host_tx_discard(struct ast_vuart *vuart, bool discard) +{ + u8 reg; + + reg = readb(vuart->regs + AST_VUART_GCRA); + + /* if the HOST_TX_DISCARD bit is set, discard is *disabled* */ + reg &= ~AST_VUART_GCRA_HOST_TX_DISCARD; + if (!discard) + reg |= AST_VUART_GCRA_HOST_TX_DISCARD; + + writeb(reg, vuart->regs + AST_VUART_GCRA); +} + +static int ast_vuart_startup(struct uart_port *uart_port) +{ + struct uart_8250_port *uart_8250_port = up_to_u8250p(uart_port); + struct ast_vuart *vuart = uart_8250_port->port.private_data; + int rc; + + rc = serial8250_do_startup(uart_port); + if (rc) + return rc; + + ast_vuart_set_host_tx_discard(vuart, false); + + return 0; +} + +static void ast_vuart_shutdown(struct uart_port *uart_port) +{ + struct uart_8250_port *uart_8250_port = up_to_u8250p(uart_port); + struct ast_vuart *vuart = uart_8250_port->port.private_data; + + ast_vuart_set_host_tx_discard(vuart, true); + + serial8250_do_shutdown(uart_port); +} + + +/** + * The device tree parsing code here is heavily based on that of the of_serial + * driver, but we have a few core differences, as we need to use our own + * ioremapping for extra register support + */ +static int ast_vuart_probe(struct platform_device *pdev) +{ + struct uart_8250_port port; + struct resource resource; + struct ast_vuart *vuart; + struct device_node *np; + u32 clk, prop; + int rc; + + np = pdev->dev.of_node; + + vuart = devm_kzalloc(&pdev->dev, sizeof(*vuart), GFP_KERNEL); + if (!vuart) + return -ENOMEM; + + vuart->pdev = pdev; + rc = of_address_to_resource(np, 0, &resource); + if (rc) { + dev_warn(&pdev->dev, "invalid address\n"); + return rc; + } + + /* create our own mapping for VUART-specific registers */ + vuart->regs = devm_ioremap_resource(&pdev->dev, &resource); + if (IS_ERR(vuart->regs)) { + dev_warn(&pdev->dev, "failed to map registers\n"); + return PTR_ERR(vuart->regs); + } + + memset(&port, 0, sizeof(port)); + port.port.private_data = vuart; + port.port.membase = vuart->regs; + port.port.mapbase = resource.start; + port.port.mapsize = resource_size(&resource); + port.port.startup = ast_vuart_startup; + port.port.shutdown = ast_vuart_shutdown; + + if (of_property_read_u32(np, "clock-frequency", &clk)) { + vuart->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(vuart->clk)) { + dev_warn(&pdev->dev, + "clk or clock-frequency not defined\n"); + return PTR_ERR(vuart->clk); + } + + rc = clk_prepare_enable(vuart->clk); + if (rc < 0) + return rc; + + clk = clk_get_rate(vuart->clk); + } + + /* If current-speed was set, then try not to change it. */ + if (of_property_read_u32(np, "current-speed", &prop) == 0) + port.port.custom_divisor = clk / (16 * prop); + + /* Check for shifted address mapping */ + if (of_property_read_u32(np, "reg-offset", &prop) == 0) + port.port.mapbase += prop; + + /* Check for registers offset within the devices address range */ + if (of_property_read_u32(np, "reg-shift", &prop) == 0) + port.port.regshift = prop; + + /* Check for fifo size */ + if (of_property_read_u32(np, "fifo-size", &prop) == 0) + port.port.fifosize = prop; + + /* Check for a fixed line number */ + rc = of_alias_get_id(np, "serial"); + if (rc >= 0) + port.port.line = rc; + + port.port.irq = irq_of_parse_and_map(np, 0); + port.port.iotype = UPIO_MEM; + if (of_property_read_u32(np, "reg-io-width", &prop) == 0) { + switch (prop) { + case 1: + port.port.iotype = UPIO_MEM; + break; + case 4: + port.port.iotype = of_device_is_big_endian(np) ? + UPIO_MEM32BE : UPIO_MEM32; + break; + default: + dev_warn(&pdev->dev, "unsupported reg-io-width (%d)\n", + prop); + rc = -EINVAL; + goto err_clk_disable; + } + } + + port.port.type = PORT_16550A; + port.port.uartclk = clk; + port.port.flags = UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF + | UPF_FIXED_PORT | UPF_FIXED_TYPE; + + if (of_find_property(np, "no-loopback-test", NULL)) + port.port.flags |= UPF_SKIP_TEST; + + port.port.dev = &pdev->dev; + + if (port.port.fifosize) + port.capabilities = UART_CAP_FIFO; + + if (of_property_read_bool(pdev->dev.of_node, + "auto-flow-control")) + port.capabilities |= UART_CAP_AFE; + + rc = serial8250_register_8250_port(&port); + if (rc < 0) + goto err_clk_disable; + + + vuart->line = rc; + ast_vuart_set_enabled(vuart, true); + ast_vuart_set_host_tx_discard(vuart, true); + platform_set_drvdata(pdev, vuart); + + /* extra sysfs control */ + rc = device_create_file(&pdev->dev, &dev_attr_lpc_address); + if (rc) + dev_warn(&pdev->dev, "can't create lpc_address file\n"); + rc = device_create_file(&pdev->dev, &dev_attr_sirq); + if (rc) + dev_warn(&pdev->dev, "can't create sirq file\n"); + + return 0; + +err_clk_disable: + if (vuart->clk) + clk_disable_unprepare(vuart->clk); + + irq_dispose_mapping(port.port.irq); + return rc; +} + +static int ast_vuart_remove(struct platform_device *pdev) +{ + struct ast_vuart *vuart = platform_get_drvdata(pdev); + + ast_vuart_set_enabled(vuart, false); + + if (vuart->clk) + clk_disable_unprepare(vuart->clk); + return 0; +} + +static const struct of_device_id ast_vuart_table[] = { + { .compatible = "aspeed,vuart" }, + { }, +}; + +static struct platform_driver ast_vuart_driver = { + .driver = { + .name = "aspeed-vuart", + .of_match_table = ast_vuart_table, + }, + .probe = ast_vuart_probe, + .remove = ast_vuart_remove, +}; + +module_platform_driver(ast_vuart_driver); + +MODULE_AUTHOR("Jeremy Kerr <jk@ozlabs.org>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Driver for Aspeed VUART device"); diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1c427beffadd..48dddc7159e2 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -578,6 +578,16 @@ config LPC18XX_WATCHDOG To compile this driver as a module, choose M here: the module will be called lpc18xx_wdt. +config ASPEED_24xx_WATCHDOG + tristate "Aspeed 23xx 24xx SoCs watchdog support" + depends on (ARCH_ASPEED || COMPILE_TEST) && OF + select WATCHDOG_CORE + help + Say Y here to include support for the watchdog timer + in Apseed 23xx or 24xx BMC SoCs. + To compile this driver as a module, choose M here: the + module will be called aspeed_wdt. + # AVR32 Architecture config AT32AP700X_WDT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 53d4827ddfe1..f4c9e4f1d85c 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o obj-$(CONFIG_DIGICOLOR_WATCHDOG) += digicolor_wdt.o obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o +obj-$(CONFIG_ASPEED_24xx_WATCHDOG) += aspeed_wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c new file mode 100644 index 000000000000..a1ea4c286e6c --- /dev/null +++ b/drivers/watchdog/aspeed_wdt.c @@ -0,0 +1,224 @@ +/* + * Copyright 2015 IBM Corp. + * + * Joel Stanley <joel@jms.id.au> + * + * Based on the qcom-watchdog driver + * + * 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. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/reboot.h> +#include <linux/watchdog.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> + +struct aspeed_wdt { + struct watchdog_device wdd; + struct clk *clk; + unsigned long rate; + struct notifier_block restart_nb; + void __iomem *base; +}; + +static const struct of_device_id aspeed_wdt_of_table[] = { + { .compatible = "aspeed,wdt", }, + { }, +}; +MODULE_DEVICE_TABLE(of, aspeed_wdt_of_table); + +#define WDT_STATUS 0x00 +#define WDT_RELOAD_VALUE 0x04 +#define WDT_RESTART 0x08 +#define WDT_CTRL 0x0C +#define WDT_CTRL_RESET_MODE_SOC (0x00 << 5) +#define WDT_CTRL_RESET_MODE_FULL_CHIP (0x01 << 5) +#define WDT_CTRL_RESET_SYSTEM (0x1 << 1) +#define WDT_CTRL_ENABLE (0x1 << 0) + +#define WDT_RESTART_MAGIC 0x4755 + +static void aspeed_wdt_enable(struct aspeed_wdt *wdt, int count) +{ + u32 ctrl = WDT_CTRL_RESET_MODE_SOC | WDT_CTRL_RESET_SYSTEM | + WDT_CTRL_ENABLE; + + writel(0, wdt->base + WDT_CTRL); + writel(count, wdt->base + WDT_RELOAD_VALUE); + writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART); + writel(ctrl, wdt->base + WDT_CTRL); +} + +static int aspeed_wdt_start(struct watchdog_device *wdd) +{ + struct aspeed_wdt *wdt = container_of(wdd, struct aspeed_wdt, wdd); + dev_dbg(wdd->dev, "starting with timeout of %d (rate %lu)\n", + wdd->timeout, wdt->rate); + aspeed_wdt_enable(wdt, wdd->timeout * wdt->rate); + return 0; +} + +static int aspeed_wdt_stop(struct watchdog_device *wdd) +{ + struct aspeed_wdt *wdt = container_of(wdd, struct aspeed_wdt, wdd); + + writel(0, wdt->base + WDT_CTRL); + return 0; +} + +static int aspeed_wdt_ping(struct watchdog_device *wdd) +{ + struct aspeed_wdt *wdt = container_of(wdd, struct aspeed_wdt, wdd); + + dev_dbg(wdd->dev, "ping\n"); + writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART); + return 0; +} + +static int aspeed_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + dev_dbg(wdd->dev, "timeout set to %u\n", timeout); + wdd->timeout = timeout; + return aspeed_wdt_start(wdd); +} + +static int aspeed_wdt_restart(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct aspeed_wdt *wdt = container_of(nb, + struct aspeed_wdt, restart_nb); + + /* + * Trigger watchdog bite: + * Setup reload count to be 128ms, and enable WDT. + */ + aspeed_wdt_enable(wdt, 128 * wdt->rate / 1000); + + return NOTIFY_DONE; +} + +static const struct watchdog_ops aspeed_wdt_ops = { + .start = aspeed_wdt_start, + .stop = aspeed_wdt_stop, + .ping = aspeed_wdt_ping, + .set_timeout = aspeed_wdt_set_timeout, + .owner = THIS_MODULE, +}; + +static const struct watchdog_info aspeed_wdt_info = { + .options = WDIOF_KEEPALIVEPING + | WDIOF_MAGICCLOSE + | WDIOF_SETTIMEOUT, + .identity = KBUILD_MODNAME, +}; + +static int aspeed_wdt_remove(struct platform_device *pdev) +{ + struct aspeed_wdt *wdt = platform_get_drvdata(pdev); + + unregister_restart_handler(&wdt->restart_nb); + watchdog_unregister_device(&wdt->wdd); + clk_disable_unprepare(wdt->clk); + return 0; +} + +static int aspeed_wdt_probe(struct platform_device *pdev) +{ + struct aspeed_wdt *wdt; + struct resource *res; + int ret; + + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); + + wdt->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(wdt->clk)) { + dev_err(&pdev->dev, "failed to get input clock\n"); + return PTR_ERR(wdt->clk); + } + + ret = clk_prepare_enable(wdt->clk); + if (ret) { + dev_err(&pdev->dev, "failed to setup clock\n"); + goto err; + } + + /* + * We use the clock rate to calculate the max timeout, so ensure it's + * not zero to avoid a divide-by-zero exception. + * + * WATCHDOG_CORE assumes units of seconds, if the WDT is clocked such + * that it would bite before a second elapses it's usefulness is + * limited. Bail if this is the case. + */ + wdt->rate = clk_get_rate(wdt->clk); + if (wdt->rate == 0 || + wdt->rate > 0x10000000U) { + dev_err(&pdev->dev, "invalid clock rate\n"); + ret = -EINVAL; + goto err; + } + + wdt->wdd.dev = &pdev->dev; + wdt->wdd.info = &aspeed_wdt_info; + wdt->wdd.ops = &aspeed_wdt_ops; + wdt->wdd.min_timeout = 1; + wdt->wdd.max_timeout = 0x10000000U / wdt->rate; + + /* + * If 'timeout-sec' unspecified in devicetree, assume a 30 second + * default, unless the max timeout is less than 30 seconds, then use + * the max instead. + */ + wdt->wdd.timeout = min(wdt->wdd.max_timeout, 30U); + watchdog_init_timeout(&wdt->wdd, 0, &pdev->dev); + + ret = watchdog_register_device(&wdt->wdd); + if (ret) { + dev_err(&pdev->dev, "failed to register\n"); + goto err; + } + + /* + * WDT restart notifier has priority 0 (use as a last resort) + */ + wdt->restart_nb.notifier_call = aspeed_wdt_restart; + ret = register_restart_handler(&wdt->restart_nb); + if (ret) + dev_err(&pdev->dev, "failed to setup restart handler\n"); + + dev_info(&pdev->dev, "rate %lu, max timeout %u, timeout %d\n", + wdt->rate, wdt->wdd.max_timeout, wdt->wdd.timeout); + + platform_set_drvdata(pdev, wdt); + return 0; + +err: + clk_disable_unprepare(wdt->clk); + return ret; +} + +static struct platform_driver aspeed_watchdog_driver = { + .probe = aspeed_wdt_probe, + .remove = aspeed_wdt_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = of_match_ptr(aspeed_wdt_of_table), + }, +}; +module_platform_driver(aspeed_watchdog_driver); + +MODULE_DESCRIPTION("Aspeed AST23/4xx Watchdog Driver"); |