| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Marvell 88E6xxx Switch Global 2 Scratch & Misc Registers support |
| * |
| * Copyright (c) 2008 Marvell Semiconductor |
| * |
| * Copyright (c) 2017 National Instruments |
| * Brandon Streiff <brandon.streiff@ni.com> |
| */ |
| |
| #include "chip.h" |
| #include "global2.h" |
| |
| /* Offset 0x1A: Scratch and Misc. Register */ |
| static int mv88e6xxx_g2_scratch_read(struct mv88e6xxx_chip *chip, int reg, |
| u8 *data) |
| { |
| u16 value; |
| int err; |
| |
| err = mv88e6xxx_g2_write(chip, MV88E6XXX_G2_SCRATCH_MISC_MISC, |
| reg << 8); |
| if (err) |
| return err; |
| |
| err = mv88e6xxx_g2_read(chip, MV88E6XXX_G2_SCRATCH_MISC_MISC, &value); |
| if (err) |
| return err; |
| |
| *data = (value & MV88E6XXX_G2_SCRATCH_MISC_DATA_MASK); |
| |
| return 0; |
| } |
| |
| static int mv88e6xxx_g2_scratch_write(struct mv88e6xxx_chip *chip, int reg, |
| u8 data) |
| { |
| u16 value = (reg << 8) | data; |
| |
| return mv88e6xxx_g2_write(chip, MV88E6XXX_G2_SCRATCH_MISC_MISC, |
| MV88E6XXX_G2_SCRATCH_MISC_UPDATE | value); |
| } |
| |
| /** |
| * mv88e6xxx_g2_scratch_get_bit - get a bit |
| * @chip: chip private data |
| * @base_reg: base of scratch bits |
| * @offset: index of bit within the register |
| * @set: is bit set? |
| */ |
| static int mv88e6xxx_g2_scratch_get_bit(struct mv88e6xxx_chip *chip, |
| int base_reg, unsigned int offset, |
| int *set) |
| { |
| int reg = base_reg + (offset / 8); |
| u8 mask = (1 << (offset & 0x7)); |
| u8 val; |
| int err; |
| |
| err = mv88e6xxx_g2_scratch_read(chip, reg, &val); |
| if (err) |
| return err; |
| |
| *set = !!(mask & val); |
| |
| return 0; |
| } |
| |
| /** |
| * mv88e6xxx_g2_scratch_set_bit - set (or clear) a bit |
| * @chip: chip private data |
| * @base_reg: base of scratch bits |
| * @offset: index of bit within the register |
| * @set: should this bit be set? |
| * |
| * Helper function for dealing with the direction and data registers. |
| */ |
| static int mv88e6xxx_g2_scratch_set_bit(struct mv88e6xxx_chip *chip, |
| int base_reg, unsigned int offset, |
| int set) |
| { |
| int reg = base_reg + (offset / 8); |
| u8 mask = (1 << (offset & 0x7)); |
| u8 val; |
| int err; |
| |
| err = mv88e6xxx_g2_scratch_read(chip, reg, &val); |
| if (err) |
| return err; |
| |
| if (set) |
| val |= mask; |
| else |
| val &= ~mask; |
| |
| return mv88e6xxx_g2_scratch_write(chip, reg, val); |
| } |
| |
| /** |
| * mv88e6352_g2_scratch_gpio_get_data - get data on gpio pin |
| * @chip: chip private data |
| * @pin: gpio index |
| * |
| * Return: 0 for low, 1 for high, negative error |
| */ |
| static int mv88e6352_g2_scratch_gpio_get_data(struct mv88e6xxx_chip *chip, |
| unsigned int pin) |
| { |
| int val = 0; |
| int err; |
| |
| err = mv88e6xxx_g2_scratch_get_bit(chip, |
| MV88E6352_G2_SCRATCH_GPIO_DATA0, |
| pin, &val); |
| if (err) |
| return err; |
| |
| return val; |
| } |
| |
| /** |
| * mv88e6352_g2_scratch_gpio_set_data - set data on gpio pin |
| * @chip: chip private data |
| * @pin: gpio index |
| * @value: value to set |
| */ |
| static int mv88e6352_g2_scratch_gpio_set_data(struct mv88e6xxx_chip *chip, |
| unsigned int pin, int value) |
| { |
| u8 mask = (1 << (pin & 0x7)); |
| int offset = (pin / 8); |
| int reg; |
| |
| reg = MV88E6352_G2_SCRATCH_GPIO_DATA0 + offset; |
| |
| if (value) |
| chip->gpio_data[offset] |= mask; |
| else |
| chip->gpio_data[offset] &= ~mask; |
| |
| return mv88e6xxx_g2_scratch_write(chip, reg, chip->gpio_data[offset]); |
| } |
| |
| /** |
| * mv88e6352_g2_scratch_gpio_get_dir - get direction of gpio pin |
| * @chip: chip private data |
| * @pin: gpio index |
| * |
| * Return: 0 for output, 1 for input (same as GPIOF_DIR_XXX). |
| */ |
| static int mv88e6352_g2_scratch_gpio_get_dir(struct mv88e6xxx_chip *chip, |
| unsigned int pin) |
| { |
| int val = 0; |
| int err; |
| |
| err = mv88e6xxx_g2_scratch_get_bit(chip, |
| MV88E6352_G2_SCRATCH_GPIO_DIR0, |
| pin, &val); |
| if (err) |
| return err; |
| |
| return val; |
| } |
| |
| /** |
| * mv88e6352_g2_scratch_gpio_set_dir - set direction of gpio pin |
| * @chip: chip private data |
| * @pin: gpio index |
| * @input: should the gpio be an input, or an output? |
| */ |
| static int mv88e6352_g2_scratch_gpio_set_dir(struct mv88e6xxx_chip *chip, |
| unsigned int pin, bool input) |
| { |
| int value = (input ? MV88E6352_G2_SCRATCH_GPIO_DIR_IN : |
| MV88E6352_G2_SCRATCH_GPIO_DIR_OUT); |
| |
| return mv88e6xxx_g2_scratch_set_bit(chip, |
| MV88E6352_G2_SCRATCH_GPIO_DIR0, |
| pin, value); |
| } |
| |
| /** |
| * mv88e6352_g2_scratch_gpio_get_pctl - get pin control setting |
| * @chip: chip private data |
| * @pin: gpio index |
| * @func: function number |
| * |
| * Note that the function numbers themselves may vary by chipset. |
| */ |
| static int mv88e6352_g2_scratch_gpio_get_pctl(struct mv88e6xxx_chip *chip, |
| unsigned int pin, int *func) |
| { |
| int reg = MV88E6352_G2_SCRATCH_GPIO_PCTL0 + (pin / 2); |
| int offset = (pin & 0x1) ? 4 : 0; |
| u8 mask = (0x7 << offset); |
| int err; |
| u8 val; |
| |
| err = mv88e6xxx_g2_scratch_read(chip, reg, &val); |
| if (err) |
| return err; |
| |
| *func = (val & mask) >> offset; |
| |
| return 0; |
| } |
| |
| /** |
| * mv88e6352_g2_scratch_gpio_set_pctl - set pin control setting |
| * @chip: chip private data |
| * @pin: gpio index |
| * @func: function number |
| */ |
| static int mv88e6352_g2_scratch_gpio_set_pctl(struct mv88e6xxx_chip *chip, |
| unsigned int pin, int func) |
| { |
| int reg = MV88E6352_G2_SCRATCH_GPIO_PCTL0 + (pin / 2); |
| int offset = (pin & 0x1) ? 4 : 0; |
| u8 mask = (0x7 << offset); |
| int err; |
| u8 val; |
| |
| err = mv88e6xxx_g2_scratch_read(chip, reg, &val); |
| if (err) |
| return err; |
| |
| val = (val & ~mask) | ((func & mask) << offset); |
| |
| return mv88e6xxx_g2_scratch_write(chip, reg, val); |
| } |
| |
| const struct mv88e6xxx_gpio_ops mv88e6352_gpio_ops = { |
| .get_data = mv88e6352_g2_scratch_gpio_get_data, |
| .set_data = mv88e6352_g2_scratch_gpio_set_data, |
| .get_dir = mv88e6352_g2_scratch_gpio_get_dir, |
| .set_dir = mv88e6352_g2_scratch_gpio_set_dir, |
| .get_pctl = mv88e6352_g2_scratch_gpio_get_pctl, |
| .set_pctl = mv88e6352_g2_scratch_gpio_set_pctl, |
| }; |
| |
| /** |
| * mv88e6390_g2_scratch_gpio_set_smi - set gpio muxing for external smi |
| * @chip: chip private data |
| * @external: set mux for external smi, or free for gpio usage |
| * |
| * Some mv88e6xxx models have GPIO pins that may be configured as |
| * an external SMI interface, or they may be made free for other |
| * GPIO uses. |
| */ |
| int mv88e6390_g2_scratch_gpio_set_smi(struct mv88e6xxx_chip *chip, |
| bool external) |
| { |
| int misc_cfg = MV88E6352_G2_SCRATCH_MISC_CFG; |
| int config_data1 = MV88E6352_G2_SCRATCH_CONFIG_DATA1; |
| int config_data2 = MV88E6352_G2_SCRATCH_CONFIG_DATA2; |
| bool no_cpu; |
| u8 p0_mode; |
| int err; |
| u8 val; |
| |
| err = mv88e6xxx_g2_scratch_read(chip, config_data2, &val); |
| if (err) |
| return err; |
| |
| p0_mode = val & MV88E6352_G2_SCRATCH_CONFIG_DATA2_P0_MODE_MASK; |
| |
| if (p0_mode == 0x01 || p0_mode == 0x02) |
| return -EBUSY; |
| |
| err = mv88e6xxx_g2_scratch_read(chip, config_data1, &val); |
| if (err) |
| return err; |
| |
| no_cpu = !!(val & MV88E6352_G2_SCRATCH_CONFIG_DATA1_NO_CPU); |
| |
| err = mv88e6xxx_g2_scratch_read(chip, misc_cfg, &val); |
| if (err) |
| return err; |
| |
| /* NO_CPU being 0 inverts the meaning of the bit */ |
| if (!no_cpu) |
| external = !external; |
| |
| if (external) |
| val |= MV88E6352_G2_SCRATCH_MISC_CFG_NORMALSMI; |
| else |
| val &= ~MV88E6352_G2_SCRATCH_MISC_CFG_NORMALSMI; |
| |
| return mv88e6xxx_g2_scratch_write(chip, misc_cfg, val); |
| } |
| |
| /** |
| * mv88e6393x_g2_scratch_gpio_set_smi - set gpio muxing for external smi |
| * @chip: chip private data |
| * @external: set mux for external smi, or free for gpio usage |
| * |
| * MV88E6191X/6193X/6393X GPIO pins 9 and 10 can be configured as an |
| * external SMI interface or as regular GPIO-s. |
| * |
| * They however have a different register layout then the existing |
| * function. |
| */ |
| |
| int mv88e6393x_g2_scratch_gpio_set_smi(struct mv88e6xxx_chip *chip, |
| bool external) |
| { |
| int misc_cfg = MV88E6352_G2_SCRATCH_MISC_CFG; |
| int err; |
| u8 val; |
| |
| err = mv88e6xxx_g2_scratch_read(chip, misc_cfg, &val); |
| if (err) |
| return err; |
| |
| if (external) |
| val &= ~MV88E6352_G2_SCRATCH_MISC_CFG_NORMALSMI; |
| else |
| val |= MV88E6352_G2_SCRATCH_MISC_CFG_NORMALSMI; |
| |
| return mv88e6xxx_g2_scratch_write(chip, misc_cfg, val); |
| } |
| |
| /** |
| * mv88e6352_g2_scratch_port_has_serdes - indicate if a port can have a serdes |
| * @chip: chip private data |
| * @port: port number to check for serdes |
| * |
| * Indicates whether the port may have a serdes attached according to the |
| * pin strapping. Returns negative error number, 0 if the port is not |
| * configured to have a serdes, and 1 if the port is configured to have a |
| * serdes attached. |
| */ |
| int mv88e6352_g2_scratch_port_has_serdes(struct mv88e6xxx_chip *chip, int port) |
| { |
| u8 config3, p; |
| int err; |
| |
| err = mv88e6xxx_g2_scratch_read(chip, MV88E6352_G2_SCRATCH_CONFIG_DATA3, |
| &config3); |
| if (err) |
| return err; |
| |
| if (config3 & MV88E6352_G2_SCRATCH_CONFIG_DATA3_S_SEL) |
| p = 5; |
| else |
| p = 4; |
| |
| return port == p; |
| } |