diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-03 08:06:56 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-03 08:06:56 -0700 |
commit | 4046136afbd1038d776bad9c59e1e4cca78186fb (patch) | |
tree | 1888ca7bd978c0bba891ac9ee51224fd06d1162e | |
parent | b55a0ff8df92646696c858a8fea4dbf38509f202 (diff) | |
parent | a100d88df1e924e5c9678fabf054d1bae7ab74fb (diff) | |
download | op-kernel-dev-4046136afbd1038d776bad9c59e1e4cca78186fb.zip op-kernel-dev-4046136afbd1038d776bad9c59e1e4cca78186fb.tar.gz |
Merge tag 'char-misc-3.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc into next
Pull char/misc driver patches from Greg KH:
"Here is the big char / misc driver update for 3.16-rc1.
Lots of different driver updates for a variety of different drivers
and minor driver subsystems.
All have been in linux-next with no reported issues"
* tag 'char-misc-3.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (79 commits)
hv: use correct order when freeing monitor_pages
spmi: of: fixup generic SPMI devicetree binding example
applicom: dereferencing NULL on error path
misc: genwqe: fix uninitialized return value in genwqe_free_sync_sgl()
miscdevice.h: Simple syntax fix to make pointers consistent.
MAINTAINERS: Add miscdevice.h to file list for char/misc drivers.
mcb: Add support for shared PCI IRQs
drivers: Remove duplicate conditionally included subdirs
misc: atmel_pwm: only build for supported platforms
mei: me: move probe quirk to cfg structure
mei: add per device configuration
mei: me: read H_CSR after asserting reset
mei: me: drop harmful wait optimization
mei: me: fix hw ready reset flow
mei: fix memory leak of mei_clients array
uio: fix vma io range check in mmap
drivers: uio_dmem_genirq: Fix memory leak in uio_dmem_genirq_probe()
w1: do not unlock unheld list_mutex in __w1_remove_master_device()
w1: optional bundling of netlink kernel replies
connector: allow multiple messages to be sent in one packet
...
68 files changed, 2931 insertions, 787 deletions
diff --git a/Documentation/connector/connector.txt b/Documentation/connector/connector.txt index e5c5f5e..f6215f9 100644 --- a/Documentation/connector/connector.txt +++ b/Documentation/connector/connector.txt @@ -24,7 +24,8 @@ netlink based networking for inter-process communication in a significantly easier way: int cn_add_callback(struct cb_id *id, char *name, void (*callback) (struct cn_msg *, struct netlink_skb_parms *)); -void cn_netlink_send(struct cn_msg *msg, u32 __group, int gfp_mask); +void cn_netlink_send_multi(struct cn_msg *msg, u16 len, u32 portid, u32 __group, int gfp_mask); +void cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, int gfp_mask); struct cb_id { @@ -71,15 +72,21 @@ void cn_del_callback(struct cb_id *id); struct cb_id *id - unique connector's user identifier. -int cn_netlink_send(struct cn_msg *msg, u32 __groups, int gfp_mask); +int cn_netlink_send_multi(struct cn_msg *msg, u16 len, u32 portid, u32 __groups, int gfp_mask); +int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __groups, int gfp_mask); Sends message to the specified groups. It can be safely called from softirq context, but may silently fail under strong memory pressure. If there are no listeners for given group -ESRCH can be returned. struct cn_msg * - message header(with attached data). + u16 len - for *_multi multiple cn_msg messages can be sent + u32 port - destination port. + If non-zero the message will be sent to the + given port, which should be set to the + original sender. u32 __group - destination group. - If __group is zero, then appropriate group will + If port and __group is zero, then appropriate group will be searched through all registered connector users, and message will be delivered to the group which was created for user with the same ID as in msg. @@ -111,7 +118,7 @@ acknowledge number MUST be the same + 1. If we receive a message and its sequence number is not equal to one we are expecting, then it is a new message. If we receive a message and its sequence number is the same as one we are expecting, but its -acknowledge is not equal to the acknowledge number in the original +acknowledge is not equal to the sequence number in the original message + 1, then it is a new message. Obviously, the protocol header contains the above id. diff --git a/Documentation/devicetree/bindings/misc/arm-charlcd.txt b/Documentation/devicetree/bindings/misc/arm-charlcd.txt new file mode 100644 index 0000000..e28e2aa --- /dev/null +++ b/Documentation/devicetree/bindings/misc/arm-charlcd.txt @@ -0,0 +1,18 @@ +ARM Versatile Character LCD +----------------------------------------------------- +This binding defines the character LCD interface found on ARM Versatile AB +and PB reference platforms. + +Required properties: +- compatible : "arm,versatile-clcd" +- reg : Location and size of character LCD registers + +Optional properties: +- interrupts - single interrupt for character LCD. The character LCD can + operate in polled mode without an interrupt. + +Example: + lcd@10008000 { + compatible = "arm,versatile-lcd"; + reg = <0x10008000 0x1000>; + }; diff --git a/Documentation/devicetree/bindings/spmi/spmi.txt b/Documentation/devicetree/bindings/spmi/spmi.txt index 462a42f..4bb10d1 100644 --- a/Documentation/devicetree/bindings/spmi/spmi.txt +++ b/Documentation/devicetree/bindings/spmi/spmi.txt @@ -26,7 +26,7 @@ Each child node must have one and only one 'reg' entry of type SPMI_USID. reg = <...>; #address-cells = <2>; - #size-cells <0>; + #size-cells = <0>; child@0 { compatible = "..."; diff --git a/Documentation/w1/w1.generic b/Documentation/w1/w1.generic index a31c5a2..b2033c6 100644 --- a/Documentation/w1/w1.generic +++ b/Documentation/w1/w1.generic @@ -82,7 +82,7 @@ driver - (standard) symlink to the w1 driver w1_master_add - Manually register a slave device w1_master_attempts - the number of times a search was attempted w1_master_max_slave_count - - the maximum slaves that may be attached to a master + - maximum number of slaves to search for at a time w1_master_name - the name of the device (w1_bus_masterX) w1_master_pullup - 5V strong pullup 0 enabled, 1 disabled w1_master_remove - Manually remove a slave device diff --git a/Documentation/w1/w1.netlink b/Documentation/w1/w1.netlink index 927a52c..ef27271 100644 --- a/Documentation/w1/w1.netlink +++ b/Documentation/w1/w1.netlink @@ -30,7 +30,7 @@ Protocol. W1_SLAVE_CMD userspace command for slave device (read/write/touch) - __u8 res - reserved + __u8 status - error indication from kernel __u16 len - size of data attached to this header data union { __u8 id[8]; - slave unique device id @@ -44,10 +44,14 @@ Protocol. __u8 cmd - command opcode. W1_CMD_READ - read command W1_CMD_WRITE - write command - W1_CMD_TOUCH - touch command - (write and sample data back to userspace) W1_CMD_SEARCH - search command W1_CMD_ALARM_SEARCH - alarm search command + W1_CMD_TOUCH - touch command + (write and sample data back to userspace) + W1_CMD_RESET - send bus reset + W1_CMD_SLAVE_ADD - add slave to kernel list + W1_CMD_SLAVE_REMOVE - remove slave from kernel list + W1_CMD_LIST_SLAVES - get slaves list from kernel __u8 res - reserved __u16 len - length of data for this command For read command data must be allocated like for write command @@ -87,8 +91,7 @@ format: id0 ... idN Each message is at most 4k in size, so if number of master devices - exceeds this, it will be split into several messages, - cn.seq will be increased for each one. + exceeds this, it will be split into several messages. W1 search and alarm search commands. request: diff --git a/MAINTAINERS b/MAINTAINERS index a4fe5ee..54b80d5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2188,6 +2188,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git S: Supported F: drivers/char/* F: drivers/misc/* +F: include/linux/miscdevice.h CHECKPATCH M: Andy Whitcroft <apw@canonical.com> diff --git a/drivers/Makefile b/drivers/Makefile index 1a1790e..f98b50d 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -83,7 +83,6 @@ obj-$(CONFIG_PCCARD) += pcmcia/ obj-$(CONFIG_DIO) += dio/ obj-$(CONFIG_SBUS) += sbus/ obj-$(CONFIG_ZORRO) += zorro/ -obj-$(CONFIG_MAC) += macintosh/ obj-$(CONFIG_ATA_OVER_ETH) += block/aoe/ obj-$(CONFIG_PARIDE) += block/paride/ obj-$(CONFIG_TC) += tc/ @@ -141,7 +140,6 @@ obj-y += clk/ obj-$(CONFIG_MAILBOX) += mailbox/ obj-$(CONFIG_HWSPINLOCK) += hwspinlock/ -obj-$(CONFIG_NFC) += nfc/ obj-$(CONFIG_IOMMU_SUPPORT) += iommu/ obj-$(CONFIG_REMOTEPROC) += remoteproc/ obj-$(CONFIG_RPMSG) += rpmsg/ diff --git a/drivers/char/applicom.c b/drivers/char/applicom.c index 974321a..1479030 100644 --- a/drivers/char/applicom.c +++ b/drivers/char/applicom.c @@ -345,7 +345,6 @@ out: free_irq(apbs[i].irq, &dummy); iounmap(apbs[i].RamIO); } - pci_disable_device(dev); return ret; } diff --git a/drivers/connector/connector.c b/drivers/connector/connector.c index b14f1d3..f612d68 100644 --- a/drivers/connector/connector.c +++ b/drivers/connector/connector.c @@ -43,6 +43,8 @@ static struct cn_dev cdev; static int cn_already_initialized; /* + * Sends mult (multiple) cn_msg at a time. + * * msg->seq and msg->ack are used to determine message genealogy. * When someone sends message it puts there locally unique sequence * and random acknowledge numbers. Sequence number may be copied into @@ -62,10 +64,13 @@ static int cn_already_initialized; * the acknowledgement number in the original message + 1, then it is * a new message. * + * If msg->len != len, then additional cn_msg messages are expected following + * the first msg. + * * The message is sent to, the portid if given, the group if given, both if * both, or if both are zero then the group is looked up and sent there. */ -int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, +int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group, gfp_t gfp_mask) { struct cn_callback_entry *__cbq; @@ -98,7 +103,7 @@ int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, if (!portid && !netlink_has_listeners(dev->nls, group)) return -ESRCH; - size = sizeof(*msg) + msg->len; + size = sizeof(*msg) + len; skb = nlmsg_new(size, gfp_mask); if (!skb) @@ -121,6 +126,14 @@ int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, gfp_mask); return netlink_unicast(dev->nls, skb, portid, !(gfp_mask&__GFP_WAIT)); } +EXPORT_SYMBOL_GPL(cn_netlink_send_mult); + +/* same as cn_netlink_send_mult except msg->len is used for len */ +int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, + gfp_t gfp_mask) +{ + return cn_netlink_send_mult(msg, msg->len, portid, __group, gfp_mask); +} EXPORT_SYMBOL_GPL(cn_netlink_send); /* diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index be56e8a..aebde48 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -28,13 +28,13 @@ config EXTCON_ADC_JACK Say Y here to enable extcon device driver based on ADC values. config EXTCON_MAX14577 - tristate "MAX14577 EXTCON Support" + tristate "MAX14577/77836 EXTCON Support" depends on MFD_MAX14577 select IRQ_DOMAIN select REGMAP_I2C help If you say yes here you get support for the MUIC device of - Maxim MAX14577 PMIC. The MAX14577 MUIC is a USB port accessory + Maxim MAX14577/77836. The MAX14577/77836 MUIC is a USB port accessory detector and switch. config EXTCON_MAX77693 diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c index e23f1c2..e18f95b 100644 --- a/drivers/extcon/extcon-adc-jack.c +++ b/drivers/extcon/extcon-adc-jack.c @@ -39,7 +39,7 @@ * @chan: iio channel being queried. */ struct adc_jack_data { - struct extcon_dev edev; + struct extcon_dev *edev; const char **cable_names; int num_cables; @@ -64,7 +64,7 @@ static void adc_jack_handler(struct work_struct *work) ret = iio_read_channel_raw(data->chan, &adc_val); if (ret < 0) { - dev_err(&data->edev.dev, "read channel() error: %d\n", ret); + dev_err(&data->edev->dev, "read channel() error: %d\n", ret); return; } @@ -80,7 +80,7 @@ static void adc_jack_handler(struct work_struct *work) } /* if no def has met, it means state = 0 (no cables attached) */ - extcon_set_state(&data->edev, state); + extcon_set_state(data->edev, state); } static irqreturn_t adc_jack_irq_thread(int irq, void *_data) @@ -102,33 +102,33 @@ static int adc_jack_probe(struct platform_device *pdev) if (!data) return -ENOMEM; - data->edev.name = pdata->name; - if (!pdata->cable_names) { - err = -EINVAL; dev_err(&pdev->dev, "error: cable_names not defined.\n"); - goto out; + return -EINVAL; } - data->edev.dev.parent = &pdev->dev; - data->edev.supported_cable = pdata->cable_names; + data->edev = devm_extcon_dev_allocate(&pdev->dev, pdata->cable_names); + if (IS_ERR(data->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + data->edev->dev.parent = &pdev->dev; + data->edev->name = pdata->name; /* Check the length of array and set num_cables */ - for (i = 0; data->edev.supported_cable[i]; i++) + for (i = 0; data->edev->supported_cable[i]; i++) ; if (i == 0 || i > SUPPORTED_CABLE_MAX) { - err = -EINVAL; dev_err(&pdev->dev, "error: pdata->cable_names size = %d\n", i - 1); - goto out; + return -EINVAL; } data->num_cables = i; if (!pdata->adc_conditions || !pdata->adc_conditions[0].state) { - err = -EINVAL; dev_err(&pdev->dev, "error: adc_conditions not defined.\n"); - goto out; + return -EINVAL; } data->adc_conditions = pdata->adc_conditions; @@ -138,10 +138,8 @@ static int adc_jack_probe(struct platform_device *pdev) data->num_conditions = i; data->chan = iio_channel_get(&pdev->dev, pdata->consumer_channel); - if (IS_ERR(data->chan)) { - err = PTR_ERR(data->chan); - goto out; - } + if (IS_ERR(data->chan)) + return PTR_ERR(data->chan); data->handling_delay = msecs_to_jiffies(pdata->handling_delay_ms); @@ -149,15 +147,14 @@ static int adc_jack_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); - err = extcon_dev_register(&data->edev); + err = devm_extcon_dev_register(&pdev->dev, data->edev); if (err) - goto out; + return err; data->irq = platform_get_irq(pdev, 0); if (!data->irq) { dev_err(&pdev->dev, "platform_get_irq failed\n"); - err = -ENODEV; - goto err_irq; + return -ENODEV; } err = request_any_context_irq(data->irq, adc_jack_irq_thread, @@ -165,15 +162,10 @@ static int adc_jack_probe(struct platform_device *pdev) if (err < 0) { dev_err(&pdev->dev, "error: irq %d\n", data->irq); - goto err_irq; + return err; } return 0; - -err_irq: - extcon_dev_unregister(&data->edev); -out: - return err; } static int adc_jack_remove(struct platform_device *pdev) @@ -182,7 +174,6 @@ static int adc_jack_remove(struct platform_device *pdev) free_irq(data->irq, data); cancel_work_sync(&data->handler.work); - extcon_dev_unregister(&data->edev); return 0; } diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 98a14f6..6c84e3d 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -91,7 +91,7 @@ struct arizona_extcon_info { int hpdet_ip; - struct extcon_dev edev; + struct extcon_dev *edev; }; static const struct arizona_micd_config micd_default_modes[] = { @@ -546,7 +546,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) } /* If the cable was removed while measuring ignore the result */ - ret = extcon_get_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL); + ret = extcon_get_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL); if (ret < 0) { dev_err(arizona->dev, "Failed to check cable state: %d\n", ret); @@ -581,7 +581,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) else report = ARIZONA_CABLE_HEADPHONE; - ret = extcon_set_cable_state_(&info->edev, report, true); + ret = extcon_set_cable_state_(info->edev, report, true); if (ret != 0) dev_err(arizona->dev, "Failed to report HP/line: %d\n", ret); @@ -664,7 +664,7 @@ err: ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); /* Just report headphone */ - ret = extcon_update_state(&info->edev, + ret = extcon_update_state(info->edev, 1 << ARIZONA_CABLE_HEADPHONE, 1 << ARIZONA_CABLE_HEADPHONE); if (ret != 0) @@ -723,7 +723,7 @@ err: ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); /* Just report headphone */ - ret = extcon_update_state(&info->edev, + ret = extcon_update_state(info->edev, 1 << ARIZONA_CABLE_HEADPHONE, 1 << ARIZONA_CABLE_HEADPHONE); if (ret != 0) @@ -764,7 +764,7 @@ static void arizona_micd_detect(struct work_struct *work) mutex_lock(&info->lock); /* If the cable was removed while measuring ignore the result */ - ret = extcon_get_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL); + ret = extcon_get_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL); if (ret < 0) { dev_err(arizona->dev, "Failed to check cable state: %d\n", ret); @@ -812,7 +812,7 @@ static void arizona_micd_detect(struct work_struct *work) if (info->detecting && (val & ARIZONA_MICD_LVL_8)) { arizona_identify_headphone(info); - ret = extcon_update_state(&info->edev, + ret = extcon_update_state(info->edev, 1 << ARIZONA_CABLE_MICROPHONE, 1 << ARIZONA_CABLE_MICROPHONE); @@ -999,7 +999,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) if (info->last_jackdet == present) { dev_dbg(arizona->dev, "Detected jack\n"); - ret = extcon_set_cable_state_(&info->edev, + ret = extcon_set_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL, true); if (ret != 0) @@ -1038,7 +1038,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) info->micd_ranges[i].key, 0); input_sync(info->input); - ret = extcon_update_state(&info->edev, 0xffffffff, 0); + ret = extcon_update_state(info->edev, 0xffffffff, 0); if (ret != 0) dev_err(arizona->dev, "Removal report failed: %d\n", ret); @@ -1105,15 +1105,14 @@ static int arizona_extcon_probe(struct platform_device *pdev) info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) { dev_err(&pdev->dev, "Failed to allocate memory\n"); - ret = -ENOMEM; - goto err; + return -ENOMEM; } info->micvdd = devm_regulator_get(arizona->dev, "MICVDD"); if (IS_ERR(info->micvdd)) { ret = PTR_ERR(info->micvdd); dev_err(arizona->dev, "Failed to get MICVDD: %d\n", ret); - goto err; + return ret; } mutex_init(&info->lock); @@ -1151,15 +1150,19 @@ static int arizona_extcon_probe(struct platform_device *pdev) break; } - info->edev.name = "Headset Jack"; - info->edev.dev.parent = arizona->dev; - info->edev.supported_cable = arizona_cable; + info->edev = devm_extcon_dev_allocate(&pdev->dev, arizona_cable); + if (IS_ERR(info->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + info->edev->name = "Headset Jack"; + info->edev->dev.parent = arizona->dev; - ret = extcon_dev_register(&info->edev); + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret < 0) { dev_err(arizona->dev, "extcon_dev_register() failed: %d\n", ret); - goto err; + return ret; } info->input = devm_input_allocate_device(&pdev->dev); @@ -1410,8 +1413,6 @@ err_rise: err_input: err_register: pm_runtime_disable(&pdev->dev); - extcon_dev_unregister(&info->edev); -err: return ret; } @@ -1445,7 +1446,6 @@ static int arizona_extcon_remove(struct platform_device *pdev) regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, ARIZONA_JD1_ENA, 0); arizona_clk32k_disable(arizona); - extcon_dev_unregister(&info->edev); return 0; } diff --git a/drivers/extcon/extcon-class.c b/drivers/extcon/extcon-class.c index 7ab21aa..18d42c0 100644 --- a/drivers/extcon/extcon-class.c +++ b/drivers/extcon/extcon-class.c @@ -565,6 +565,100 @@ static void dummy_sysfs_dev_release(struct device *dev) { } +/* + * extcon_dev_allocate() - Allocate the memory of extcon device. + * @supported_cable: Array of supported cable names ending with NULL. + * If supported_cable is NULL, cable name related APIs + * are disabled. + * + * This function allocates the memory for extcon device without allocating + * memory in each extcon provider driver and initialize default setting for + * extcon device. + * + * Return the pointer of extcon device if success or ERR_PTR(err) if fail + */ +struct extcon_dev *extcon_dev_allocate(const char **supported_cable) +{ + struct extcon_dev *edev; + + edev = kzalloc(sizeof(*edev), GFP_KERNEL); + if (!edev) + return ERR_PTR(-ENOMEM); + + edev->max_supported = 0; + edev->supported_cable = supported_cable; + + return edev; +} + +/* + * extcon_dev_free() - Free the memory of extcon device. + * @edev: the extcon device to free + */ +void extcon_dev_free(struct extcon_dev *edev) +{ + kfree(edev); +} +EXPORT_SYMBOL_GPL(extcon_dev_free); + +static int devm_extcon_dev_match(struct device *dev, void *res, void *data) +{ + struct extcon_dev **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +static void devm_extcon_dev_release(struct device *dev, void *res) +{ + extcon_dev_free(*(struct extcon_dev **)res); +} + +/** + * devm_extcon_dev_allocate - Allocate managed extcon device + * @dev: device owning the extcon device being created + * @supported_cable: Array of supported cable names ending with NULL. + * If supported_cable is NULL, cable name related APIs + * are disabled. + * + * This function manages automatically the memory of extcon device using device + * resource management and simplify the control of freeing the memory of extcon + * device. + * + * Returns the pointer memory of allocated extcon_dev if success + * or ERR_PTR(err) if fail + */ +struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, + const char **supported_cable) +{ + struct extcon_dev **ptr, *edev; + + ptr = devres_alloc(devm_extcon_dev_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + edev = extcon_dev_allocate(supported_cable); + if (IS_ERR(edev)) { + devres_free(ptr); + return edev; + } + + *ptr = edev; + devres_add(dev, ptr); + + return edev; +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_allocate); + +void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev) +{ + WARN_ON(devres_release(dev, devm_extcon_dev_release, + devm_extcon_dev_match, edev)); +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_free); + /** * extcon_dev_register() - Register a new extcon device * @edev : the new extcon device (should be allocated before calling) @@ -819,6 +913,63 @@ void extcon_dev_unregister(struct extcon_dev *edev) } EXPORT_SYMBOL_GPL(extcon_dev_unregister); +static void devm_extcon_dev_unreg(struct device *dev, void *res) +{ + extcon_dev_unregister(*(struct extcon_dev **)res); +} + +/** + * devm_extcon_dev_register() - Resource-managed extcon_dev_register() + * @dev: device to allocate extcon device + * @edev: the new extcon device to register + * + * Managed extcon_dev_register() function. If extcon device is attached with + * this function, that extcon device is automatically unregistered on driver + * detach. Internally this function calls extcon_dev_register() function. + * To get more information, refer that function. + * + * If extcon device is registered with this function and the device needs to be + * unregistered separately, devm_extcon_dev_unregister() should be used. + * + * Returns 0 if success or negaive error number if failure. + */ +int devm_extcon_dev_register(struct device *dev, struct extcon_dev *edev) +{ + struct extcon_dev **ptr; + int ret; + + ptr = devres_alloc(devm_extcon_dev_unreg, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = extcon_dev_register(edev); + if (ret) { + devres_free(ptr); + return ret; + } + + *ptr = edev; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_register); + +/** + * devm_extcon_dev_unregister() - Resource-managed extcon_dev_unregister() + * @dev: device the extcon belongs to + * @edev: the extcon device to unregister + * + * Unregister extcon device that is registered with devm_extcon_dev_register() + * function. + */ +void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev) +{ + WARN_ON(devres_release(dev, devm_extcon_dev_unreg, + devm_extcon_dev_match, edev)); +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_unregister); + #ifdef CONFIG_OF /* * extcon_get_edev_by_phandle - Get the extcon device from devicetree diff --git a/drivers/extcon/extcon-gpio.c b/drivers/extcon/extcon-gpio.c index 13d5222..645b283 100644 --- a/drivers/extcon/extcon-gpio.c +++ b/drivers/extcon/extcon-gpio.c @@ -32,7 +32,7 @@ #include <linux/extcon/extcon-gpio.h> struct gpio_extcon_data { - struct extcon_dev edev; + struct extcon_dev *edev; unsigned gpio; bool gpio_active_low; const char *state_on; @@ -53,7 +53,7 @@ static void gpio_extcon_work(struct work_struct *work) state = gpio_get_value(data->gpio); if (data->gpio_active_low) state = !state; - extcon_set_state(&data->edev, state); + extcon_set_state(data->edev, state); } static irqreturn_t gpio_irq_handler(int irq, void *dev_id) @@ -67,9 +67,10 @@ static irqreturn_t gpio_irq_handler(int irq, void *dev_id) static ssize_t extcon_gpio_print_state(struct extcon_dev *edev, char *buf) { - struct gpio_extcon_data *extcon_data = - container_of(edev, struct gpio_extcon_data, edev); + struct device *dev = edev->dev.parent; + struct gpio_extcon_data *extcon_data = dev_get_drvdata(dev); const char *state; + if (extcon_get_state(edev)) state = extcon_data->state_on; else @@ -98,15 +99,21 @@ static int gpio_extcon_probe(struct platform_device *pdev) if (!extcon_data) return -ENOMEM; - extcon_data->edev.name = pdata->name; - extcon_data->edev.dev.parent = &pdev->dev; + extcon_data->edev = devm_extcon_dev_allocate(&pdev->dev, NULL); + if (IS_ERR(extcon_data->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + extcon_data->edev->name = pdata->name; + extcon_data->edev->dev.parent = &pdev->dev; + extcon_data->gpio = pdata->gpio; extcon_data->gpio_active_low = pdata->gpio_active_low; extcon_data->state_on = pdata->state_on; extcon_data->state_off = pdata->state_off; extcon_data->check_on_resume = pdata->check_on_resume; if (pdata->state_on && pdata->state_off) - extcon_data->edev.print_state = extcon_gpio_print_state; + extcon_data->edev->print_state = extcon_gpio_print_state; ret = devm_gpio_request_one(&pdev->dev, extcon_data->gpio, GPIOF_DIR_IN, pdev->name); @@ -121,34 +128,27 @@ static int gpio_extcon_probe(struct platform_device *pdev) msecs_to_jiffies(pdata->debounce); } - ret = extcon_dev_register(&extcon_data->edev); + ret = devm_extcon_dev_register(&pdev->dev, extcon_data->edev); if (ret < 0) return ret; INIT_DELAYED_WORK(&extcon_data->work, gpio_extcon_work); extcon_data->irq = gpio_to_irq(extcon_data->gpio); - if (extcon_data->irq < 0) { - ret = extcon_data->irq; - goto err; - } + if (extcon_data->irq < 0) + return extcon_data->irq; ret = request_any_context_irq(extcon_data->irq, gpio_irq_handler, pdata->irq_flags, pdev->name, extcon_data); if (ret < 0) - goto err; + return ret; platform_set_drvdata(pdev, extcon_data); /* Perform initial detection */ gpio_extcon_work(&extcon_data->work.work); return 0; - -err: - extcon_dev_unregister(&extcon_data->edev); - - return ret; } static int gpio_extcon_remove(struct platform_device *pdev) @@ -157,7 +157,6 @@ static int gpio_extcon_remove(struct platform_device *pdev) cancel_delayed_work_sync(&extcon_data->work); free_irq(extcon_data->irq, extcon_data); - extcon_dev_unregister(&extcon_data->edev); return 0; } diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index 3846941..d49e891 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -1,8 +1,9 @@ /* - * extcon-max14577.c - MAX14577 extcon driver to support MAX14577 MUIC + * extcon-max14577.c - MAX14577/77836 extcon driver to support MUIC * - * Copyright (C) 2013 Samsung Electrnoics + * Copyright (C) 2013,2014 Samsung Electrnoics * Chanwoo Choi <cw00.choi@samsung.com> + * Krzysztof Kozlowski <k.kozlowski@samsung.com> * * 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 @@ -24,7 +25,6 @@ #include <linux/mfd/max14577-private.h> #include <linux/extcon.h> -#define DEV_NAME "max14577-muic" #define DELAY_MS_DEFAULT 17000 /* unit: millisecond */ enum max14577_muic_adc_debounce_time { @@ -40,6 +40,42 @@ enum max14577_muic_status { MAX14577_MUIC_STATUS_END, }; +/** + * struct max14577_muic_irq + * @irq: the index of irq list of MUIC device. + * @name: the name of irq. + * @virq: the virtual irq to use irq domain + */ +struct max14577_muic_irq { + unsigned int irq; + const char *name; + unsigned int virq; +}; + +static struct max14577_muic_irq max14577_muic_irqs[] = { + { MAX14577_IRQ_INT1_ADC, "muic-ADC" }, + { MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" }, + { MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" }, + { MAX14577_IRQ_INT2_CHGTYP, "muic-CHGTYP" }, + { MAX14577_IRQ_INT2_CHGDETRUN, "muic-CHGDETRUN" }, + { MAX14577_IRQ_INT2_DCDTMR, "muic-DCDTMR" }, + { MAX14577_IRQ_INT2_DBCHG, "muic-DBCHG" }, + { MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" }, +}; + +static struct max14577_muic_irq max77836_muic_irqs[] = { + { MAX14577_IRQ_INT1_ADC, "muic-ADC" }, + { MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" }, + { MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" }, + { MAX77836_IRQ_INT1_ADC1K, "muic-ADC1K" }, + { MAX14577_IRQ_INT2_CHGTYP, "muic-CHGTYP" }, + { MAX14577_IRQ_INT2_CHGDETRUN, "muic-CHGDETRUN" }, + { MAX14577_IRQ_INT2_DCDTMR, "muic-DCDTMR" }, + { MAX14577_IRQ_INT2_DBCHG, "muic-DBCHG" }, + { MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" }, + { MAX77836_IRQ_INT2_VIDRM, "muic-VIDRM" }, +}; + struct max14577_muic_info { struct device *dev; struct max14577 *max14577; @@ -48,6 +84,8 @@ struct max14577_muic_info { int prev_chg_type; u8 status[MAX14577_MUIC_STATUS_END]; + struct max14577_muic_irq *muic_irqs; + unsigned int muic_irqs_num; bool irq_adc; bool irq_chg; struct work_struct irq_work; @@ -74,29 +112,6 @@ enum max14577_muic_cable_group { MAX14577_CABLE_GROUP_CHG, }; -/** - * struct max14577_muic_irq - * @irq: the index of irq list of MUIC device. - * @name: the name of irq. - * @virq: the virtual irq to use irq domain - */ -struct max14577_muic_irq { - unsigned int irq; - const char *name; - unsigned int virq; -}; - -static struct max14577_muic_irq muic_irqs[] = { - { MAX14577_IRQ_INT1_ADC, "muic-ADC" }, - { MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" }, - { MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" }, - { MAX14577_IRQ_INT2_CHGTYP, "muic-CHGTYP" }, - { MAX14577_IRQ_INT2_CHGDETRUN, "muic-CHGDETRUN" }, - { MAX14577_IRQ_INT2_DCDTMR, "muic-DCDTMR" }, - { MAX14577_IRQ_INT2_DBCHG, "muic-DBCHG" }, - { MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" }, -}; - /* Define supported accessory type */ enum max14577_muic_acc_type { MAX14577_MUIC_ADC_GROUND = 0x0, @@ -528,21 +543,12 @@ static void max14577_muic_irq_work(struct work_struct *work) return; } -static irqreturn_t max14577_muic_irq_handler(int irq, void *data) +/* + * Sets irq_adc or irq_chg in max14577_muic_info and returns 1. + * Returns 0 if irq_type does not match registered IRQ for this device type. + */ +static int max14577_parse_irq(struct max14577_muic_info *info, int irq_type) { - struct max14577_muic_info *info = data; - int i, irq_type = -1; - - /* - * We may be called multiple times for different nested IRQ-s. - * Including changes in INT1_ADC and INT2_CGHTYP at once. - * However we only need to know whether it was ADC, charger - * or both interrupts so decode IRQ and turn on proper flags. - */ - for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) - if (irq == muic_irqs[i].virq) - irq_type = muic_irqs[i].irq; - switch (irq_type) { case MAX14577_IRQ_INT1_ADC: case MAX14577_IRQ_INT1_ADCLOW: @@ -550,7 +556,7 @@ static irqreturn_t max14577_muic_irq_handler(int irq, void *data) /* Handle all of accessory except for type of charger accessory */ info->irq_adc = true; - break; + return 1; case MAX14577_IRQ_INT2_CHGTYP: case MAX14577_IRQ_INT2_CHGDETRUN: case MAX14577_IRQ_INT2_DCDTMR: @@ -558,8 +564,62 @@ static irqreturn_t max14577_muic_irq_handler(int irq, void *data) case MAX14577_IRQ_INT2_VBVOLT: /* Handle charger accessory */ info->irq_chg = true; + return 1; + default: + return 0; + } +} + +/* + * Sets irq_adc or irq_chg in max14577_muic_info and returns 1. + * Returns 0 if irq_type does not match registered IRQ for this device type. + */ +static int max77836_parse_irq(struct max14577_muic_info *info, int irq_type) +{ + /* First check common max14577 interrupts */ + if (max14577_parse_irq(info, irq_type)) + return 1; + + switch (irq_type) { + case MAX77836_IRQ_INT1_ADC1K: + info->irq_adc = true; + return 1; + case MAX77836_IRQ_INT2_VIDRM: + /* Handle charger accessory */ + info->irq_chg = true; + return 1; + default: + return 0; + } +} + +static irqreturn_t max14577_muic_irq_handler(int irq, void *data) +{ + struct max14577_muic_info *info = data; + int i, irq_type = -1; + bool irq_parsed; + + /* + * We may be called multiple times for different nested IRQ-s. + * Including changes in INT1_ADC and INT2_CGHTYP at once. + * However we only need to know whether it was ADC, charger + * or both interrupts so decode IRQ and turn on proper flags. + */ + for (i = 0; i < info->muic_irqs_num; i++) + if (irq == info->muic_irqs[i].virq) + irq_type = info->muic_irqs[i].irq; + + switch (info->max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + irq_parsed = max77836_parse_irq(info, irq_type); break; + case MAXIM_DEVICE_TYPE_MAX14577: default: + irq_parsed = max14577_parse_irq(info, irq_type); + break; + } + + if (!irq_parsed) { dev_err(info->dev, "muic interrupt: irq %d occurred, skipped\n", irq_type); return IRQ_HANDLED; @@ -644,13 +704,24 @@ static int max14577_muic_probe(struct platform_device *pdev) INIT_WORK(&info->irq_work, max14577_muic_irq_work); + switch (max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + info->muic_irqs = max77836_muic_irqs; + info->muic_irqs_num = ARRAY_SIZE(max77836_muic_irqs); + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + info->muic_irqs = max14577_muic_irqs; + info->muic_irqs_num = ARRAY_SIZE(max14577_muic_irqs); + } + /* Support irq domain for max14577 MUIC device */ - for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { - struct max14577_muic_irq *muic_irq = &muic_irqs[i]; + for (i = 0; i < info->muic_irqs_num; i++) { + struct max14577_muic_irq *muic_irq = &info->muic_irqs[i]; unsigned int virq = 0; virq = regmap_irq_get_virq(max14577->irq_data, muic_irq->irq); - if (!virq) + if (virq <= 0) return -EINVAL; muic_irq->virq = virq; @@ -668,14 +739,16 @@ static int max14577_muic_probe(struct platform_device *pdev) } /* Initialize extcon device */ - info->edev = devm_kzalloc(&pdev->dev, sizeof(*info->edev), GFP_KERNEL); - if (!info->edev) { + info->edev = devm_extcon_dev_allocate(&pdev->dev, + max14577_extcon_cable); + if (IS_ERR(info->edev)) { dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); return -ENOMEM; } - info->edev->name = DEV_NAME; - info->edev->supported_cable = max14577_extcon_cable; - ret = extcon_dev_register(info->edev); + + info->edev->name = dev_name(&pdev->dev); + + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); return ret; @@ -694,7 +767,7 @@ static int max14577_muic_probe(struct platform_device *pdev) MAX14577_REG_DEVICEID, &id); if (ret < 0) { dev_err(&pdev->dev, "failed to read revision number\n"); - goto err_extcon; + return ret; } dev_info(info->dev, "device ID : 0x%x\n", id); @@ -710,19 +783,10 @@ static int max14577_muic_probe(struct platform_device *pdev) * driver should notify cable state to upper layer. */ INIT_DELAYED_WORK(&info->wq_detcable, max14577_muic_detect_cable_wq); - ret = queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, delay_jiffies); - if (ret < 0) { - dev_err(&pdev->dev, - "failed to schedule delayed work for cable detect\n"); - goto err_extcon; - } return ret; - -err_extcon: - extcon_dev_unregister(info->edev); - return ret; } static int max14577_muic_remove(struct platform_device *pdev) @@ -730,23 +794,30 @@ static int max14577_muic_remove(struct platform_device *pdev) struct max14577_muic_info *info = platform_get_drvdata(pdev); cancel_work_sync(&info->irq_work); - extcon_dev_unregister(info->edev); return 0; } +static const struct platform_device_id max14577_muic_id[] = { + { "max14577-muic", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836-muic", MAXIM_DEVICE_TYPE_MAX77836, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max14577_muic_id); + static struct platform_driver max14577_muic_driver = { .driver = { - .name = DEV_NAME, + .name = "max14577-muic", .owner = THIS_MODULE, }, .probe = max14577_muic_probe, .remove = max14577_muic_remove, + .id_table = max14577_muic_id, }; module_platform_driver(max14577_muic_driver); -MODULE_DESCRIPTION("MAXIM 14577 Extcon driver"); -MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); +MODULE_DESCRIPTION("Maxim 14577/77836 Extcon driver"); +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>, Krzysztof Kozlowski <k.kozlowski@samsung.com>"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:extcon-max14577"); diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index da268fb..2c7c3e1 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1175,25 +1175,24 @@ static int max77693_muic_probe(struct platform_device *pdev) } /* Initialize extcon device */ - info->edev = devm_kzalloc(&pdev->dev, sizeof(struct extcon_dev), - GFP_KERNEL); - if (!info->edev) { + info->edev = devm_extcon_dev_allocate(&pdev->dev, + max77693_extcon_cable); + if (IS_ERR(info->edev)) { dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); ret = -ENOMEM; goto err_irq; } info->edev->name = DEV_NAME; info->edev->dev.parent = &pdev->dev; - info->edev->supported_cable = max77693_extcon_cable; - ret = extcon_dev_register(info->edev); + + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); goto err_irq; } - /* Initialize MUIC register by using platform data or default data */ - if (pdata->muic_data) { + if (pdata && pdata->muic_data) { init_data = pdata->muic_data->init_data; num_init_data = pdata->muic_data->num_init_data; } else { @@ -1226,7 +1225,7 @@ static int max77693_muic_probe(struct platform_device *pdev) = init_data[i].data; } - if (pdata->muic_data) { + if (pdata && pdata->muic_data) { struct max77693_muic_platform_data *muic_pdata = pdata->muic_data; @@ -1267,7 +1266,7 @@ static int max77693_muic_probe(struct platform_device *pdev) MAX77693_MUIC_REG_ID, &id); if (ret < 0) { dev_err(&pdev->dev, "failed to read revision number\n"); - goto err_extcon; + goto err_irq; } dev_info(info->dev, "device ID : 0x%x\n", id); @@ -1283,12 +1282,11 @@ static int max77693_muic_probe(struct platform_device *pdev) * driver should notify cable state to upper layer. */ INIT_DELAYED_WORK(&info->wq_detcable, max77693_muic_detect_cable_wq); - schedule_delayed_work(&info->wq_detcable, delay_jiffies); + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + delay_jiffies); return ret; -err_extcon: - extcon_dev_unregister(info->edev); err_irq: while (--i >= 0) free_irq(muic_irqs[i].virq, info); @@ -1304,7 +1302,6 @@ static int max77693_muic_remove(struct platform_device *pdev) free_irq(muic_irqs[i].virq, info); cancel_work_sync(&info->irq_work); input_unregister_device(info->dock); - extcon_dev_unregister(info->edev); return 0; } diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 6a00464..d9f7f1b 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -699,23 +699,22 @@ static int max8997_muic_probe(struct platform_device *pdev) } /* External connector */ - info->edev = devm_kzalloc(&pdev->dev, sizeof(struct extcon_dev), - GFP_KERNEL); - if (!info->edev) { + info->edev = devm_extcon_dev_allocate(&pdev->dev, max8997_extcon_cable); + if (IS_ERR(info->edev)) { dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); ret = -ENOMEM; goto err_irq; } info->edev->name = DEV_NAME; info->edev->dev.parent = &pdev->dev; - info->edev->supported_cable = max8997_extcon_cable; - ret = extcon_dev_register(info->edev); + + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); goto err_irq; } - if (pdata->muic_pdata) { + if (pdata && pdata->muic_pdata) { struct max8997_muic_platform_data *muic_pdata = pdata->muic_pdata; @@ -770,7 +769,8 @@ static int max8997_muic_probe(struct platform_device *pdev) * driver should notify cable state to upper layer. */ INIT_DELAYED_WORK(&info->wq_detcable, max8997_muic_detect_cable_wq); - schedule_delayed_work(&info->wq_detcable, delay_jiffies); + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + delay_jiffies); return 0; @@ -789,8 +789,6 @@ static int max8997_muic_remove(struct platform_device *pdev) free_irq(muic_irqs[i].virq, info); cancel_work_sync(&info->irq_work); - extcon_dev_unregister(info->edev); - return 0; } diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c index ddff2b7..7417ce8 100644 --- a/drivers/extcon/extcon-palmas.c +++ b/drivers/extcon/extcon-palmas.c @@ -23,6 +23,7 @@ #include <linux/module.h> #include <linux/interrupt.h> #include <linux/platform_device.h> +#include <linux/slab.h> #include <linux/err.h> #include <linux/mfd/palmas.h> #include <linux/of.h> @@ -56,7 +57,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb) if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) { if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) { palmas_usb->linkstat = PALMAS_USB_STATE_VBUS; - extcon_set_cable_state(&palmas_usb->edev, "USB", true); + extcon_set_cable_state(palmas_usb->edev, "USB", true); dev_info(palmas_usb->dev, "USB cable is attached\n"); } else { dev_dbg(palmas_usb->dev, @@ -65,7 +66,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb) } else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) { if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) { palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; - extcon_set_cable_state(&palmas_usb->edev, "USB", false); + extcon_set_cable_state(palmas_usb->edev, "USB", false); dev_info(palmas_usb->dev, "USB cable is detached\n"); } else { dev_dbg(palmas_usb->dev, @@ -92,7 +93,7 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb) PALMAS_USB_ID_INT_LATCH_CLR, PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND); palmas_usb->linkstat = PALMAS_USB_STATE_ID; - extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", true); + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true); dev_info(palmas_usb->dev, "USB-HOST cable is attached\n"); } else if ((set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) && (id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT)) { @@ -100,17 +101,17 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb) PALMAS_USB_ID_INT_LATCH_CLR, PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT); palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; - extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false); + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false); dev_info(palmas_usb->dev, "USB-HOST cable is detached\n"); } else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) && (!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) { palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; - extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false); + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false); dev_info(palmas_usb->dev, "USB-HOST cable is detached\n"); } else if ((palmas_usb->linkstat == PALMAS_USB_STATE_DISCONNECT) && (id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) { palmas_usb->linkstat = PALMAS_USB_STATE_ID; - extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", true); + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true); dev_info(palmas_usb->dev, " USB-HOST cable is attached\n"); } @@ -186,13 +187,20 @@ static int palmas_usb_probe(struct platform_device *pdev) platform_set_drvdata(pdev, palmas_usb); - palmas_usb->edev.supported_cable = palmas_extcon_cable; - palmas_usb->edev.dev.parent = palmas_usb->dev; - palmas_usb->edev.mutually_exclusive = mutually_exclusive; + palmas_usb->edev = devm_extcon_dev_allocate(&pdev->dev, + palmas_extcon_cable); + if (IS_ERR(palmas_usb->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + palmas_usb->edev->name = kstrdup(node->name, GFP_KERNEL); + palmas_usb->edev->dev.parent = palmas_usb->dev; + palmas_usb->edev->mutually_exclusive = mutually_exclusive; - status = extcon_dev_register(&palmas_usb->edev); + status = devm_extcon_dev_register(&pdev->dev, palmas_usb->edev); if (status) { dev_err(&pdev->dev, "failed to register extcon device\n"); + kfree(palmas_usb->edev->name); return status; } @@ -206,7 +214,8 @@ static int palmas_usb_probe(struct platform_device *pdev) if (status < 0) { dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", palmas_usb->id_irq, status); - goto fail_extcon; + kfree(palmas_usb->edev->name); + return status; } } @@ -220,25 +229,21 @@ static int palmas_usb_probe(struct platform_device *pdev) if (status < 0) { dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", palmas_usb->vbus_irq, status); - goto fail_extcon; + kfree(palmas_usb->edev->name); + return status; } } palmas_enable_irq(palmas_usb); device_set_wakeup_capable(&pdev->dev, true); return 0; - -fail_extcon: - extcon_dev_unregister(&palmas_usb->edev); - - return status; } static int palmas_usb_remove(struct platform_device *pdev) { struct palmas_usb *palmas_usb = platform_get_drvdata(pdev); - extcon_dev_unregister(&palmas_usb->edev); + kfree(palmas_usb->edev->name); return 0; } diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 602ca86..284cf66 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -471,18 +471,26 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle) } EXPORT_SYMBOL_GPL(vmbus_teardown_gpadl); +static void reset_channel_cb(void *arg) +{ + struct vmbus_channel *channel = arg; + + channel->onchannel_callback = NULL; +} + static void vmbus_close_internal(struct vmbus_channel *channel) { struct vmbus_channel_close_channel *msg; int ret; - unsigned long flags; channel->state = CHANNEL_OPEN_STATE; channel->sc_creation_callback = NULL; /* Stop callback and cancel the timer asap */ - spin_lock_irqsave(&channel->inbound_lock, flags); - channel->onchannel_callback = NULL; - spin_unlock_irqrestore(&channel->inbound_lock, flags); + if (channel->target_cpu != smp_processor_id()) + smp_call_function_single(channel->target_cpu, reset_channel_cb, + channel, true); + else + reset_channel_cb(channel); /* Send a closing message */ @@ -674,8 +682,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, u32 pfncount = NUM_PAGES_SPANNED(multi_pagebuffer->offset, multi_pagebuffer->len); - - if ((pfncount < 0) || (pfncount > MAX_MULTIPAGE_BUFFER_COUNT)) + if (pfncount > MAX_MULTIPAGE_BUFFER_COUNT) return -EINVAL; /* diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index fa92046..6c8b032c 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -149,6 +149,7 @@ static struct vmbus_channel *alloc_channel(void) spin_lock_init(&channel->sc_lock); INIT_LIST_HEAD(&channel->sc_list); + INIT_LIST_HEAD(&channel->percpu_list); channel->controlwq = create_workqueue("hv_vmbus_ctl"); if (!channel->controlwq) { @@ -188,7 +189,20 @@ static void free_channel(struct vmbus_channel *channel) queue_work(vmbus_connection.work_queue, &channel->work); } +static void percpu_channel_enq(void *arg) +{ + struct vmbus_channel *channel = arg; + int cpu = smp_processor_id(); + + list_add_tail(&channel->percpu_list, &hv_context.percpu_list[cpu]); +} +static void percpu_channel_deq(void *arg) +{ + struct vmbus_channel *channel = arg; + + list_del(&channel->percpu_list); +} /* * vmbus_process_rescind_offer - @@ -210,6 +224,12 @@ static void vmbus_process_rescind_offer(struct work_struct *work) msg.header.msgtype = CHANNELMSG_RELID_RELEASED; vmbus_post_msg(&msg, sizeof(struct vmbus_channel_relid_released)); + if (channel->target_cpu != smp_processor_id()) + smp_call_function_single(channel->target_cpu, + percpu_channel_deq, channel, true); + else + percpu_channel_deq(channel); + if (channel->primary_channel == NULL) { spin_lock_irqsave(&vmbus_connection.channel_lock, flags); list_del(&channel->listentry); @@ -245,6 +265,7 @@ static void vmbus_process_offer(struct work_struct *work) work); struct vmbus_channel *channel; bool fnew = true; + bool enq = false; int ret; unsigned long flags; @@ -264,12 +285,22 @@ static void vmbus_process_offer(struct work_struct *work) } } - if (fnew) + if (fnew) { list_add_tail(&newchannel->listentry, &vmbus_connection.chn_list); + enq = true; + } spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags); + if (enq) { + if (newchannel->target_cpu != smp_processor_id()) + smp_call_function_single(newchannel->target_cpu, + percpu_channel_enq, + newchannel, true); + else + percpu_channel_enq(newchannel); + } if (!fnew) { /* * Check to see if this is a sub-channel. @@ -282,6 +313,14 @@ static void vmbus_process_offer(struct work_struct *work) spin_lock_irqsave(&channel->sc_lock, flags); list_add_tail(&newchannel->sc_list, &channel->sc_list); spin_unlock_irqrestore(&channel->sc_lock, flags); + + if (newchannel->target_cpu != smp_processor_id()) + smp_call_function_single(newchannel->target_cpu, + percpu_channel_enq, + newchannel, true); + else + percpu_channel_enq(newchannel); + newchannel->state = CHANNEL_OPEN_STATE; if (channel->sc_creation_callback != NULL) channel->sc_creation_callback(newchannel); @@ -365,7 +404,7 @@ static u32 next_vp; * performance critical channels (IDE, SCSI and Network) will be uniformly * distributed across all available CPUs. */ -static u32 get_vp_index(uuid_le *type_guid) +static void init_vp_index(struct vmbus_channel *channel, uuid_le *type_guid) { u32 cur_cpu; int i; @@ -387,10 +426,13 @@ static u32 get_vp_index(uuid_le *type_guid) * Also if the channel is not a performance critical * channel, bind it to cpu 0. */ - return 0; + channel->target_cpu = 0; + channel->target_vp = 0; + return; } cur_cpu = (++next_vp % max_cpus); - return hv_context.vp_index[cur_cpu]; + channel->target_cpu = cur_cpu; + channel->target_vp = hv_context.vp_index[cur_cpu]; } /* @@ -438,7 +480,7 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) offer->connection_id; } - newchannel->target_vp = get_vp_index(&offer->offer.if_type); + init_vp_index(newchannel, &offer->offer.if_type); memcpy(&newchannel->offermsg, offer, sizeof(struct vmbus_channel_offer_channel)); diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 2e7801a..e84f452 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -224,8 +224,8 @@ cleanup: vmbus_connection.int_page = NULL; } - free_pages((unsigned long)vmbus_connection.monitor_pages[0], 1); - free_pages((unsigned long)vmbus_connection.monitor_pages[1], 1); + free_pages((unsigned long)vmbus_connection.monitor_pages[0], 0); + free_pages((unsigned long)vmbus_connection.monitor_pages[1], 0); vmbus_connection.monitor_pages[0] = NULL; vmbus_connection.monitor_pages[1] = NULL; @@ -234,6 +234,28 @@ cleanup: return ret; } +/* + * Map the given relid to the corresponding channel based on the + * per-cpu list of channels that have been affinitized to this CPU. + * This will be used in the channel callback path as we can do this + * mapping in a lock-free fashion. + */ +static struct vmbus_channel *pcpu_relid2channel(u32 relid) +{ + struct vmbus_channel *channel; + struct vmbus_channel *found_channel = NULL; + int cpu = smp_processor_id(); + struct list_head *pcpu_head = &hv_context.percpu_list[cpu]; + + list_for_each_entry(channel, pcpu_head, percpu_list) { + if (channel->offermsg.child_relid == relid) { + found_channel = channel; + break; + } + } + + return found_channel; +} /* * relid2channel - Get the channel object given its @@ -277,7 +299,6 @@ struct vmbus_channel *relid2channel(u32 relid) static void process_chn_event(u32 relid) { struct vmbus_channel *channel; - unsigned long flags; void *arg; bool read_state; u32 bytes_to_read; @@ -286,7 +307,7 @@ static void process_chn_event(u32 relid) * Find the channel based on this relid and invokes the * channel callback to process the event */ - channel = relid2channel(relid); + channel = pcpu_relid2channel(relid); if (!channel) { pr_err("channel not found for relid - %u\n", relid); @@ -296,13 +317,12 @@ static void process_chn_event(u32 relid) /* * A channel once created is persistent even when there * is no driver handling the device. An unloading driver - * sets the onchannel_callback to NULL under the - * protection of the channel inbound_lock. Thus, checking - * and invoking the driver specific callback takes care of - * orderly unloading of the driver. + * sets the onchannel_callback to NULL on the same CPU + * as where this interrupt is handled (in an interrupt context). + * Thus, checking and invoking the driver specific callback takes + * care of orderly unloading of the driver. */ - spin_lock_irqsave(&channel->inbound_lock, flags); if (channel->onchannel_callback != NULL) { arg = channel->channel_callback_context; read_state = channel->batched_reading; @@ -327,7 +347,6 @@ static void process_chn_event(u32 relid) pr_err("no channel callback for relid - %u\n", relid); } - spin_unlock_irqrestore(&channel->inbound_lock, flags); } /* diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index bcb4950..edfc848 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -383,6 +383,8 @@ void hv_synic_init(void *arg) */ rdmsrl(HV_X64_MSR_VP_INDEX, vp_index); hv_context.vp_index[cpu] = (u32)vp_index; + + INIT_LIST_HEAD(&hv_context.percpu_list[cpu]); return; } diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 7e6d78d..5e90c5d 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -19,6 +19,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/kernel.h> +#include <linux/jiffies.h> #include <linux/mman.h> #include <linux/delay.h> #include <linux/init.h> @@ -459,6 +460,11 @@ static bool do_hot_add; */ static uint pressure_report_delay = 45; +/* + * The last time we posted a pressure report to host. + */ +static unsigned long last_post_time; + module_param(hot_add, bool, (S_IRUGO | S_IWUSR)); MODULE_PARM_DESC(hot_add, "If set attempt memory hot_add"); @@ -542,6 +548,7 @@ struct hv_dynmem_device { static struct hv_dynmem_device dm_device; +static void post_status(struct hv_dynmem_device *dm); #ifdef CONFIG_MEMORY_HOTPLUG static void hv_bring_pgs_online(unsigned long start_pfn, unsigned long size) @@ -612,7 +619,7 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size, * have not been "onlined" within the allowed time. */ wait_for_completion_timeout(&dm_device.ol_waitevent, 5*HZ); - + post_status(&dm_device); } return; @@ -951,11 +958,17 @@ static void post_status(struct hv_dynmem_device *dm) { struct dm_status status; struct sysinfo val; + unsigned long now = jiffies; + unsigned long last_post = last_post_time; if (pressure_report_delay > 0) { --pressure_report_delay; return; } + + if (!time_after(now, (last_post_time + HZ))) + return; + si_meminfo(&val); memset(&status, 0, sizeof(struct dm_status)); status.hdr.type = DM_STATUS_REPORT; @@ -983,6 +996,14 @@ static void post_status(struct hv_dynmem_device *dm) if (status.hdr.trans_id != atomic_read(&trans_id)) return; + /* + * If the last post time that we sampled has changed, + * we have raced, don't post the status. + */ + if (last_post != last_post_time) + return; + + last_post_time = jiffies; vmbus_sendpacket(dm->dev->channel, &status, sizeof(struct dm_status), (unsigned long)NULL, @@ -1117,7 +1138,7 @@ static void balloon_up(struct work_struct *dummy) if (ret == -EAGAIN) msleep(20); - + post_status(&dm_device); } while (ret == -EAGAIN); if (ret) { @@ -1144,8 +1165,10 @@ static void balloon_down(struct hv_dynmem_device *dm, struct dm_unballoon_response resp; int i; - for (i = 0; i < range_count; i++) + for (i = 0; i < range_count; i++) { free_balloon_pages(dm, &range_array[i]); + post_status(&dm_device); + } if (req->more_pages == 1) return; diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 860134d..18d1a84 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -510,6 +510,11 @@ struct hv_context { * basis. */ struct tasklet_struct *event_dpc[NR_CPUS]; + /* + * To optimize the mapping of relid to channel, maintain + * per-cpu list of the channels based on their CPU affinity. + */ + struct list_head percpu_list[NR_CPUS]; }; extern struct hv_context hv_context; diff --git a/drivers/mcb/mcb-core.c b/drivers/mcb/mcb-core.c index bbe1293..9018ab8 100644 --- a/drivers/mcb/mcb-core.c +++ b/drivers/mcb/mcb-core.c @@ -183,14 +183,14 @@ EXPORT_SYMBOL_GPL(mcb_device_register); * * Allocate a new @mcb_bus. */ -struct mcb_bus *mcb_alloc_bus(void) +struct mcb_bus *mcb_alloc_bus(struct device *carrier) { struct mcb_bus *bus; int bus_nr; bus = kzalloc(sizeof(struct mcb_bus), GFP_KERNEL); if (!bus) - return NULL; + return ERR_PTR(-ENOMEM); bus_nr = ida_simple_get(&mcb_ida, 0, 0, GFP_KERNEL); if (bus_nr < 0) { @@ -200,7 +200,7 @@ struct mcb_bus *mcb_alloc_bus(void) INIT_LIST_HEAD(&bus->children); bus->bus_nr = bus_nr; - + bus->carrier = carrier; return bus; } EXPORT_SYMBOL_GPL(mcb_alloc_bus); @@ -378,6 +378,13 @@ void mcb_release_mem(struct resource *mem) } EXPORT_SYMBOL_GPL(mcb_release_mem); +static int __mcb_get_irq(struct mcb_device *dev) +{ + struct resource *irq = &dev->irq; + + return irq->start; +} + /** * mcb_get_irq() - Get device's IRQ number * @dev: The @mcb_device the IRQ is for @@ -386,9 +393,12 @@ EXPORT_SYMBOL_GPL(mcb_release_mem); */ int mcb_get_irq(struct mcb_device *dev) { - struct resource *irq = &dev->irq; + struct mcb_bus *bus = dev->bus; - return irq->start; + if (bus->get_irq) + return bus->get_irq(dev); + + return __mcb_get_irq(dev); } EXPORT_SYMBOL_GPL(mcb_get_irq); diff --git a/drivers/mcb/mcb-pci.c b/drivers/mcb/mcb-pci.c index 99c742c..b591819 100644 --- a/drivers/mcb/mcb-pci.c +++ b/drivers/mcb/mcb-pci.c @@ -20,6 +20,15 @@ struct priv { void __iomem *base; }; +static int mcb_pci_get_irq(struct mcb_device *mdev) +{ + struct mcb_bus *mbus = mdev->bus; + struct device *dev = mbus->carrier; + struct pci_dev *pdev = to_pci_dev(dev); + + return pdev->irq; +} + static int mcb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct priv *priv; @@ -67,7 +76,13 @@ static int mcb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) pci_set_drvdata(pdev, priv); - priv->bus = mcb_alloc_bus(); + priv->bus = mcb_alloc_bus(&pdev->dev); + if (IS_ERR(priv->bus)) { + ret = PTR_ERR(priv->bus); + goto err_drvdata; + } + + priv->bus->get_irq = mcb_pci_get_irq; ret = chameleon_parse_cells(priv->bus, mapbase, priv->base); if (ret < 0) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index f04ac62..6deb8a1 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -331,15 +331,15 @@ config MFD_88PM860X battery-charger under the corresponding menus. config MFD_MAX14577 - bool "Maxim Semiconductor MAX14577 MUIC + Charger Support" + bool "Maxim Semiconductor MAX14577/77836 MUIC + Charger Support" depends on I2C=y select MFD_CORE select REGMAP_I2C select REGMAP_IRQ select IRQ_DOMAIN help - Say yes here to add support for Maxim Semiconductor MAX14577. - This is a Micro-USB IC with Charger controls on chip. + Say yes here to add support for Maxim Semiconductor MAX14577 and + MAX77836 Micro-USB ICs with battery charger. This driver provides common support for accessing the device; additional drivers must be enabled in order to use the functionality of the device. diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c index 5f13cef..484d372 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -1,7 +1,7 @@ /* - * max14577.c - mfd core driver for the Maxim 14577 + * max14577.c - mfd core driver for the Maxim 14577/77836 * - * Copyright (C) 2013 Samsung Electrnoics + * Copyright (C) 2014 Samsung Electrnoics * Chanwoo Choi <cw00.choi@samsung.com> * Krzysztof Kozlowski <k.kozlowski@samsung.com> * @@ -21,6 +21,7 @@ #include <linux/err.h> #include <linux/module.h> #include <linux/interrupt.h> +#include <linux/of_device.h> #include <linux/mfd/core.h> #include <linux/mfd/max14577.h> #include <linux/mfd/max14577-private.h> @@ -37,7 +38,38 @@ static struct mfd_cell max14577_devs[] = { { .name = "max14577-charger", }, }; -static bool max14577_volatile_reg(struct device *dev, unsigned int reg) +static struct mfd_cell max77836_devs[] = { + { + .name = "max77836-muic", + .of_compatible = "maxim,max77836-muic", + }, + { + .name = "max77836-regulator", + .of_compatible = "maxim,max77836-regulator", + }, + { + .name = "max77836-charger", + .of_compatible = "maxim,max77836-charger", + }, + { + .name = "max77836-battery", + .of_compatible = "maxim,max77836-battery", + }, +}; + +static struct of_device_id max14577_dt_match[] = { + { + .compatible = "maxim,max14577", + .data = (void *)MAXIM_DEVICE_TYPE_MAX14577, + }, + { + .compatible = "maxim,max77836", + .data = (void *)MAXIM_DEVICE_TYPE_MAX77836, + }, + {}, +}; + +static bool max14577_muic_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case MAX14577_REG_INT1 ... MAX14577_REG_STATUS3: @@ -48,49 +80,221 @@ static bool max14577_volatile_reg(struct device *dev, unsigned int reg) return false; } -static const struct regmap_config max14577_regmap_config = { +static bool max77836_muic_volatile_reg(struct device *dev, unsigned int reg) +{ + /* Any max14577 volatile registers are also max77836 volatile. */ + if (max14577_muic_volatile_reg(dev, reg)) + return true; + + switch (reg) { + case MAX77836_FG_REG_VCELL_MSB ... MAX77836_FG_REG_SOC_LSB: + case MAX77836_FG_REG_CRATE_MSB ... MAX77836_FG_REG_CRATE_LSB: + case MAX77836_FG_REG_STATUS_H ... MAX77836_FG_REG_STATUS_L: + case MAX77836_PMIC_REG_INTSRC: + case MAX77836_PMIC_REG_TOPSYS_INT: + case MAX77836_PMIC_REG_TOPSYS_STAT: + return true; + default: + break; + } + return false; +} + +static const struct regmap_config max14577_muic_regmap_config = { .reg_bits = 8, .val_bits = 8, - .volatile_reg = max14577_volatile_reg, + .volatile_reg = max14577_muic_volatile_reg, .max_register = MAX14577_REG_END, }; +static const struct regmap_config max77836_pmic_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = max77836_muic_volatile_reg, + .max_register = MAX77836_PMIC_REG_END, +}; + static const struct regmap_irq max14577_irqs[] = { /* INT1 interrupts */ - { .reg_offset = 0, .mask = INT1_ADC_MASK, }, - { .reg_offset = 0, .mask = INT1_ADCLOW_MASK, }, - { .reg_offset = 0, .mask = INT1_ADCERR_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCLOW_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCERR_MASK, }, /* INT2 interrupts */ - { .reg_offset = 1, .mask = INT2_CHGTYP_MASK, }, - { .reg_offset = 1, .mask = INT2_CHGDETRUN_MASK, }, - { .reg_offset = 1, .mask = INT2_DCDTMR_MASK, }, - { .reg_offset = 1, .mask = INT2_DBCHG_MASK, }, - { .reg_offset = 1, .mask = INT2_VBVOLT_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_CHGTYP_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_CHGDETRUN_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DCDTMR_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DBCHG_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_VBVOLT_MASK, }, /* INT3 interrupts */ - { .reg_offset = 2, .mask = INT3_EOC_MASK, }, - { .reg_offset = 2, .mask = INT3_CGMBC_MASK, }, - { .reg_offset = 2, .mask = INT3_OVP_MASK, }, - { .reg_offset = 2, .mask = INT3_MBCCHGERR_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_EOC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_CGMBC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_OVP_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_MBCCHGERR_MASK, }, }; static const struct regmap_irq_chip max14577_irq_chip = { .name = "max14577", .status_base = MAX14577_REG_INT1, .mask_base = MAX14577_REG_INTMASK1, - .mask_invert = 1, + .mask_invert = true, .num_regs = 3, .irqs = max14577_irqs, .num_irqs = ARRAY_SIZE(max14577_irqs), }; +static const struct regmap_irq max77836_muic_irqs[] = { + /* INT1 interrupts */ + { .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCLOW_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCERR_MASK, }, + { .reg_offset = 0, .mask = MAX77836_INT1_ADC1K_MASK, }, + /* INT2 interrupts */ + { .reg_offset = 1, .mask = MAX14577_INT2_CHGTYP_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_CHGDETRUN_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DCDTMR_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DBCHG_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_VBVOLT_MASK, }, + { .reg_offset = 1, .mask = MAX77836_INT2_VIDRM_MASK, }, + /* INT3 interrupts */ + { .reg_offset = 2, .mask = MAX14577_INT3_EOC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_CGMBC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_OVP_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_MBCCHGERR_MASK, }, +}; + +static const struct regmap_irq_chip max77836_muic_irq_chip = { + .name = "max77836-muic", + .status_base = MAX14577_REG_INT1, + .mask_base = MAX14577_REG_INTMASK1, + .mask_invert = true, + .num_regs = 3, + .irqs = max77836_muic_irqs, + .num_irqs = ARRAY_SIZE(max77836_muic_irqs), +}; + +static const struct regmap_irq max77836_pmic_irqs[] = { + { .reg_offset = 0, .mask = MAX77836_TOPSYS_INT_T120C_MASK, }, + { .reg_offset = 0, .mask = MAX77836_TOPSYS_INT_T140C_MASK, }, +}; + +static const struct regmap_irq_chip max77836_pmic_irq_chip = { + .name = "max77836-pmic", + .status_base = MAX77836_PMIC_REG_TOPSYS_INT, + .mask_base = MAX77836_PMIC_REG_TOPSYS_INT_MASK, + .mask_invert = false, + .num_regs = 1, + .irqs = max77836_pmic_irqs, + .num_irqs = ARRAY_SIZE(max77836_pmic_irqs), +}; + +static void max14577_print_dev_type(struct max14577 *max14577) +{ + u8 reg_data, vendor_id, device_id; + int ret; + + ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID, + ®_data); + if (ret) { + dev_err(max14577->dev, + "Failed to read DEVICEID register: %d\n", ret); + return; + } + + vendor_id = ((reg_data & DEVID_VENDORID_MASK) >> + DEVID_VENDORID_SHIFT); + device_id = ((reg_data & DEVID_DEVICEID_MASK) >> + DEVID_DEVICEID_SHIFT); + + dev_info(max14577->dev, "Device type: %u (ID: 0x%x, vendor: 0x%x)\n", + max14577->dev_type, device_id, vendor_id); +} + +/* + * Max77836 specific initialization code for driver probe. + * Adds new I2C dummy device, regmap and regmap IRQ chip. + * Unmasks Interrupt Source register. + * + * On success returns 0. + * On failure returns errno and reverts any changes done so far (e.g. remove + * I2C dummy device), except masking the INT SRC register. + */ +static int max77836_init(struct max14577 *max14577) +{ + int ret; + u8 intsrc_mask; + + max14577->i2c_pmic = i2c_new_dummy(max14577->i2c->adapter, + I2C_ADDR_PMIC); + if (!max14577->i2c_pmic) { + dev_err(max14577->dev, "Failed to register PMIC I2C device\n"); + return -ENODEV; + } + i2c_set_clientdata(max14577->i2c_pmic, max14577); + + max14577->regmap_pmic = devm_regmap_init_i2c(max14577->i2c_pmic, + &max77836_pmic_regmap_config); + if (IS_ERR(max14577->regmap_pmic)) { + ret = PTR_ERR(max14577->regmap_pmic); + dev_err(max14577->dev, "Failed to allocate PMIC register map: %d\n", + ret); + goto err; + } + + /* Un-mask MAX77836 Interrupt Source register */ + ret = max14577_read_reg(max14577->regmap_pmic, + MAX77836_PMIC_REG_INTSRC_MASK, &intsrc_mask); + if (ret < 0) { + dev_err(max14577->dev, "Failed to read PMIC register\n"); + goto err; + } + + intsrc_mask &= ~(MAX77836_INTSRC_MASK_TOP_INT_MASK); + intsrc_mask &= ~(MAX77836_INTSRC_MASK_MUIC_CHG_INT_MASK); + ret = max14577_write_reg(max14577->regmap_pmic, + MAX77836_PMIC_REG_INTSRC_MASK, intsrc_mask); + if (ret < 0) { + dev_err(max14577->dev, "Failed to write PMIC register\n"); + goto err; + } + + ret = regmap_add_irq_chip(max14577->regmap_pmic, max14577->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED, + 0, &max77836_pmic_irq_chip, + &max14577->irq_data_pmic); + if (ret != 0) { + dev_err(max14577->dev, "Failed to request PMIC IRQ %d: %d\n", + max14577->irq, ret); + goto err; + } + + return 0; + +err: + i2c_unregister_device(max14577->i2c_pmic); + + return ret; +} + +/* + * Max77836 specific de-initialization code for driver remove. + */ +static void max77836_remove(struct max14577 *max14577) +{ + regmap_del_irq_chip(max14577->irq, max14577->irq_data_pmic); + i2c_unregister_device(max14577->i2c_pmic); +} + static int max14577_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct max14577 *max14577; struct max14577_platform_data *pdata = dev_get_platdata(&i2c->dev); struct device_node *np = i2c->dev.of_node; - u8 reg_data; int ret = 0; + const struct regmap_irq_chip *irq_chip; + struct mfd_cell *mfd_devs; + unsigned int mfd_devs_size; + int irq_flags; if (np) { pdata = devm_kzalloc(&i2c->dev, sizeof(*pdata), GFP_KERNEL); @@ -113,7 +317,8 @@ static int max14577_i2c_probe(struct i2c_client *i2c, max14577->i2c = i2c; max14577->irq = i2c->irq; - max14577->regmap = devm_regmap_init_i2c(i2c, &max14577_regmap_config); + max14577->regmap = devm_regmap_init_i2c(i2c, + &max14577_muic_regmap_config); if (IS_ERR(max14577->regmap)) { ret = PTR_ERR(max14577->regmap); dev_err(max14577->dev, "Failed to allocate register map: %d\n", @@ -121,23 +326,36 @@ static int max14577_i2c_probe(struct i2c_client *i2c, return ret; } - ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID, - ®_data); - if (ret) { - dev_err(max14577->dev, "Device not found on this channel: %d\n", - ret); - return ret; + if (np) { + const struct of_device_id *of_id; + + of_id = of_match_device(max14577_dt_match, &i2c->dev); + if (of_id) + max14577->dev_type = (unsigned int)of_id->data; + } else { + max14577->dev_type = id->driver_data; + } + + max14577_print_dev_type(max14577); + + switch (max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + irq_chip = &max77836_muic_irq_chip; + mfd_devs = max77836_devs; + mfd_devs_size = ARRAY_SIZE(max77836_devs); + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED; + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + irq_chip = &max14577_irq_chip; + mfd_devs = max14577_devs; + mfd_devs_size = ARRAY_SIZE(max14577_devs); + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + break; } - max14577->vendor_id = ((reg_data & DEVID_VENDORID_MASK) >> - DEVID_VENDORID_SHIFT); - max14577->device_id = ((reg_data & DEVID_DEVICEID_MASK) >> - DEVID_DEVICEID_SHIFT); - dev_info(max14577->dev, "Device ID: 0x%x, vendor: 0x%x\n", - max14577->device_id, max14577->vendor_id); ret = regmap_add_irq_chip(max14577->regmap, max14577->irq, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, - &max14577_irq_chip, + irq_flags, 0, irq_chip, &max14577->irq_data); if (ret != 0) { dev_err(&i2c->dev, "Failed to request IRQ %d: %d\n", @@ -145,8 +363,15 @@ static int max14577_i2c_probe(struct i2c_client *i2c, return ret; } - ret = mfd_add_devices(max14577->dev, -1, max14577_devs, - ARRAY_SIZE(max14577_devs), NULL, 0, + /* Max77836 specific initialization code (additional regmap) */ + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) { + ret = max77836_init(max14577); + if (ret < 0) + goto err_max77836; + } + + ret = mfd_add_devices(max14577->dev, -1, mfd_devs, + mfd_devs_size, NULL, 0, regmap_irq_get_domain(max14577->irq_data)); if (ret < 0) goto err_mfd; @@ -156,6 +381,9 @@ static int max14577_i2c_probe(struct i2c_client *i2c, return 0; err_mfd: + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) + max77836_remove(max14577); +err_max77836: regmap_del_irq_chip(max14577->irq, max14577->irq_data); return ret; @@ -167,12 +395,15 @@ static int max14577_i2c_remove(struct i2c_client *i2c) mfd_remove_devices(max14577->dev); regmap_del_irq_chip(max14577->irq, max14577->irq_data); + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) + max77836_remove(max14577); return 0; } static const struct i2c_device_id max14577_i2c_id[] = { - { "max14577", 0 }, + { "max14577", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836", MAXIM_DEVICE_TYPE_MAX77836, }, { } }; MODULE_DEVICE_TABLE(i2c, max14577_i2c_id); @@ -215,11 +446,6 @@ static int max14577_resume(struct device *dev) } #endif /* CONFIG_PM_SLEEP */ -static struct of_device_id max14577_dt_match[] = { - { .compatible = "maxim,max14577", }, - {}, -}; - static SIMPLE_DEV_PM_OPS(max14577_pm, max14577_suspend, max14577_resume); static struct i2c_driver max14577_i2c_driver = { @@ -236,6 +462,9 @@ static struct i2c_driver max14577_i2c_driver = { static int __init max14577_i2c_init(void) { + BUILD_BUG_ON(ARRAY_SIZE(max14577_i2c_id) != MAXIM_DEVICE_TYPE_NUM); + BUILD_BUG_ON(ARRAY_SIZE(max14577_dt_match) != MAXIM_DEVICE_TYPE_NUM); + return i2c_add_driver(&max14577_i2c_driver); } subsys_initcall(max14577_i2c_init); @@ -247,5 +476,5 @@ static void __exit max14577_i2c_exit(void) module_exit(max14577_i2c_exit); MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>, Krzysztof Kozlowski <k.kozlowski@samsung.com>"); -MODULE_DESCRIPTION("MAXIM 14577 multi-function core driver"); +MODULE_DESCRIPTION("Maxim 14577/77836 multi-function core driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index d9663ef..a43d0c4 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -54,6 +54,7 @@ config AD525X_DPOT_SPI config ATMEL_PWM tristate "Atmel AT32/AT91 PWM support" depends on HAVE_CLK + depends on AVR32 || AT91SAM9263 || AT91SAM9RL || AT91SAM9G45 help This option enables device driver support for the PWM channels on certain Atmel processors. Pulse Width Modulation is used for @@ -200,7 +201,7 @@ config ICS932S401 config ATMEL_SSC tristate "Device driver for Atmel SSC peripheral" - depends on HAS_IOMEM + depends on HAS_IOMEM && (AVR32 || ARCH_AT91 || COMPILE_TEST) ---help--- This option enables device driver support for Atmel Synchronized Serial Communication peripheral (SSC). @@ -468,7 +469,7 @@ config BMP085_SPI config PCH_PHUB tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB" select GENERIC_NET_UTILS - depends on PCI + depends on PCI && (X86_32 || COMPILE_TEST) help This driver is for PCH(Platform controller Hub) PHUB(Packet Hub) of Intel Topcliff which is an IOH(Input/Output Hub) for x86 embedded diff --git a/drivers/misc/arm-charlcd.c b/drivers/misc/arm-charlcd.c index b7ebf80..c72e96b 100644 --- a/drivers/misc/arm-charlcd.c +++ b/drivers/misc/arm-charlcd.c @@ -11,6 +11,7 @@ #include <linux/module.h> #include <linux/interrupt.h> #include <linux/platform_device.h> +#include <linux/of.h> #include <linux/completion.h> #include <linux/delay.h> #include <linux/io.h> @@ -366,11 +367,17 @@ static const struct dev_pm_ops charlcd_pm_ops = { .resume = charlcd_resume, }; +static const struct of_device_id charlcd_match[] = { + { .compatible = "arm,versatile-lcd", }, + {} +}; + static struct platform_driver charlcd_driver = { .driver = { .name = DRIVERNAME, .owner = THIS_MODULE, .pm = &charlcd_pm_ops, + .of_match_table = of_match_ptr(charlcd_match), }, .remove = __exit_p(charlcd_remove), }; diff --git a/drivers/misc/ds1682.c b/drivers/misc/ds1682.c index 6a672f9..b909fb3 100644 --- a/drivers/misc/ds1682.c +++ b/drivers/misc/ds1682.c @@ -85,7 +85,6 @@ static ssize_t ds1682_store(struct device *dev, struct device_attribute *attr, { struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); struct i2c_client *client = to_i2c_client(dev); - char *endp; u64 val; __le32 val_le; int rc; @@ -93,8 +92,8 @@ static ssize_t ds1682_store(struct device *dev, struct device_attribute *attr, dev_dbg(dev, "ds1682_store() called on %s\n", attr->attr.name); /* Decode input */ - val = simple_strtoull(buf, &endp, 0); - if (buf == endp) { + rc = kstrtoull(buf, 0, &val); + if (rc < 0) { dev_dbg(dev, "input string not a number\n"); return -EINVAL; } diff --git a/drivers/misc/genwqe/card_debugfs.c b/drivers/misc/genwqe/card_debugfs.c index 50d2096..0a33ade 100644 --- a/drivers/misc/genwqe/card_debugfs.c +++ b/drivers/misc/genwqe/card_debugfs.c @@ -348,7 +348,7 @@ int genwqe_init_debugfs(struct genwqe_dev *cd) char name[64]; unsigned int i; - sprintf(card_name, "%s%u_card", GENWQE_DEVNAME, cd->card_idx); + sprintf(card_name, "%s%d_card", GENWQE_DEVNAME, cd->card_idx); root = debugfs_create_dir(card_name, cd->debugfs_genwqe); if (!root) { @@ -454,7 +454,7 @@ int genwqe_init_debugfs(struct genwqe_dev *cd) } for (i = 0; i < GENWQE_MAX_VFS; i++) { - sprintf(name, "vf%d_jobtimeout_msec", i); + sprintf(name, "vf%u_jobtimeout_msec", i); file = debugfs_create_u32(name, 0666, root, &cd->vf_jobtimeout_msec[i]); diff --git a/drivers/misc/genwqe/card_utils.c b/drivers/misc/genwqe/card_utils.c index c00adfa..62cc6bb 100644 --- a/drivers/misc/genwqe/card_utils.c +++ b/drivers/misc/genwqe/card_utils.c @@ -454,7 +454,7 @@ int genwqe_setup_sgl(struct genwqe_dev *cd, struct genwqe_sgl *sgl, */ int genwqe_free_sync_sgl(struct genwqe_dev *cd, struct genwqe_sgl *sgl) { - int rc; + int rc = 0; struct pci_dev *pci_dev = cd->pci_dev; if (sgl->fpage) { diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index b8deb34..0d6234d 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -111,8 +111,6 @@ int mei_amthif_host_init(struct mei_device *dev) return ret; } - cl->state = MEI_FILE_CONNECTING; - ret = mei_cl_connect(cl, NULL); dev->iamthif_state = MEI_IAMTHIF_IDLE; diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index ddc5ac9..0e993ef 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -247,7 +247,7 @@ static int ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, return id; if (length > dev->me_clients[id].props.max_msg_length) - return -EINVAL; + return -EFBIG; cb = mei_io_cb_init(cl, NULL); if (!cb) @@ -427,8 +427,6 @@ int mei_cl_enable_device(struct mei_cl_device *device) mutex_lock(&dev->device_lock); - cl->state = MEI_FILE_CONNECTING; - err = mei_cl_connect(cl, NULL); if (err < 0) { mutex_unlock(&dev->device_lock); diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 8c078b8..59d20c5 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -18,6 +18,7 @@ #include <linux/sched.h> #include <linux/wait.h> #include <linux/delay.h> +#include <linux/pm_runtime.h> #include <linux/mei.h> @@ -415,6 +416,10 @@ void mei_host_client_init(struct work_struct *work) dev->reset_count = 0; mutex_unlock(&dev->device_lock); + + pm_runtime_mark_last_busy(&dev->pdev->dev); + dev_dbg(&dev->pdev->dev, "rpm: autosuspend\n"); + pm_runtime_autosuspend(&dev->pdev->dev); } /** @@ -425,6 +430,12 @@ void mei_host_client_init(struct work_struct *work) */ bool mei_hbuf_acquire(struct mei_device *dev) { + if (mei_pg_state(dev) == MEI_PG_ON || + dev->pg_event == MEI_PG_EVENT_WAIT) { + dev_dbg(&dev->pdev->dev, "device is in pg\n"); + return false; + } + if (!dev->hbuf_is_ready) { dev_dbg(&dev->pdev->dev, "hbuf is not ready\n"); return false; @@ -460,9 +471,18 @@ int mei_cl_disconnect(struct mei_cl *cl) if (cl->state != MEI_FILE_DISCONNECTING) return 0; + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + cb = mei_io_cb_init(cl, NULL); - if (!cb) - return -ENOMEM; + if (!cb) { + rets = -ENOMEM; + goto free; + } cb->fop_type = MEI_FOP_CLOSE; if (mei_hbuf_acquire(dev)) { @@ -494,8 +514,7 @@ int mei_cl_disconnect(struct mei_cl *cl) cl_err(dev, cl, "wrong status client disconnect.\n"); if (err) - cl_dbg(dev, cl, "wait failed disconnect err=%08x\n", - err); + cl_dbg(dev, cl, "wait failed disconnect err=%d\n", err); cl_err(dev, cl, "failed to disconnect from FW client.\n"); } @@ -503,6 +522,10 @@ int mei_cl_disconnect(struct mei_cl *cl) mei_io_list_flush(&dev->ctrl_rd_list, cl); mei_io_list_flush(&dev->ctrl_wr_list, cl); free: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + mei_io_cb_free(cb); return rets; } @@ -557,6 +580,13 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) dev = cl->dev; + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + cb = mei_io_cb_init(cl, file); if (!cb) { rets = -ENOMEM; @@ -567,6 +597,7 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) /* run hbuf acquire last so we don't have to undo */ if (!mei_cl_is_other_connecting(cl) && mei_hbuf_acquire(dev)) { + cl->state = MEI_FILE_CONNECTING; if (mei_hbm_cl_connect_req(dev, cl)) { rets = -ENODEV; goto out; @@ -596,6 +627,10 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) rets = cl->status; out: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + mei_io_cb_free(cb); return rets; } @@ -713,23 +748,31 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length) return -ENOTTY; } + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + cb = mei_io_cb_init(cl, NULL); - if (!cb) - return -ENOMEM; + if (!cb) { + rets = -ENOMEM; + goto out; + } /* always allocate at least client max message */ length = max_t(size_t, length, dev->me_clients[i].props.max_msg_length); rets = mei_io_cb_alloc_resp_buf(cb, length); if (rets) - goto err; + goto out; cb->fop_type = MEI_FOP_READ; if (mei_hbuf_acquire(dev)) { - if (mei_hbm_cl_flow_control_req(dev, cl)) { - cl_err(dev, cl, "flow control send failed\n"); - rets = -ENODEV; - goto err; - } + rets = mei_hbm_cl_flow_control_req(dev, cl); + if (rets < 0) + goto out; + list_add_tail(&cb->list, &dev->read_list.list); } else { list_add_tail(&cb->list, &dev->ctrl_wr_list.list); @@ -737,9 +780,14 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length) cl->read_cb = cb; - return rets; -err: - mei_io_cb_free(cb); +out: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + + if (rets) + mei_io_cb_free(cb); + return rets; } @@ -776,7 +824,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, return rets; if (rets == 0) { - cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); + cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); return 0; } @@ -856,6 +904,12 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) cl_dbg(dev, cl, "mei_cl_write %d\n", buf->size); + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } cb->fop_type = MEI_FOP_WRITE; cb->buf_idx = 0; @@ -926,6 +980,10 @@ out: rets = buf->size; err: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + return rets; } diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 4960288..8041062 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -14,10 +14,12 @@ * */ +#include <linux/export.h> #include <linux/pci.h> #include <linux/sched.h> #include <linux/wait.h> #include <linux/mei.h> +#include <linux/pm_runtime.h> #include "mei_dev.h" #include "hbm.h" @@ -58,6 +60,34 @@ static int mei_cl_conn_status_to_errno(enum mei_cl_connect_status status) } /** + * mei_hbm_idle - set hbm to idle state + * + * @dev: the device structure + */ +void mei_hbm_idle(struct mei_device *dev) +{ + dev->init_clients_timer = 0; + dev->hbm_state = MEI_HBM_IDLE; +} + +/** + * mei_hbm_reset - reset hbm counters and book keeping data structurs + * + * @dev: the device structure + */ +void mei_hbm_reset(struct mei_device *dev) +{ + dev->me_clients_num = 0; + dev->me_client_presentation_num = 0; + dev->me_client_index = 0; + + kfree(dev->me_clients); + dev->me_clients = NULL; + + mei_hbm_idle(dev); +} + +/** * mei_hbm_me_cl_allocate - allocates storage for me clients * * @dev: the device structure @@ -69,9 +99,7 @@ static int mei_hbm_me_cl_allocate(struct mei_device *dev) struct mei_me_client *clients; int b; - dev->me_clients_num = 0; - dev->me_client_presentation_num = 0; - dev->me_client_index = 0; + mei_hbm_reset(dev); /* count how many ME clients we have */ for_each_set_bit(b, dev->me_clients_map, MEI_CLIENTS_MAX) @@ -80,9 +108,6 @@ static int mei_hbm_me_cl_allocate(struct mei_device *dev) if (dev->me_clients_num == 0) return 0; - kfree(dev->me_clients); - dev->me_clients = NULL; - dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%ld.\n", dev->me_clients_num * sizeof(struct mei_me_client)); /* allocate storage for ME clients representation */ @@ -133,17 +158,6 @@ bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf) } -/** - * mei_hbm_idle - set hbm to idle state - * - * @dev: the device structure - */ -void mei_hbm_idle(struct mei_device *dev) -{ - dev->init_clients_timer = 0; - dev->hbm_state = MEI_HBM_IDLE; -} - int mei_hbm_start_wait(struct mei_device *dev) { int ret; @@ -289,6 +303,34 @@ static int mei_hbm_prop_req(struct mei_device *dev) return 0; } +/* + * mei_hbm_pg - sends pg command + * + * @dev: the device structure + * @pg_cmd: the pg command code + * + * This function returns -EIO on write failure + */ +int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd) +{ + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; + struct hbm_power_gate *req; + const size_t len = sizeof(struct hbm_power_gate); + int ret; + + mei_hbm_hdr(mei_hdr, len); + + req = (struct hbm_power_gate *)dev->wr_msg.data; + memset(req, 0, len); + req->hbm_cmd = pg_cmd; + + ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); + if (ret) + dev_err(&dev->pdev->dev, "power gate command write failed.\n"); + return ret; +} +EXPORT_SYMBOL_GPL(mei_hbm_pg); + /** * mei_hbm_stop_req - send stop request message * @@ -701,6 +743,27 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) mei_hbm_cl_flow_control_res(dev, flow_control); break; + case MEI_PG_ISOLATION_ENTRY_RES_CMD: + dev_dbg(&dev->pdev->dev, "power gate isolation entry response received\n"); + dev->pg_event = MEI_PG_EVENT_RECEIVED; + if (waitqueue_active(&dev->wait_pg)) + wake_up(&dev->wait_pg); + break; + + case MEI_PG_ISOLATION_EXIT_REQ_CMD: + dev_dbg(&dev->pdev->dev, "power gate isolation exit request received\n"); + dev->pg_event = MEI_PG_EVENT_RECEIVED; + if (waitqueue_active(&dev->wait_pg)) + wake_up(&dev->wait_pg); + else + /* + * If the driver is not waiting on this then + * this is HW initiated exit from PG. + * Start runtime pm resume sequence to exit from PG. + */ + pm_request_resume(&dev->pdev->dev); + break; + case HOST_CLIENT_PROPERTIES_RES_CMD: dev_dbg(&dev->pdev->dev, "hbm: properties response: message received.\n"); diff --git a/drivers/misc/mei/hbm.h b/drivers/misc/mei/hbm.h index 20e8782..683eb28 100644 --- a/drivers/misc/mei/hbm.h +++ b/drivers/misc/mei/hbm.h @@ -50,6 +50,7 @@ static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length) } void mei_hbm_idle(struct mei_device *dev); +void mei_hbm_reset(struct mei_device *dev); int mei_hbm_start_req(struct mei_device *dev); int mei_hbm_start_wait(struct mei_device *dev); int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl); @@ -57,6 +58,7 @@ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl); bool mei_hbm_version_is_supported(struct mei_device *dev); +int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd); #endif /* _MEI_HBM_H_ */ diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index cabc043..a7856c0 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -133,6 +133,8 @@ #define ME_CB_RW 8 /* ME_CSR_HA - ME Control Status Host Access register (read only) */ #define ME_CSR_HA 0xC +/* H_HGC_CSR - PGI register */ +#define H_HPG_CSR 0x10 /* register bits of H_CSR (Host Control Status register) */ @@ -162,6 +164,8 @@ access to ME_CBD */ #define ME_CBWP_HRA 0x00FF0000 /* ME CB Read Pointer HRA - host read only access to ME_CBRP */ #define ME_CBRP_HRA 0x0000FF00 +/* ME Power Gate Isolation Capability HRA - host ready only access */ +#define ME_PGIC_HRA 0x00000040 /* ME Reset HRA - host read only access to ME_RST */ #define ME_RST_HRA 0x00000010 /* ME Ready HRA - host read only access to ME_RDY */ @@ -173,4 +177,9 @@ access to ME_CBD */ /* ME Interrupt Enable HRA - host read only access to ME_IE */ #define ME_IE_HRA 0x00000001 + +/* register bits - H_HPG_CSR */ +#define H_HPG_CSR_PGIHEXR 0x00000001 +#define H_HPG_CSR_PGI 0x00000002 + #endif /* _MEI_HW_MEI_REGS_H_ */ diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 8dbdaae..6a2d272 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -109,10 +109,27 @@ static inline void mei_hcsr_set(struct mei_me_hw *hw, u32 hcsr) */ static void mei_me_hw_config(struct mei_device *dev) { + struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr = mei_hcsr_read(to_me_hw(dev)); /* Doesn't change in runtime */ dev->hbuf_depth = (hcsr & H_CBD) >> 24; + + hw->pg_state = MEI_PG_OFF; +} + +/** + * mei_me_pg_state - translate internal pg state + * to the mei power gating state + * + * @hw - me hardware + * returns: MEI_PG_OFF if aliveness is on and MEI_PG_ON otherwise + */ +static inline enum mei_pg_state mei_me_pg_state(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + return hw->pg_state; } + /** * mei_clear_interrupts - clear and stop interrupts * @@ -164,6 +181,9 @@ static void mei_me_hw_reset_release(struct mei_device *dev) hcsr |= H_IG; hcsr &= ~H_RST; mei_hcsr_set(hw, hcsr); + + /* complete this write before we set host ready on another CPU */ + mmiowb(); } /** * mei_me_hw_reset - resets fw via mei csr register. @@ -183,8 +203,21 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) else hcsr &= ~H_IE; + dev->recvd_hw_ready = false; mei_me_reg_write(hw, H_CSR, hcsr); + /* + * Host reads the H_CSR once to ensure that the + * posted write to H_CSR completes. + */ + hcsr = mei_hcsr_read(hw); + + if ((hcsr & H_RST) == 0) + dev_warn(&dev->pdev->dev, "H_RST is not set = 0x%08X", hcsr); + + if ((hcsr & H_RDY) == H_RDY) + dev_warn(&dev->pdev->dev, "H_RDY is not cleared 0x%08X", hcsr); + if (intr_enable == false) mei_me_hw_reset_release(dev); @@ -201,6 +234,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) static void mei_me_host_set_ready(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); + hw->host_hw_state = mei_hcsr_read(hw); hw->host_hw_state |= H_IE | H_IG | H_RDY; mei_hcsr_set(hw, hw->host_hw_state); } @@ -233,10 +267,7 @@ static bool mei_me_hw_is_ready(struct mei_device *dev) static int mei_me_hw_ready_wait(struct mei_device *dev) { int err; - if (mei_me_hw_is_ready(dev)) - return 0; - dev->recvd_hw_ready = false; mutex_unlock(&dev->device_lock); err = wait_event_interruptible_timeout(dev->wait_hw_ready, dev->recvd_hw_ready, @@ -431,6 +462,144 @@ static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, } /** + * mei_me_pg_enter - write pg enter register to mei device. + * + * @dev: the device structure + */ +static void mei_me_pg_enter(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + u32 reg = mei_me_reg_read(hw, H_HPG_CSR); + reg |= H_HPG_CSR_PGI; + mei_me_reg_write(hw, H_HPG_CSR, reg); +} + +/** + * mei_me_pg_enter - write pg enter register to mei device. + * + * @dev: the device structure + */ +static void mei_me_pg_exit(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + u32 reg = mei_me_reg_read(hw, H_HPG_CSR); + + WARN(!(reg & H_HPG_CSR_PGI), "PGI is not set\n"); + + reg |= H_HPG_CSR_PGIHEXR; + mei_me_reg_write(hw, H_HPG_CSR, reg); +} + +/** + * mei_me_pg_set_sync - perform pg entry procedure + * + * @dev: the device structure + * + * returns 0 on success an error code otherwise + */ +int mei_me_pg_set_sync(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); + int ret; + + dev->pg_event = MEI_PG_EVENT_WAIT; + + ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_ENTRY_REQ_CMD); + if (ret) + return ret; + + mutex_unlock(&dev->device_lock); + wait_event_timeout(dev->wait_pg, + dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); + mutex_lock(&dev->device_lock); + + if (dev->pg_event == MEI_PG_EVENT_RECEIVED) { + mei_me_pg_enter(dev); + ret = 0; + } else { + ret = -ETIME; + } + + dev->pg_event = MEI_PG_EVENT_IDLE; + hw->pg_state = MEI_PG_ON; + + return ret; +} + +/** + * mei_me_pg_unset_sync - perform pg exit procedure + * + * @dev: the device structure + * + * returns 0 on success an error code otherwise + */ +int mei_me_pg_unset_sync(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); + int ret; + + if (dev->pg_event == MEI_PG_EVENT_RECEIVED) + goto reply; + + dev->pg_event = MEI_PG_EVENT_WAIT; + + mei_me_pg_exit(dev); + + mutex_unlock(&dev->device_lock); + wait_event_timeout(dev->wait_pg, + dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); + mutex_lock(&dev->device_lock); + +reply: + if (dev->pg_event == MEI_PG_EVENT_RECEIVED) + ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_EXIT_RES_CMD); + else + ret = -ETIME; + + dev->pg_event = MEI_PG_EVENT_IDLE; + hw->pg_state = MEI_PG_OFF; + + return ret; +} + +/** + * mei_me_pg_is_enabled - detect if PG is supported by HW + * + * @dev: the device structure + * + * returns: true is pg supported, false otherwise + */ +static bool mei_me_pg_is_enabled(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + u32 reg = mei_me_reg_read(hw, ME_CSR_HA); + + if ((reg & ME_PGIC_HRA) == 0) + goto notsupported; + + if (dev->version.major_version < HBM_MAJOR_VERSION_PGI) + goto notsupported; + + if (dev->version.major_version == HBM_MAJOR_VERSION_PGI && + dev->version.minor_version < HBM_MINOR_VERSION_PGI) + goto notsupported; + + return true; + +notsupported: + dev_dbg(&dev->pdev->dev, "pg: not supported: HGP = %d hbm version %d.%d ?= %d.%d\n", + !!(reg & ME_PGIC_HRA), + dev->version.major_version, + dev->version.minor_version, + HBM_MAJOR_VERSION_PGI, + HBM_MINOR_VERSION_PGI); + + return false; +} + +/** * mei_me_irq_quick_handler - The ISR of the MEI device * * @irq: The irq number @@ -491,14 +660,13 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) /* check if we need to start the dev */ if (!mei_host_is_ready(dev)) { if (mei_hw_is_ready(dev)) { + mei_me_hw_reset_release(dev); dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); dev->recvd_hw_ready = true; wake_up_interruptible(&dev->wait_hw_ready); } else { - - dev_dbg(&dev->pdev->dev, "Reset Completed.\n"); - mei_me_hw_reset_release(dev); + dev_dbg(&dev->pdev->dev, "Spurious Interrupt\n"); } goto end; } @@ -524,9 +692,15 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) dev->hbuf_is_ready = mei_hbuf_is_ready(dev); - rets = mei_irq_write_handler(dev, &complete_list); - - dev->hbuf_is_ready = mei_hbuf_is_ready(dev); + /* + * During PG handshake only allowed write is the replay to the + * PG exit message, so block calling write function + * if the pg state is not idle + */ + if (dev->pg_event == MEI_PG_EVENT_IDLE) { + rets = mei_irq_write_handler(dev, &complete_list); + dev->hbuf_is_ready = mei_hbuf_is_ready(dev); + } mei_irq_compl_handler(dev, &complete_list); @@ -535,8 +709,65 @@ end: mutex_unlock(&dev->device_lock); return IRQ_HANDLED; } + +/** + * mei_me_fw_status - retrieve fw status from the pci config space + * + * @dev: the device structure + * @fw_status: fw status registers storage + * + * returns 0 on success an error code otherwise + */ +static int mei_me_fw_status(struct mei_device *dev, + struct mei_fw_status *fw_status) +{ + const u32 pci_cfg_reg[] = {PCI_CFG_HFS_1, PCI_CFG_HFS_2}; + int i; + + if (!fw_status) + return -EINVAL; + + switch (dev->pdev->device) { + case MEI_DEV_ID_IBXPK_1: + case MEI_DEV_ID_IBXPK_2: + case MEI_DEV_ID_CPT_1: + case MEI_DEV_ID_PBG_1: + case MEI_DEV_ID_PPT_1: + case MEI_DEV_ID_PPT_2: + case MEI_DEV_ID_PPT_3: + case MEI_DEV_ID_LPT_H: + case MEI_DEV_ID_LPT_W: + case MEI_DEV_ID_LPT_LP: + case MEI_DEV_ID_LPT_HR: + case MEI_DEV_ID_WPT_LP: + fw_status->count = 2; + break; + case MEI_DEV_ID_ICH10_1: + case MEI_DEV_ID_ICH10_2: + case MEI_DEV_ID_ICH10_3: + case MEI_DEV_ID_ICH10_4: + fw_status->count = 1; + break; + default: + fw_status->count = 0; + break; + } + + for (i = 0; i < fw_status->count && i < MEI_FW_STATUS_MAX; i++) { + int ret; + ret = pci_read_config_dword(dev->pdev, + pci_cfg_reg[i], &fw_status->status[i]); + if (ret) + return ret; + } + return 0; +} + static const struct mei_hw_ops mei_me_hw_ops = { + .pg_state = mei_me_pg_state, + + .fw_status = mei_me_fw_status, .host_is_ready = mei_me_host_is_ready, .hw_is_ready = mei_me_hw_is_ready, @@ -544,6 +775,8 @@ static const struct mei_hw_ops mei_me_hw_ops = { .hw_config = mei_me_hw_config, .hw_start = mei_me_hw_start, + .pg_is_enabled = mei_me_pg_is_enabled, + .intr_clear = mei_me_intr_clear, .intr_enable = mei_me_intr_enable, .intr_disable = mei_me_intr_disable, @@ -559,14 +792,81 @@ static const struct mei_hw_ops mei_me_hw_ops = { .read = mei_me_read_slots }; +static bool mei_me_fw_type_nm(struct pci_dev *pdev) +{ + u32 reg; + pci_read_config_dword(pdev, PCI_CFG_HFS_2, ®); + /* make sure that bit 9 (NM) is up and bit 10 (DM) is down */ + return (reg & 0x600) == 0x200; +} + +#define MEI_CFG_FW_NM \ + .quirk_probe = mei_me_fw_type_nm + +static bool mei_me_fw_type_sps(struct pci_dev *pdev) +{ + u32 reg; + /* Read ME FW Status check for SPS Firmware */ + pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®); + /* if bits [19:16] = 15, running SPS Firmware */ + return (reg & 0xf0000) == 0xf0000; +} + +#define MEI_CFG_FW_SPS \ + .quirk_probe = mei_me_fw_type_sps + + +#define MEI_CFG_LEGACY_HFS \ + .fw_status.count = 0 + +#define MEI_CFG_ICH_HFS \ + .fw_status.count = 1, \ + .fw_status.status[0] = PCI_CFG_HFS_1 + +#define MEI_CFG_PCH_HFS \ + .fw_status.count = 2, \ + .fw_status.status[0] = PCI_CFG_HFS_1, \ + .fw_status.status[1] = PCI_CFG_HFS_2 + + +/* ICH Legacy devices */ +const struct mei_cfg mei_me_legacy_cfg = { + MEI_CFG_LEGACY_HFS, +}; + +/* ICH devices */ +const struct mei_cfg mei_me_ich_cfg = { + MEI_CFG_ICH_HFS, +}; + +/* PCH devices */ +const struct mei_cfg mei_me_pch_cfg = { + MEI_CFG_PCH_HFS, +}; + + +/* PCH Cougar Point and Patsburg with quirk for Node Manager exclusion */ +const struct mei_cfg mei_me_pch_cpt_pbg_cfg = { + MEI_CFG_PCH_HFS, + MEI_CFG_FW_NM, +}; + +/* PCH Lynx Point with quirk for SPS Firmware exclusion */ +const struct mei_cfg mei_me_lpt_cfg = { + MEI_CFG_PCH_HFS, + MEI_CFG_FW_SPS, +}; + /** * mei_me_dev_init - allocates and initializes the mei device structure * * @pdev: The pci device structure + * @cfg: per device generation config * * returns The mei_device_device pointer on success, NULL on failure. */ -struct mei_device *mei_me_dev_init(struct pci_dev *pdev) +struct mei_device *mei_me_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg) { struct mei_device *dev; @@ -575,7 +875,7 @@ struct mei_device *mei_me_dev_init(struct pci_dev *pdev) if (!dev) return NULL; - mei_device_init(dev); + mei_device_init(dev, cfg); dev->ops = &mei_me_hw_ops; diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 893d511..12b0f4b 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -24,6 +24,8 @@ #include "mei_dev.h" #include "client.h" +#define MEI_ME_RPM_TIMEOUT 500 /* ms */ + struct mei_me_hw { void __iomem *mem_addr; /* @@ -31,11 +33,22 @@ struct mei_me_hw { */ u32 host_hw_state; u32 me_hw_state; + enum mei_pg_state pg_state; }; #define to_me_hw(dev) (struct mei_me_hw *)((dev)->hw) -struct mei_device *mei_me_dev_init(struct pci_dev *pdev); +extern const struct mei_cfg mei_me_legacy_cfg; +extern const struct mei_cfg mei_me_ich_cfg; +extern const struct mei_cfg mei_me_pch_cfg; +extern const struct mei_cfg mei_me_pch_cpt_pbg_cfg; +extern const struct mei_cfg mei_me_lpt_cfg; + +struct mei_device *mei_me_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg); + +int mei_me_pg_set_sync(struct mei_device *dev); +int mei_me_pg_unset_sync(struct mei_device *dev); irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id); irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id); diff --git a/drivers/misc/mei/hw-txe-regs.h b/drivers/misc/mei/hw-txe-regs.h index 7283c24..f19229c 100644 --- a/drivers/misc/mei/hw-txe-regs.h +++ b/drivers/misc/mei/hw-txe-regs.h @@ -89,7 +89,7 @@ enum { # define PCI_CFG_TXE_FW_STS0_ERR_CODE_MSK 0x0000F000 # define PCI_CFG_TXE_FW_STS0_OP_MODE_MSK 0x000F0000 # define PCI_CFG_TXE_FW_STS0_RST_CNT_MSK 0x00F00000 - +#define PCI_CFG_TXE_FW_STS1 0x48 #define IPC_BASE_ADDR 0x80400 /* SeC IPC Base Address */ diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index f60182a..9327378 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -158,7 +158,7 @@ static bool mei_txe_aliveness_set(struct mei_device *dev, u32 req) dev_dbg(&dev->pdev->dev, "Aliveness current=%d request=%d\n", hw->aliveness, req); if (do_req) { - hw->recvd_aliveness = false; + dev->pg_event = MEI_PG_EVENT_WAIT; mei_txe_br_reg_write(hw, SICR_HOST_ALIVENESS_REQ_REG, req); } return do_req; @@ -213,6 +213,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) do { hw->aliveness = mei_txe_aliveness_get(dev); if (hw->aliveness == expected) { + dev->pg_event = MEI_PG_EVENT_IDLE; dev_dbg(&dev->pdev->dev, "aliveness settled after %d msecs\n", t); return t; @@ -223,6 +224,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) t += MSEC_PER_SEC / 5; } while (t < SEC_ALIVENESS_WAIT_TIMEOUT); + dev->pg_event = MEI_PG_EVENT_IDLE; dev_err(&dev->pdev->dev, "aliveness timed out\n"); return -ETIME; } @@ -249,19 +251,22 @@ static int mei_txe_aliveness_wait(struct mei_device *dev, u32 expected) return 0; mutex_unlock(&dev->device_lock); - err = wait_event_timeout(hw->wait_aliveness, - hw->recvd_aliveness, timeout); + err = wait_event_timeout(hw->wait_aliveness_resp, + dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); mutex_lock(&dev->device_lock); hw->aliveness = mei_txe_aliveness_get(dev); ret = hw->aliveness == expected ? 0 : -ETIME; if (ret) - dev_err(&dev->pdev->dev, "aliveness timed out"); + dev_warn(&dev->pdev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n", + err, hw->aliveness, dev->pg_event); else - dev_dbg(&dev->pdev->dev, "aliveness settled after %d msecs\n", - jiffies_to_msecs(timeout - err)); - hw->recvd_aliveness = false; + dev_dbg(&dev->pdev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n", + jiffies_to_msecs(timeout - err), + hw->aliveness, dev->pg_event); + + dev->pg_event = MEI_PG_EVENT_IDLE; return ret; } @@ -280,6 +285,32 @@ int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req) } /** + * mei_txe_pg_is_enabled - detect if PG is supported by HW + * + * @dev: the device structure + * + * returns: true is pg supported, false otherwise + */ +static bool mei_txe_pg_is_enabled(struct mei_device *dev) +{ + return true; +} + +/** + * mei_txe_pg_state - translate aliveness register value + * to the mei power gating state + * + * @dev: the device structure + * + * returns: MEI_PG_OFF if aliveness is on and MEI_PG_ON otherwise + */ +static inline enum mei_pg_state mei_txe_pg_state(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + return hw->aliveness ? MEI_PG_OFF : MEI_PG_ON; +} + +/** * mei_txe_input_ready_interrupt_enable - sets the Input Ready Interrupt * * @dev: the device structure @@ -589,7 +620,10 @@ static int mei_txe_write(struct mei_device *dev, mei_txe_input_ready_interrupt_enable(dev); if (!mei_txe_is_input_ready(dev)) { - dev_err(&dev->pdev->dev, "Input is not ready"); + struct mei_fw_status fw_status; + mei_fw_status(dev, &fw_status); + dev_err(&dev->pdev->dev, "Input is not ready " FW_STS_FMT "\n", + FW_STS_PRM(fw_status)); return -EAGAIN; } @@ -960,9 +994,9 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) /* Clear the interrupt cause */ dev_dbg(&dev->pdev->dev, "Aliveness Interrupt: Status: %d\n", hw->aliveness); - hw->recvd_aliveness = true; - if (waitqueue_active(&hw->wait_aliveness)) - wake_up(&hw->wait_aliveness); + dev->pg_event = MEI_PG_EVENT_RECEIVED; + if (waitqueue_active(&hw->wait_aliveness_resp)) + wake_up(&hw->wait_aliveness_resp); } @@ -1008,15 +1042,51 @@ end: return IRQ_HANDLED; } + +/** + * mei_txe_fw_status - retrieve fw status from the pci config space + * + * @dev: the device structure + * @fw_status: fw status registers storage + * + * returns: 0 on success an error code otherwise + */ +static int mei_txe_fw_status(struct mei_device *dev, + struct mei_fw_status *fw_status) +{ + const u32 pci_cfg_reg[] = {PCI_CFG_TXE_FW_STS0, PCI_CFG_TXE_FW_STS1}; + int i; + + if (!fw_status) + return -EINVAL; + + fw_status->count = 2; + + for (i = 0; i < fw_status->count && i < MEI_FW_STATUS_MAX; i++) { + int ret; + ret = pci_read_config_dword(dev->pdev, + pci_cfg_reg[i], &fw_status->status[i]); + if (ret) + return ret; + } + + return 0; +} + static const struct mei_hw_ops mei_txe_hw_ops = { + .fw_status = mei_txe_fw_status, .host_is_ready = mei_txe_host_is_ready, + .pg_state = mei_txe_pg_state, + .hw_is_ready = mei_txe_hw_is_ready, .hw_reset = mei_txe_hw_reset, .hw_config = mei_txe_hw_config, .hw_start = mei_txe_hw_start, + .pg_is_enabled = mei_txe_pg_is_enabled, + .intr_clear = mei_txe_intr_clear, .intr_enable = mei_txe_intr_enable, .intr_disable = mei_txe_intr_disable, @@ -1034,14 +1104,27 @@ static const struct mei_hw_ops mei_txe_hw_ops = { }; +#define MEI_CFG_TXE_FW_STS \ + .fw_status.count = 2, \ + .fw_status.status[0] = PCI_CFG_TXE_FW_STS0, \ + .fw_status.status[1] = PCI_CFG_TXE_FW_STS1 + +const struct mei_cfg mei_txe_cfg = { + MEI_CFG_TXE_FW_STS, +}; + + /** * mei_txe_dev_init - allocates and initializes txe hardware specific structure * * @pdev - pci device + * @cfg - per device generation config + * * returns struct mei_device * on success or NULL; * */ -struct mei_device *mei_txe_dev_init(struct pci_dev *pdev) +struct mei_device *mei_txe_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg) { struct mei_device *dev; struct mei_txe_hw *hw; @@ -1051,11 +1134,11 @@ struct mei_device *mei_txe_dev_init(struct pci_dev *pdev) if (!dev) return NULL; - mei_device_init(dev); + mei_device_init(dev, cfg); hw = to_txe_hw(dev); - init_waitqueue_head(&hw->wait_aliveness); + init_waitqueue_head(&hw->wait_aliveness_resp); dev->ops = &mei_txe_hw_ops; diff --git a/drivers/misc/mei/hw-txe.h b/drivers/misc/mei/hw-txe.h index 0812d98..e244af7 100644 --- a/drivers/misc/mei/hw-txe.h +++ b/drivers/misc/mei/hw-txe.h @@ -22,6 +22,8 @@ #include "hw.h" #include "hw-txe-regs.h" +#define MEI_TXI_RPM_TIMEOUT 500 /* ms */ + /* Flatten Hierarchy interrupt cause */ #define TXE_INTR_READINESS_BIT 0 /* HISR_INT_0_STS */ #define TXE_INTR_READINESS HISR_INT_0_STS @@ -35,12 +37,11 @@ /** * struct mei_txe_hw - txe hardware specifics * - * @mem_addr: SeC and BRIDGE bars - * @aliveness: aliveness (power gating) state of the hardware - * @readiness: readiness state of the hardware - * @wait_aliveness: aliveness wait queue - * @recvd_aliveness: aliveness interrupt was recived - * @intr_cause: translated interrupt cause + * @mem_addr: SeC and BRIDGE bars + * @aliveness: aliveness (power gating) state of the hardware + * @readiness: readiness state of the hardware + * @wait_aliveness_resp: aliveness wait queue + * @intr_cause: translated interrupt cause */ struct mei_txe_hw { void __iomem *mem_addr[NUM_OF_MEM_BARS]; @@ -48,8 +49,7 @@ struct mei_txe_hw { u32 readiness; u32 slots; - wait_queue_head_t wait_aliveness; - bool recvd_aliveness; + wait_queue_head_t wait_aliveness_resp; unsigned long intr_cause; }; @@ -61,7 +61,10 @@ static inline struct mei_device *hw_txe_to_mei(struct mei_txe_hw *hw) return container_of((void *)hw, struct mei_device, hw); } -struct mei_device *mei_txe_dev_init(struct pci_dev *pdev); +extern const struct mei_cfg mei_txe_cfg; + +struct mei_device *mei_txe_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg); irqreturn_t mei_txe_irq_quick_handler(int irq, void *dev_id); irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id); diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 6b476ab..dd448e5 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -31,14 +31,21 @@ #define MEI_IAMTHIF_STALL_TIMER 12 /* HPS */ #define MEI_IAMTHIF_READ_TIMER 10 /* HPS */ +#define MEI_PGI_TIMEOUT 1 /* PG Isolation time response 1 sec */ #define MEI_HBM_TIMEOUT 1 /* 1 second */ /* * MEI Version */ -#define HBM_MINOR_VERSION 0 +#define HBM_MINOR_VERSION 1 #define HBM_MAJOR_VERSION 1 +/* + * MEI version with PGI support + */ +#define HBM_MINOR_VERSION_PGI 1 +#define HBM_MAJOR_VERSION_PGI 1 + /* Host bus message command opcode */ #define MEI_HBM_CMD_OP_MSK 0x7f /* Host bus message command RESPONSE */ @@ -69,6 +76,11 @@ #define MEI_FLOW_CONTROL_CMD 0x08 +#define MEI_PG_ISOLATION_ENTRY_REQ_CMD 0x0a +#define MEI_PG_ISOLATION_ENTRY_RES_CMD 0x8a +#define MEI_PG_ISOLATION_EXIT_REQ_CMD 0x0b +#define MEI_PG_ISOLATION_EXIT_RES_CMD 0x8b + /* * MEI Stop Reason * used by hbm_host_stop_request.reason @@ -208,6 +220,17 @@ struct hbm_props_response { } __packed; /** + * struct hbm_power_gate - power gate request/response + * + * @hbm_cmd - bus message command header + * @reserved[3] + */ +struct hbm_power_gate { + u8 hbm_cmd; + u8 reserved[3]; +} __packed; + +/** * struct hbm_client_connect_request - connect/disconnect request * * @hbm_cmd - bus message command header diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 4460975..0069292 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -74,9 +74,13 @@ int mei_reset(struct mei_device *dev) if (state != MEI_DEV_INITIALIZING && state != MEI_DEV_DISABLED && state != MEI_DEV_POWER_DOWN && - state != MEI_DEV_POWER_UP) - dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n", - mei_dev_state_str(state)); + state != MEI_DEV_POWER_UP) { + struct mei_fw_status fw_status; + mei_fw_status(dev, &fw_status); + dev_warn(&dev->pdev->dev, + "unexpected reset: dev_state = %s " FW_STS_FMT "\n", + mei_dev_state_str(state), FW_STS_PRM(fw_status)); + } /* we're already in reset, cancel the init timer * if the reset was called due the hbm protocol error @@ -118,8 +122,8 @@ int mei_reset(struct mei_device *dev) mei_amthif_reset_params(dev); } + mei_hbm_reset(dev); - dev->me_clients_num = 0; dev->rd_msg_hdr = 0; dev->wd_pending = false; @@ -303,15 +307,58 @@ void mei_stop(struct mei_device *dev) } EXPORT_SYMBOL_GPL(mei_stop); +/** + * mei_write_is_idle - check if the write queues are idle + * + * @dev: the device structure + * + * returns true of there is no pending write + */ +bool mei_write_is_idle(struct mei_device *dev) +{ + bool idle = (dev->dev_state == MEI_DEV_ENABLED && + list_empty(&dev->ctrl_wr_list.list) && + list_empty(&dev->write_list.list)); + dev_dbg(&dev->pdev->dev, "write pg: is idle[%d] state=%s ctrl=%d write=%d\n", + idle, + mei_dev_state_str(dev->dev_state), + list_empty(&dev->ctrl_wr_list.list), + list_empty(&dev->write_list.list)); -void mei_device_init(struct mei_device *dev) + return idle; +} +EXPORT_SYMBOL_GPL(mei_write_is_idle); + +int mei_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status) +{ + int i; + const struct mei_fw_status *fw_src = &dev->cfg->fw_status; + + if (!fw_status) + return -EINVAL; + + fw_status->count = fw_src->count; + for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { + int ret; + ret = pci_read_config_dword(dev->pdev, + fw_src->status[i], &fw_status->status[i]); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mei_fw_status); + +void mei_device_init(struct mei_device *dev, const struct mei_cfg *cfg) { /* setup our list array */ INIT_LIST_HEAD(&dev->file_list); INIT_LIST_HEAD(&dev->device_list); mutex_init(&dev->device_lock); init_waitqueue_head(&dev->wait_hw_ready); + init_waitqueue_head(&dev->wait_pg); init_waitqueue_head(&dev->wait_recvd_msg); init_waitqueue_head(&dev->wait_stop_wd); dev->dev_state = MEI_DEV_INITIALIZING; @@ -340,6 +387,9 @@ void mei_device_init(struct mei_device *dev) * 0: Reserved for MEI Bus Message communications */ bitmap_set(dev->host_clients_map, 0, 1); + + dev->pg_event = MEI_PG_EVENT_IDLE; + dev->cfg = cfg; } EXPORT_SYMBOL_GPL(mei_device_init); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 1474131..66f0a1a 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -467,7 +467,6 @@ static int mei_ioctl_connect_client(struct file *file, } cl->me_client_id = dev->me_clients[i].client_id; - cl->state = MEI_FILE_CONNECTING; dev_dbg(&dev->pdev->dev, "Connect to FW Client ID = %d\n", cl->me_client_id); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 94a5167..5c7e990 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -153,6 +153,20 @@ struct mei_msg_data { unsigned char *data; }; +/* Maximum number of processed FW status registers */ +#define MEI_FW_STATUS_MAX 2 + +/* + * struct mei_fw_status - storage of FW status data + * + * @count - number of actually available elements in array + * @status - FW status registers + */ +struct mei_fw_status { + int count; + u32 status[MEI_FW_STATUS_MAX]; +}; + /** * struct mei_me_client - representation of me (fw) client * @@ -213,6 +227,7 @@ struct mei_cl { /** struct mei_hw_ops * + * @fw_status - read FW status from PCI config space * @host_is_ready - query for host readiness * @hw_is_ready - query if hw is ready @@ -220,6 +235,9 @@ struct mei_cl { * @hw_start - start hw after reset * @hw_config - configure hw + * @pg_state - power gating state of the device + * @pg_is_enabled - is power gating enabled + * @intr_clear - clear pending interrupts * @intr_enable - enable interrupts * @intr_disable - disable interrupts @@ -237,6 +255,8 @@ struct mei_cl { */ struct mei_hw_ops { + int (*fw_status)(struct mei_device *dev, + struct mei_fw_status *fw_status); bool (*host_is_ready)(struct mei_device *dev); bool (*hw_is_ready)(struct mei_device *dev); @@ -244,6 +264,9 @@ struct mei_hw_ops { int (*hw_start)(struct mei_device *dev); void (*hw_config)(struct mei_device *dev); + enum mei_pg_state (*pg_state)(struct mei_device *dev); + bool (*pg_is_enabled)(struct mei_device *dev); + void (*intr_clear)(struct mei_device *dev); void (*intr_enable)(struct mei_device *dev); void (*intr_disable)(struct mei_device *dev); @@ -331,16 +354,61 @@ struct mei_cl_device { void *priv_data; }; + + /** + * enum mei_pg_event - power gating transition events + * + * @MEI_PG_EVENT_IDLE: the driver is not in power gating transition + * @MEI_PG_EVENT_WAIT: the driver is waiting for a pg event to complete + * @MEI_PG_EVENT_RECEIVED: the driver received pg event + */ +enum mei_pg_event { + MEI_PG_EVENT_IDLE, + MEI_PG_EVENT_WAIT, + MEI_PG_EVENT_RECEIVED, +}; + +/** + * enum mei_pg_state - device internal power gating state + * + * @MEI_PG_OFF: device is not power gated - it is active + * @MEI_PG_ON: device is power gated - it is in lower power state + */ +enum mei_pg_state { + MEI_PG_OFF = 0, + MEI_PG_ON = 1, +}; + +/* + * mei_cfg + * + * @fw_status - FW status + * @quirk_probe - device exclusion quirk + */ +struct mei_cfg { + const struct mei_fw_status fw_status; + bool (*quirk_probe)(struct pci_dev *pdev); +}; + + +#define MEI_PCI_DEVICE(dev, cfg) \ + .vendor = PCI_VENDOR_ID_INTEL, .device = (dev), \ + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, \ + .driver_data = (kernel_ulong_t)&(cfg) + + /** * struct mei_device - MEI private device struct * @reset_count - limits the number of consecutive resets * @hbm_state - state of host bus message protocol + * @pg_event - power gating event * @mem_addr - mem mapped base register address * @hbuf_depth - depth of hardware host/write buffer is slots * @hbuf_is_ready - query if the host host/write buffer is ready * @wr_msg - the buffer for hbm control messages + * @cfg - per device generation config and ops */ struct mei_device { struct pci_dev *pdev; /* pointer to pci device struct */ @@ -371,6 +439,7 @@ struct mei_device { * waiting queue for receive message from FW */ wait_queue_head_t wait_hw_ready; + wait_queue_head_t wait_pg; wait_queue_head_t wait_recvd_msg; wait_queue_head_t wait_stop_wd; @@ -382,6 +451,14 @@ struct mei_device { enum mei_hbm_state hbm_state; u16 init_clients_timer; + /* + * Power Gating support + */ + enum mei_pg_event pg_event; +#ifdef CONFIG_PM_RUNTIME + struct dev_pm_domain pg_domain; +#endif /* CONFIG_PM_RUNTIME */ + unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */ u32 rd_msg_hdr; @@ -442,6 +519,7 @@ struct mei_device { const struct mei_hw_ops *ops; + const struct mei_cfg *cfg; char hw[0] __aligned(sizeof(void *)); }; @@ -474,7 +552,7 @@ static inline u32 mei_slots2data(int slots) /* * mei init function prototypes */ -void mei_device_init(struct mei_device *dev); +void mei_device_init(struct mei_device *dev, const struct mei_cfg *cfg); int mei_reset(struct mei_device *dev); int mei_start(struct mei_device *dev); int mei_restart(struct mei_device *dev); @@ -553,10 +631,22 @@ void mei_watchdog_unregister(struct mei_device *dev); * Register Access Function */ + static inline void mei_hw_config(struct mei_device *dev) { dev->ops->hw_config(dev); } + +static inline enum mei_pg_state mei_pg_state(struct mei_device *dev) +{ + return dev->ops->pg_state(dev); +} + +static inline bool mei_pg_is_enabled(struct mei_device *dev) +{ + return dev->ops->pg_is_enabled(dev); +} + static inline int mei_hw_reset(struct mei_device *dev, bool enable) { return dev->ops->hw_reset(dev, enable); @@ -629,8 +719,17 @@ static inline int mei_count_full_read_slots(struct mei_device *dev) return dev->ops->rdbuf_full_slots(dev); } +int mei_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status); + +#define FW_STS_FMT "%08X %08X" +#define FW_STS_PRM(fw_status) \ + (fw_status).count > 0 ? (fw_status).status[0] : 0xDEADBEEF, \ + (fw_status).count > 1 ? (fw_status).status[1] : 0xDEADBEEF + bool mei_hbuf_acquire(struct mei_device *dev); +bool mei_write_is_idle(struct mei_device *dev); + #if IS_ENABLED(CONFIG_DEBUG_FS) int mei_dbgfs_register(struct mei_device *dev, const char *name); void mei_dbgfs_deregister(struct mei_device *dev); diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 95889e2..1b46c64 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -33,6 +33,8 @@ #include <linux/interrupt.h> #include <linux/miscdevice.h> +#include <linux/pm_runtime.h> + #include <linux/mei.h> #include "mei_dev.h" @@ -42,42 +44,44 @@ /* mei_pci_tbl - PCI Device ID Table */ static const struct pci_device_id mei_me_pci_tbl[] = { - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82946GZ)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G35)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82Q965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GM965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GME965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q35)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82G33)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q33)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82X38)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_3200)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_6)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_7)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_8)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_9)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_10)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_4)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_4)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_CPT_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PBG_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_H)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_W)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_LP)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_HR)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_WPT_LP)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82946GZ, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82G35, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82Q965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82G965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82GM965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82GME965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82Q35, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82G33, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82Q33, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82X38, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_3200, mei_me_legacy_cfg)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_6, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_7, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_8, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_9, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_10, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_1, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_2, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_3, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_4, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_1, mei_me_ich_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_2, mei_me_ich_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_3, mei_me_ich_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_4, mei_me_ich_cfg)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_IBXPK_1, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_IBXPK_2, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_CPT_1, mei_me_pch_cpt_pbg_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PBG_1, mei_me_pch_cpt_pbg_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_1, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_2, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_3, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_H, mei_me_lpt_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_W, mei_me_lpt_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_LP, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_HR, mei_me_lpt_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_WPT_LP, mei_me_pch_cfg)}, /* required last entry */ {0, } @@ -85,44 +89,33 @@ static const struct pci_device_id mei_me_pci_tbl[] = { MODULE_DEVICE_TABLE(pci, mei_me_pci_tbl); +#ifdef CONFIG_PM_RUNTIME +static inline void mei_me_set_pm_domain(struct mei_device *dev); +static inline void mei_me_unset_pm_domain(struct mei_device *dev); +#else +static inline void mei_me_set_pm_domain(struct mei_device *dev) {} +static inline void mei_me_unset_pm_domain(struct mei_device *dev) {} +#endif /* CONFIG_PM_RUNTIME */ + /** * mei_quirk_probe - probe for devices that doesn't valid ME interface * * @pdev: PCI device structure - * @ent: entry into pci_device_table + * @cfg: per generation config * * returns true if ME Interface is valid, false otherwise */ static bool mei_me_quirk_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) + const struct mei_cfg *cfg) { - u32 reg; - /* Cougar Point || Patsburg */ - if (ent->device == MEI_DEV_ID_CPT_1 || - ent->device == MEI_DEV_ID_PBG_1) { - pci_read_config_dword(pdev, PCI_CFG_HFS_2, ®); - /* make sure that bit 9 (NM) is up and bit 10 (DM) is down */ - if ((reg & 0x600) == 0x200) - goto no_mei; - } - - /* Lynx Point */ - if (ent->device == MEI_DEV_ID_LPT_H || - ent->device == MEI_DEV_ID_LPT_W || - ent->device == MEI_DEV_ID_LPT_HR) { - /* Read ME FW Status check for SPS Firmware */ - pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®); - /* if bits [19:16] = 15, running SPS Firmware */ - if ((reg & 0xf0000) == 0xf0000) - goto no_mei; + if (cfg->quirk_probe && cfg->quirk_probe(pdev)) { + dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n"); + return false; } return true; - -no_mei: - dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n"); - return false; } + /** * mei_probe - Device Initialization Routine * @@ -133,15 +126,14 @@ no_mei: */ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { + const struct mei_cfg *cfg = (struct mei_cfg *)(ent->driver_data); struct mei_device *dev; struct mei_me_hw *hw; int err; - if (!mei_me_quirk_probe(pdev, ent)) { - err = -ENODEV; - goto end; - } + if (!mei_me_quirk_probe(pdev, cfg)) + return -ENODEV; /* enable pci dev */ err = pci_enable_device(pdev); @@ -173,7 +165,7 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) /* allocates and initializes the mei dev structure */ - dev = mei_me_dev_init(pdev); + dev = mei_me_dev_init(pdev, cfg); if (!dev) { err = -ENOMEM; goto release_regions; @@ -212,6 +204,9 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto release_irq; } + pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_ME_RPM_TIMEOUT); + pm_runtime_use_autosuspend(&pdev->dev); + err = mei_register(dev); if (err) goto release_irq; @@ -220,6 +215,17 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) schedule_delayed_work(&dev->timer_work, HZ); + /* + * For not wake-able HW runtime pm framework + * can't be used on pci device level. + * Use domain runtime pm callbacks instead. + */ + if (!pci_dev_run_wake(pdev)) + mei_me_set_pm_domain(dev); + + if (mei_pg_is_enabled(dev)) + pm_runtime_put_noidle(&pdev->dev); + dev_dbg(&pdev->dev, "initialization successful.\n"); return 0; @@ -259,12 +265,18 @@ static void mei_me_remove(struct pci_dev *pdev) if (!dev) return; + if (mei_pg_is_enabled(dev)) + pm_runtime_get_noresume(&pdev->dev); + hw = to_me_hw(dev); dev_dbg(&pdev->dev, "stop\n"); mei_stop(dev); + if (!pci_dev_run_wake(pdev)) + mei_me_unset_pm_domain(dev); + /* disable interrupts */ mei_disable_interrupts(dev); @@ -343,12 +355,120 @@ static int mei_me_pci_resume(struct device *device) return 0; } +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int mei_me_pm_runtime_idle(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + + dev_dbg(&pdev->dev, "rpm: me: runtime_idle\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + if (mei_write_is_idle(dev)) + pm_schedule_suspend(device, MEI_ME_RPM_TIMEOUT * 2); + + return -EBUSY; +} + +static int mei_me_pm_runtime_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: me: runtime suspend\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + if (mei_write_is_idle(dev)) + ret = mei_me_pg_set_sync(dev); + else + ret = -EAGAIN; + + mutex_unlock(&dev->device_lock); + + dev_dbg(&pdev->dev, "rpm: me: runtime suspend ret=%d\n", ret); + + return ret; +} + +static int mei_me_pm_runtime_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: me: runtime resume\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + ret = mei_me_pg_unset_sync(dev); + + mutex_unlock(&dev->device_lock); + + dev_dbg(&pdev->dev, "rpm: me: runtime resume ret = %d\n", ret); + + return ret; +} + +/** + * mei_me_set_pm_domain - fill and set pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_me_set_pm_domain(struct mei_device *dev) +{ + struct pci_dev *pdev = dev->pdev; + + if (pdev->dev.bus && pdev->dev.bus->pm) { + dev->pg_domain.ops = *pdev->dev.bus->pm; + + dev->pg_domain.ops.runtime_suspend = mei_me_pm_runtime_suspend; + dev->pg_domain.ops.runtime_resume = mei_me_pm_runtime_resume; + dev->pg_domain.ops.runtime_idle = mei_me_pm_runtime_idle; + + pdev->dev.pm_domain = &dev->pg_domain; + } +} + +/** + * mei_me_unset_pm_domain - clean pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_me_unset_pm_domain(struct mei_device *dev) +{ + /* stop using pm callbacks if any */ + dev->pdev->dev.pm_domain = NULL; +} +#endif /* CONFIG_PM_RUNTIME */ + +#ifdef CONFIG_PM +static const struct dev_pm_ops mei_me_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mei_me_pci_suspend, + mei_me_pci_resume) + SET_RUNTIME_PM_OPS( + mei_me_pm_runtime_suspend, + mei_me_pm_runtime_resume, + mei_me_pm_runtime_idle) +}; -static SIMPLE_DEV_PM_OPS(mei_me_pm_ops, mei_me_pci_suspend, mei_me_pci_resume); #define MEI_ME_PM_OPS (&mei_me_pm_ops) #else #define MEI_ME_PM_OPS NULL -#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ /* * PCI driver structure */ diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index ad3adb0..2343c62 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -27,6 +27,7 @@ #include <linux/jiffies.h> #include <linux/interrupt.h> #include <linux/workqueue.h> +#include <linux/pm_runtime.h> #include <linux/mei.h> @@ -35,11 +36,18 @@ #include "hw-txe.h" static const struct pci_device_id mei_txe_pci_tbl[] = { - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0F18)}, /* Baytrail */ + {MEI_PCI_DEVICE(0x0F18, mei_txe_cfg)}, /* Baytrail */ {0, } }; MODULE_DEVICE_TABLE(pci, mei_txe_pci_tbl); +#ifdef CONFIG_PM_RUNTIME +static inline void mei_txe_set_pm_domain(struct mei_device *dev); +static inline void mei_txe_unset_pm_domain(struct mei_device *dev); +#else +static inline void mei_txe_set_pm_domain(struct mei_device *dev) {} +static inline void mei_txe_unset_pm_domain(struct mei_device *dev) {} +#endif /* CONFIG_PM_RUNTIME */ static void mei_txe_pci_iounmap(struct pci_dev *pdev, struct mei_txe_hw *hw) { @@ -61,6 +69,7 @@ static void mei_txe_pci_iounmap(struct pci_dev *pdev, struct mei_txe_hw *hw) */ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { + const struct mei_cfg *cfg = (struct mei_cfg *)(ent->driver_data); struct mei_device *dev; struct mei_txe_hw *hw; int err; @@ -91,7 +100,7 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) } /* allocates and initializes the mei dev structure */ - dev = mei_txe_dev_init(pdev); + dev = mei_txe_dev_init(pdev, cfg); if (!dev) { err = -ENOMEM; goto release_regions; @@ -137,12 +146,25 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto release_irq; } + pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_TXI_RPM_TIMEOUT); + pm_runtime_use_autosuspend(&pdev->dev); + err = mei_register(dev); if (err) goto release_irq; pci_set_drvdata(pdev, dev); + /* + * For not wake-able HW runtime pm framework + * can't be used on pci device level. + * Use domain runtime pm callbacks instead. + */ + if (!pci_dev_run_wake(pdev)) + mei_txe_set_pm_domain(dev); + + pm_runtime_put_noidle(&pdev->dev); + return 0; release_irq: @@ -187,10 +209,15 @@ static void mei_txe_remove(struct pci_dev *pdev) return; } + pm_runtime_get_noresume(&pdev->dev); + hw = to_txe_hw(dev); mei_stop(dev); + if (!pci_dev_run_wake(pdev)) + mei_txe_unset_pm_domain(dev); + /* disable interrupts */ mei_disable_interrupts(dev); free_irq(pdev->irq, dev); @@ -265,15 +292,131 @@ static int mei_txe_pci_resume(struct device *device) return err; } +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int mei_txe_pm_runtime_idle(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + + dev_dbg(&pdev->dev, "rpm: txe: runtime_idle\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + if (mei_write_is_idle(dev)) + pm_schedule_suspend(device, MEI_TXI_RPM_TIMEOUT * 2); + + return -EBUSY; +} +static int mei_txe_pm_runtime_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: txe: runtime suspend\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + if (mei_write_is_idle(dev)) + ret = mei_txe_aliveness_set_sync(dev, 0); + else + ret = -EAGAIN; + + /* + * If everything is okay we're about to enter PCI low + * power state (D3) therefor we need to disable the + * interrupts towards host. + * However if device is not wakeable we do not enter + * D-low state and we need to keep the interrupt kicking + */ + if (!ret && pci_dev_run_wake(pdev)) + mei_disable_interrupts(dev); + + dev_dbg(&pdev->dev, "rpm: txe: runtime suspend ret=%d\n", ret); + + mutex_unlock(&dev->device_lock); + return ret; +} + +static int mei_txe_pm_runtime_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: txe: runtime resume\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + mei_enable_interrupts(dev); + + ret = mei_txe_aliveness_set_sync(dev, 1); + + mutex_unlock(&dev->device_lock); + + dev_dbg(&pdev->dev, "rpm: txe: runtime resume ret = %d\n", ret); + + return ret; +} + +/** + * mei_txe_set_pm_domain - fill and set pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_txe_set_pm_domain(struct mei_device *dev) +{ + struct pci_dev *pdev = dev->pdev; + + if (pdev->dev.bus && pdev->dev.bus->pm) { + dev->pg_domain.ops = *pdev->dev.bus->pm; + + dev->pg_domain.ops.runtime_suspend = mei_txe_pm_runtime_suspend; + dev->pg_domain.ops.runtime_resume = mei_txe_pm_runtime_resume; + dev->pg_domain.ops.runtime_idle = mei_txe_pm_runtime_idle; + + pdev->dev.pm_domain = &dev->pg_domain; + } +} -static SIMPLE_DEV_PM_OPS(mei_txe_pm_ops, - mei_txe_pci_suspend, - mei_txe_pci_resume); +/** + * mei_txe_unset_pm_domain - clean pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_txe_unset_pm_domain(struct mei_device *dev) +{ + /* stop using pm callbacks if any */ + dev->pdev->dev.pm_domain = NULL; +} +#endif /* CONFIG_PM_RUNTIME */ + +#ifdef CONFIG_PM +static const struct dev_pm_ops mei_txe_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mei_txe_pci_suspend, + mei_txe_pci_resume) + SET_RUNTIME_PM_OPS( + mei_txe_pm_runtime_suspend, + mei_txe_pm_runtime_resume, + mei_txe_pm_runtime_idle) +}; #define MEI_TXE_PM_OPS (&mei_txe_pm_ops) #else #define MEI_TXE_PM_OPS NULL -#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ + /* * PCI driver structure */ diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index ebf1cbc..a84a664 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -84,8 +84,6 @@ int mei_wd_host_init(struct mei_device *dev) return ret; } - cl->state = MEI_FILE_CONNECTING; - ret = mei_cl_connect(cl, NULL); if (ret) { diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 903eb37..f0cc9e6 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -266,11 +266,12 @@ config REGULATOR_LP8788 This driver supports LP8788 voltage regulator chip. config REGULATOR_MAX14577 - tristate "Maxim 14577 regulator" + tristate "Maxim 14577/77836 regulator" depends on MFD_MAX14577 help - This driver controls a Maxim 14577 regulator via I2C bus. - The regulators include safeout LDO and current regulator 'CHARGER'. + This driver controls a Maxim MAX14577/77836 regulator via I2C bus. + The MAX14577 regulators include safeout LDO and charger current + regulator. The MAX77836 has two additional LDOs. config REGULATOR_MAX1586 tristate "Maxim 1586/1587 voltage regulator" diff --git a/drivers/regulator/max14577.c b/drivers/regulator/max14577.c index ed60baa..5d9c605 100644 --- a/drivers/regulator/max14577.c +++ b/drivers/regulator/max14577.c @@ -1,5 +1,5 @@ /* - * max14577.c - Regulator driver for the Maxim 14577 + * max14577.c - Regulator driver for the Maxim 14577/77836 * * Copyright (C) 2013,2014 Samsung Electronics * Krzysztof Kozlowski <k.kozlowski@samsung.com> @@ -22,6 +22,42 @@ #include <linux/mfd/max14577-private.h> #include <linux/regulator/of_regulator.h> +/* + * Valid limits of current for max14577 and max77836 chargers. + * They must correspond to MBCICHWRCL and MBCICHWRCH fields in CHGCTRL4 + * register for given chipset. + */ +struct maxim_charger_current { + /* Minimal current, set in CHGCTRL4/MBCICHWRCL, uA */ + unsigned int min; + /* + * Minimal current when high setting is active, + * set in CHGCTRL4/MBCICHWRCH, uA + */ + unsigned int high_start; + /* Value of one step in high setting, uA */ + unsigned int high_step; + /* Maximum current of high setting, uA */ + unsigned int max; +}; + +/* Table of valid charger currents for different Maxim chipsets */ +static const struct maxim_charger_current maxim_charger_currents[] = { + [MAXIM_DEVICE_TYPE_UNKNOWN] = { 0, 0, 0, 0 }, + [MAXIM_DEVICE_TYPE_MAX14577] = { + .min = MAX14577_REGULATOR_CURRENT_LIMIT_MIN, + .high_start = MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START, + .high_step = MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP, + .max = MAX14577_REGULATOR_CURRENT_LIMIT_MAX, + }, + [MAXIM_DEVICE_TYPE_MAX77836] = { + .min = MAX77836_REGULATOR_CURRENT_LIMIT_MIN, + .high_start = MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START, + .high_step = MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP, + .max = MAX77836_REGULATOR_CURRENT_LIMIT_MAX, + }, +}; + static int max14577_reg_is_enabled(struct regulator_dev *rdev) { int rid = rdev_get_id(rdev); @@ -47,6 +83,9 @@ static int max14577_reg_get_current_limit(struct regulator_dev *rdev) { u8 reg_data; struct regmap *rmap = rdev->regmap; + struct max14577 *max14577 = rdev_get_drvdata(rdev); + const struct maxim_charger_current *limits = + &maxim_charger_currents[max14577->dev_type]; if (rdev_get_id(rdev) != MAX14577_CHARGER) return -EINVAL; @@ -54,12 +93,11 @@ static int max14577_reg_get_current_limit(struct regulator_dev *rdev) max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL4, ®_data); if ((reg_data & CHGCTRL4_MBCICHWRCL_MASK) == 0) - return MAX14577_REGULATOR_CURRENT_LIMIT_MIN; + return limits->min; reg_data = ((reg_data & CHGCTRL4_MBCICHWRCH_MASK) >> CHGCTRL4_MBCICHWRCH_SHIFT); - return MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START + - reg_data * MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP; + return limits->high_start + reg_data * limits->high_step; } static int max14577_reg_set_current_limit(struct regulator_dev *rdev, @@ -67,33 +105,39 @@ static int max14577_reg_set_current_limit(struct regulator_dev *rdev, { int i, current_bits = 0xf; u8 reg_data; + struct max14577 *max14577 = rdev_get_drvdata(rdev); + const struct maxim_charger_current *limits = + &maxim_charger_currents[max14577->dev_type]; if (rdev_get_id(rdev) != MAX14577_CHARGER) return -EINVAL; - if (min_uA > MAX14577_REGULATOR_CURRENT_LIMIT_MAX || - max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_MIN) + if (min_uA > limits->max || max_uA < limits->min) return -EINVAL; - if (max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START) { - /* Less than 200 mA, so set 90mA (turn only Low Bit off) */ + if (max_uA < limits->high_start) { + /* + * Less than high_start, + * so set the minimal current (turn only Low Bit off) + */ u8 reg_data = 0x0 << CHGCTRL4_MBCICHWRCL_SHIFT; return max14577_update_reg(rdev->regmap, MAX14577_CHG_REG_CHG_CTRL4, CHGCTRL4_MBCICHWRCL_MASK, reg_data); } - /* max_uA is in range: <LIMIT_HIGH_START, inifinite>, so search for - * valid current starting from LIMIT_MAX. */ - for (i = MAX14577_REGULATOR_CURRENT_LIMIT_MAX; - i >= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START; - i -= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP) { + /* + * max_uA is in range: <high_start, inifinite>, so search for + * valid current starting from maximum current. + */ + for (i = limits->max; i >= limits->high_start; i -= limits->high_step) { if (i <= max_uA) break; current_bits--; } BUG_ON(current_bits < 0); /* Cannot happen */ - /* Turn Low Bit on (use range 200mA-950 mA) */ + + /* Turn Low Bit on (use range high_start-max)... */ reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; /* and set proper High Bits */ reg_data |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT; @@ -118,7 +162,7 @@ static struct regulator_ops max14577_charger_ops = { .set_current_limit = max14577_reg_set_current_limit, }; -static const struct regulator_desc supported_regulators[] = { +static const struct regulator_desc max14577_supported_regulators[] = { [MAX14577_SAFEOUT] = { .name = "SAFEOUT", .id = MAX14577_SAFEOUT, @@ -141,16 +185,88 @@ static const struct regulator_desc supported_regulators[] = { }, }; +static struct regulator_ops max77836_ldo_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + /* TODO: add .set_suspend_mode */ +}; + +static const struct regulator_desc max77836_supported_regulators[] = { + [MAX14577_SAFEOUT] = { + .name = "SAFEOUT", + .id = MAX14577_SAFEOUT, + .ops = &max14577_safeout_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = 1, + .min_uV = MAX14577_REGULATOR_SAFEOUT_VOLTAGE, + .enable_reg = MAX14577_REG_CONTROL2, + .enable_mask = CTRL2_SFOUTORD_MASK, + }, + [MAX14577_CHARGER] = { + .name = "CHARGER", + .id = MAX14577_CHARGER, + .ops = &max14577_charger_ops, + .type = REGULATOR_CURRENT, + .owner = THIS_MODULE, + .enable_reg = MAX14577_CHG_REG_CHG_CTRL2, + .enable_mask = CHGCTRL2_MBCHOSTEN_MASK, + }, + [MAX77836_LDO1] = { + .name = "LDO1", + .id = MAX77836_LDO1, + .ops = &max77836_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM, + .min_uV = MAX77836_REGULATOR_LDO_VOLTAGE_MIN, + .uV_step = MAX77836_REGULATOR_LDO_VOLTAGE_STEP, + .enable_reg = MAX77836_LDO_REG_CNFG1_LDO1, + .enable_mask = MAX77836_CNFG1_LDO_PWRMD_MASK, + .vsel_reg = MAX77836_LDO_REG_CNFG1_LDO1, + .vsel_mask = MAX77836_CNFG1_LDO_TV_MASK, + }, + [MAX77836_LDO2] = { + .name = "LDO2", + .id = MAX77836_LDO2, + .ops = &max77836_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM, + .min_uV = MAX77836_REGULATOR_LDO_VOLTAGE_MIN, + .uV_step = MAX77836_REGULATOR_LDO_VOLTAGE_STEP, + .enable_reg = MAX77836_LDO_REG_CNFG1_LDO2, + .enable_mask = MAX77836_CNFG1_LDO_PWRMD_MASK, + .vsel_reg = MAX77836_LDO_REG_CNFG1_LDO2, + .vsel_mask = MAX77836_CNFG1_LDO_TV_MASK, + }, +}; + #ifdef CONFIG_OF static struct of_regulator_match max14577_regulator_matches[] = { { .name = "SAFEOUT", }, { .name = "CHARGER", }, }; -static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) +static struct of_regulator_match max77836_regulator_matches[] = { + { .name = "SAFEOUT", }, + { .name = "CHARGER", }, + { .name = "LDO1", }, + { .name = "LDO2", }, +}; + +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev, + enum maxim_device_type dev_type) { int ret; struct device_node *np; + struct of_regulator_match *regulator_matches; + unsigned int regulator_matches_size; np = of_get_child_by_name(pdev->dev.parent->of_node, "regulators"); if (!np) { @@ -158,8 +274,19 @@ static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) return -EINVAL; } - ret = of_regulator_match(&pdev->dev, np, max14577_regulator_matches, - MAX14577_REG_MAX); + switch (dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + regulator_matches = max77836_regulator_matches; + regulator_matches_size = ARRAY_SIZE(max77836_regulator_matches); + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + regulator_matches = max14577_regulator_matches; + regulator_matches_size = ARRAY_SIZE(max14577_regulator_matches); + } + + ret = of_regulator_match(&pdev->dev, np, regulator_matches, + regulator_matches_size); if (ret < 0) dev_err(&pdev->dev, "Error parsing regulator init data: %d\n", ret); else @@ -170,31 +297,74 @@ static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) return ret; } -static inline struct regulator_init_data *match_init_data(int index) +static inline struct regulator_init_data *match_init_data(int index, + enum maxim_device_type dev_type) { - return max14577_regulator_matches[index].init_data; + switch (dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + return max77836_regulator_matches[index].init_data; + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + return max14577_regulator_matches[index].init_data; + } } -static inline struct device_node *match_of_node(int index) +static inline struct device_node *match_of_node(int index, + enum maxim_device_type dev_type) { - return max14577_regulator_matches[index].of_node; + switch (dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + return max77836_regulator_matches[index].of_node; + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + return max14577_regulator_matches[index].of_node; + } } #else /* CONFIG_OF */ -static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev, + enum maxim_device_type dev_type) { return 0; } -static inline struct regulator_init_data *match_init_data(int index) +static inline struct regulator_init_data *match_init_data(int index, + enum maxim_device_type dev_type) { return NULL; } -static inline struct device_node *match_of_node(int index) +static inline struct device_node *match_of_node(int index, + enum maxim_device_type dev_type) { return NULL; } #endif /* CONFIG_OF */ +/** + * Registers for regulators of max77836 use different I2C slave addresses so + * different regmaps must be used for them. + * + * Returns proper regmap for accessing regulator passed by id. + */ +static struct regmap *max14577_get_regmap(struct max14577 *max14577, + int reg_id) +{ + switch (max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + switch (reg_id) { + case MAX77836_SAFEOUT ... MAX77836_CHARGER: + return max14577->regmap; + default: + /* MAX77836_LDO1 ... MAX77836_LDO2 */ + return max14577->regmap_pmic; + } + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + return max14577->regmap; + } +} static int max14577_regulator_probe(struct platform_device *pdev) { @@ -202,15 +372,29 @@ static int max14577_regulator_probe(struct platform_device *pdev) struct max14577_platform_data *pdata = dev_get_platdata(max14577->dev); int i, ret; struct regulator_config config = {}; + const struct regulator_desc *supported_regulators; + unsigned int supported_regulators_size; + enum maxim_device_type dev_type = max14577->dev_type; - ret = max14577_regulator_dt_parse_pdata(pdev); + ret = max14577_regulator_dt_parse_pdata(pdev, dev_type); if (ret) return ret; + switch (dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + supported_regulators = max77836_supported_regulators; + supported_regulators_size = ARRAY_SIZE(max77836_supported_regulators); + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + supported_regulators = max14577_supported_regulators; + supported_regulators_size = ARRAY_SIZE(max14577_supported_regulators); + } + config.dev = &pdev->dev; - config.regmap = max14577->regmap; + config.driver_data = max14577; - for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) { + for (i = 0; i < supported_regulators_size; i++) { struct regulator_dev *regulator; /* * Index of supported_regulators[] is also the id and must @@ -220,17 +404,19 @@ static int max14577_regulator_probe(struct platform_device *pdev) config.init_data = pdata->regulators[i].initdata; config.of_node = pdata->regulators[i].of_node; } else { - config.init_data = match_init_data(i); - config.of_node = match_of_node(i); + config.init_data = match_init_data(i, dev_type); + config.of_node = match_of_node(i, dev_type); } + config.regmap = max14577_get_regmap(max14577, + supported_regulators[i].id); regulator = devm_regulator_register(&pdev->dev, &supported_regulators[i], &config); if (IS_ERR(regulator)) { ret = PTR_ERR(regulator); dev_err(&pdev->dev, - "Regulator init failed for ID %d with error: %d\n", - i, ret); + "Regulator init failed for %d/%s with error: %d\n", + i, supported_regulators[i].name, ret); return ret; } } @@ -238,20 +424,41 @@ static int max14577_regulator_probe(struct platform_device *pdev) return ret; } +static const struct platform_device_id max14577_regulator_id[] = { + { "max14577-regulator", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836-regulator", MAXIM_DEVICE_TYPE_MAX77836, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max14577_regulator_id); + static struct platform_driver max14577_regulator_driver = { .driver = { .owner = THIS_MODULE, .name = "max14577-regulator", }, - .probe = max14577_regulator_probe, + .probe = max14577_regulator_probe, + .id_table = max14577_regulator_id, }; static int __init max14577_regulator_init(void) { + /* Check for valid values for charger */ BUILD_BUG_ON(MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START + MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf != MAX14577_REGULATOR_CURRENT_LIMIT_MAX); - BUILD_BUG_ON(ARRAY_SIZE(supported_regulators) != MAX14577_REG_MAX); + BUILD_BUG_ON(MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START + + MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf != + MAX77836_REGULATOR_CURRENT_LIMIT_MAX); + /* Valid charger current values must be provided for each chipset */ + BUILD_BUG_ON(ARRAY_SIZE(maxim_charger_currents) != MAXIM_DEVICE_TYPE_NUM); + + BUILD_BUG_ON(ARRAY_SIZE(max14577_supported_regulators) != MAX14577_REGULATOR_NUM); + BUILD_BUG_ON(ARRAY_SIZE(max77836_supported_regulators) != MAX77836_REGULATOR_NUM); + + BUILD_BUG_ON(MAX77836_REGULATOR_LDO_VOLTAGE_MIN + + (MAX77836_REGULATOR_LDO_VOLTAGE_STEP * + (MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM - 1)) != + MAX77836_REGULATOR_LDO_VOLTAGE_MAX); return platform_driver_register(&max14577_regulator_driver); } @@ -264,6 +471,6 @@ static void __exit max14577_regulator_exit(void) module_exit(max14577_regulator_exit); MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>"); -MODULE_DESCRIPTION("MAXIM 14577 regulator driver"); +MODULE_DESCRIPTION("Maxim 14577/77836 regulator driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:max14577-regulator"); diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index a673e5b..e371f5a 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -655,7 +655,7 @@ static int uio_mmap_physical(struct vm_area_struct *vma) if (mem->addr & ~PAGE_MASK) return -ENODEV; - if (vma->vm_end - vma->vm_start > mem->size) + if (vma->vm_end - vma->vm_start > PAGE_ALIGN(mem->size)) return -EINVAL; vma->vm_ops = &uio_physical_vm_ops; diff --git a/drivers/uio/uio_dmem_genirq.c b/drivers/uio/uio_dmem_genirq.c index 1270f3b..8d0bba4 100644 --- a/drivers/uio/uio_dmem_genirq.c +++ b/drivers/uio/uio_dmem_genirq.c @@ -204,7 +204,7 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev) ret = platform_get_irq(pdev, 0); if (ret < 0) { dev_err(&pdev->dev, "failed to get IRQ\n"); - goto bad0; + goto bad1; } uioinfo->irq = ret; } @@ -275,6 +275,7 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev) ret = uio_register_device(&pdev->dev, priv->uioinfo); if (ret) { dev_err(&pdev->dev, "unable to register uio device\n"); + pm_runtime_disable(&pdev->dev); goto bad1; } @@ -282,7 +283,6 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev) return 0; bad1: kfree(priv); - pm_runtime_disable(&pdev->dev); bad0: /* kfree uioinfo for OF */ if (pdev->dev.of_node) diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index ff52618..5d73415 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -1078,6 +1078,8 @@ static void w1_search_process(struct w1_master *dev, u8 search_type) * w1_process_callbacks() - execute each dev->async_list callback entry * @dev: w1_master device * + * The w1 master list_mutex must be held. + * * Return: 1 if there were commands to executed 0 otherwise */ int w1_process_callbacks(struct w1_master *dev) diff --git a/drivers/w1/w1.h b/drivers/w1/w1.h index 734dab7..56a49ba 100644 --- a/drivers/w1/w1.h +++ b/drivers/w1/w1.h @@ -203,7 +203,6 @@ enum w1_master_flags { * @search_id: allows continuing a search * @refcnt: reference count * @priv: private data storage - * @priv_size: size allocated * @enable_pullup: allows a strong pullup * @pullup_duration: time for the next strong pullup * @flags: one of w1_master_flags @@ -214,7 +213,6 @@ enum w1_master_flags { * @dev: sysfs device * @bus_master: io operations available * @seq: sequence number used for netlink broadcasts - * @portid: destination for the current netlink command */ struct w1_master { @@ -241,7 +239,6 @@ struct w1_master atomic_t refcnt; void *priv; - int priv_size; /** 5V strong pullup enabled flag, 1 enabled, zero disabled. */ int enable_pullup; @@ -260,11 +257,6 @@ struct w1_master struct w1_bus_master *bus_master; u32 seq; - /* port id to send netlink responses to. The value is temporarily - * stored here while processing a message, set after locking the - * mutex, zero before unlocking the mutex. - */ - u32 portid; }; /** diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c index 9b084db..728039d2 100644 --- a/drivers/w1/w1_int.c +++ b/drivers/w1/w1_int.c @@ -219,9 +219,13 @@ void __w1_remove_master_device(struct w1_master *dev) if (msleep_interruptible(1000)) flush_signals(current); + mutex_lock(&dev->list_mutex); w1_process_callbacks(dev); + mutex_unlock(&dev->list_mutex); } + mutex_lock(&dev->list_mutex); w1_process_callbacks(dev); + mutex_unlock(&dev->list_mutex); memset(&msg, 0, sizeof(msg)); msg.id.mst.id = dev->id; diff --git a/drivers/w1/w1_netlink.c b/drivers/w1/w1_netlink.c index a02704a..351a297 100644 --- a/drivers/w1/w1_netlink.c +++ b/drivers/w1/w1_netlink.c @@ -29,51 +29,247 @@ #include "w1_netlink.h" #if defined(CONFIG_W1_CON) && (defined(CONFIG_CONNECTOR) || (defined(CONFIG_CONNECTOR_MODULE) && defined(CONFIG_W1_MODULE))) -void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg) + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +/* Bundle together everything required to process a request in one memory + * allocation. + */ +struct w1_cb_block { + atomic_t refcnt; + u32 portid; /* Sending process port ID */ + /* maximum value for first_cn->len */ + u16 maxlen; + /* pointers to building up the reply message */ + struct cn_msg *first_cn; /* fixed once the structure is populated */ + struct cn_msg *cn; /* advances as cn_msg is appeneded */ + struct w1_netlink_msg *msg; /* advances as w1_netlink_msg is appened */ + struct w1_netlink_cmd *cmd; /* advances as cmds are appened */ + struct w1_netlink_msg *cur_msg; /* currently message being processed */ + /* copy of the original request follows */ + struct cn_msg request_cn; + /* followed by variable length: + * cn_msg, data (w1_netlink_msg and w1_netlink_cmd) + * one or more struct w1_cb_node + * reply first_cn, data (w1_netlink_msg and w1_netlink_cmd) + */ +}; +struct w1_cb_node { + struct w1_async_cmd async; + /* pointers within w1_cb_block and cn data */ + struct w1_cb_block *block; + struct w1_netlink_msg *msg; + struct w1_slave *sl; + struct w1_master *dev; +}; + +/** + * w1_reply_len() - calculate current reply length, compare to maxlen + * @block: block to calculate + * + * Calculates the current message length including possible multiple + * cn_msg and data, excludes the first sizeof(struct cn_msg). Direclty + * compariable to maxlen and usable to send the message. + */ +static u16 w1_reply_len(struct w1_cb_block *block) +{ + if (!block->cn) + return 0; + return (u8 *)block->cn - (u8 *)block->first_cn + block->cn->len; +} + +static void w1_unref_block(struct w1_cb_block *block) +{ + if (atomic_sub_return(1, &block->refcnt) == 0) { + u16 len = w1_reply_len(block); + if (len) { + cn_netlink_send_mult(block->first_cn, len, + block->portid, 0, GFP_KERNEL); + } + kfree(block); + } +} + +/** + * w1_reply_make_space() - send message if needed to make space + * @block: block to make space on + * @space: how many bytes requested + * + * Verify there is enough room left for the caller to add "space" bytes to the + * message, if there isn't send the message and reset. + */ +static void w1_reply_make_space(struct w1_cb_block *block, u16 space) +{ + u16 len = w1_reply_len(block); + if (len + space >= block->maxlen) { + cn_netlink_send_mult(block->first_cn, len, block->portid, 0, GFP_KERNEL); + block->first_cn->len = 0; + block->cn = NULL; + block->msg = NULL; + block->cmd = NULL; + } +} + +/* Early send when replies aren't bundled. */ +static void w1_netlink_check_send(struct w1_cb_block *block) +{ + if (!(block->request_cn.flags & W1_CN_BUNDLE) && block->cn) + w1_reply_make_space(block, block->maxlen); +} + +/** + * w1_netlink_setup_msg() - prepare to write block->msg + * @block: block to operate on + * @ack: determines if cn can be reused + * + * block->cn will be setup with the correct ack, advancing if needed + * block->cn->len does not include space for block->msg + * block->msg advances but remains uninitialized + */ +static void w1_netlink_setup_msg(struct w1_cb_block *block, u32 ack) +{ + if (block->cn && block->cn->ack == ack) { + block->msg = (struct w1_netlink_msg *)(block->cn->data + block->cn->len); + } else { + /* advance or set to data */ + if (block->cn) + block->cn = (struct cn_msg *)(block->cn->data + + block->cn->len); + else + block->cn = block->first_cn; + + memcpy(block->cn, &block->request_cn, sizeof(*block->cn)); + block->cn->len = 0; + block->cn->ack = ack; + block->msg = (struct w1_netlink_msg *)block->cn->data; + } +} + +/* Append cmd to msg, include cmd->data as well. This is because + * any following data goes with the command and in the case of a read is + * the results. + */ +static void w1_netlink_queue_cmd(struct w1_cb_block *block, + struct w1_netlink_cmd *cmd) +{ + u32 space; + w1_reply_make_space(block, sizeof(struct cn_msg) + + sizeof(struct w1_netlink_msg) + sizeof(*cmd) + cmd->len); + + /* There's a status message sent after each command, so no point + * in trying to bundle this cmd after an existing one, because + * there won't be one. Allocate and copy over a new cn_msg. + */ + w1_netlink_setup_msg(block, block->request_cn.seq + 1); + memcpy(block->msg, block->cur_msg, sizeof(*block->msg)); + block->cn->len += sizeof(*block->msg); + block->msg->len = 0; + block->cmd = (struct w1_netlink_cmd *)(block->msg->data); + + space = sizeof(*cmd) + cmd->len; + if (block->cmd != cmd) + memcpy(block->cmd, cmd, space); + block->cn->len += space; + block->msg->len += space; +} + +/* Append req_msg and req_cmd, no other commands and no data from req_cmd are + * copied. + */ +static void w1_netlink_queue_status(struct w1_cb_block *block, + struct w1_netlink_msg *req_msg, struct w1_netlink_cmd *req_cmd, + int error) { - char buf[sizeof(struct cn_msg) + sizeof(struct w1_netlink_msg)]; - struct cn_msg *m = (struct cn_msg *)buf; - struct w1_netlink_msg *w = (struct w1_netlink_msg *)(m+1); + u16 space = sizeof(struct cn_msg) + sizeof(*req_msg) + sizeof(*req_cmd); + w1_reply_make_space(block, space); + w1_netlink_setup_msg(block, block->request_cn.ack); + + memcpy(block->msg, req_msg, sizeof(*req_msg)); + block->cn->len += sizeof(*req_msg); + block->msg->len = 0; + block->msg->status = (u8)-error; + if (req_cmd) { + struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)block->msg->data; + memcpy(cmd, req_cmd, sizeof(*cmd)); + block->cn->len += sizeof(*cmd); + block->msg->len += sizeof(*cmd); + cmd->len = 0; + } + w1_netlink_check_send(block); +} - memset(buf, 0, sizeof(buf)); +/** + * w1_netlink_send_error() - sends the error message now + * @cn: original cn_msg + * @msg: original w1_netlink_msg + * @portid: where to send it + * @error: error status + * + * Use when a block isn't available to queue the message to and cn, msg + * might not be contiguous. + */ +static void w1_netlink_send_error(struct cn_msg *cn, struct w1_netlink_msg *msg, + int portid, int error) +{ + struct { + struct cn_msg cn; + struct w1_netlink_msg msg; + } packet; + memcpy(&packet.cn, cn, sizeof(packet.cn)); + memcpy(&packet.msg, msg, sizeof(packet.msg)); + packet.cn.len = sizeof(packet.msg); + packet.msg.len = 0; + packet.msg.status = (u8)-error; + cn_netlink_send(&packet.cn, portid, 0, GFP_KERNEL); +} + +/** + * w1_netlink_send() - sends w1 netlink notifications + * @dev: w1_master the even is associated with or for + * @msg: w1_netlink_msg message to be sent + * + * This are notifications generated from the kernel. + */ +void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg) +{ + struct { + struct cn_msg cn; + struct w1_netlink_msg msg; + } packet; + memset(&packet, 0, sizeof(packet)); - m->id.idx = CN_W1_IDX; - m->id.val = CN_W1_VAL; + packet.cn.id.idx = CN_W1_IDX; + packet.cn.id.val = CN_W1_VAL; - m->seq = dev->seq++; - m->len = sizeof(struct w1_netlink_msg); + packet.cn.seq = dev->seq++; + packet.cn.len = sizeof(*msg); - memcpy(w, msg, sizeof(struct w1_netlink_msg)); + memcpy(&packet.msg, msg, sizeof(*msg)); + packet.msg.len = 0; - cn_netlink_send(m, dev->portid, 0, GFP_KERNEL); + cn_netlink_send(&packet.cn, 0, 0, GFP_KERNEL); } static void w1_send_slave(struct w1_master *dev, u64 rn) { - struct cn_msg *msg = dev->priv; - struct w1_netlink_msg *hdr = (struct w1_netlink_msg *)(msg + 1); - struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)(hdr + 1); - int avail; + struct w1_cb_block *block = dev->priv; + struct w1_netlink_cmd *cache_cmd = block->cmd; u64 *data; - avail = dev->priv_size - cmd->len; + w1_reply_make_space(block, sizeof(*data)); - if (avail < 8) { - msg->ack++; - cn_netlink_send(msg, dev->portid, 0, GFP_KERNEL); - - msg->len = sizeof(struct w1_netlink_msg) + - sizeof(struct w1_netlink_cmd); - hdr->len = sizeof(struct w1_netlink_cmd); - cmd->len = 0; + /* Add cmd back if the packet was sent */ + if (!block->cmd) { + cache_cmd->len = 0; + w1_netlink_queue_cmd(block, cache_cmd); } - data = (void *)(cmd + 1) + cmd->len; + data = (u64 *)(block->cmd->data + block->cmd->len); *data = rn; - cmd->len += 8; - hdr->len += 8; - msg->len += 8; + block->cn->len += sizeof(*data); + block->msg->len += sizeof(*data); + block->cmd->len += sizeof(*data); } static void w1_found_send_slave(struct w1_master *dev, u64 rn) @@ -85,40 +281,15 @@ static void w1_found_send_slave(struct w1_master *dev, u64 rn) } /* Get the current slave list, or search (with or without alarm) */ -static int w1_get_slaves(struct w1_master *dev, - struct cn_msg *req_msg, struct w1_netlink_msg *req_hdr, - struct w1_netlink_cmd *req_cmd) +static int w1_get_slaves(struct w1_master *dev, struct w1_netlink_cmd *req_cmd) { - struct cn_msg *msg; - struct w1_netlink_msg *hdr; - struct w1_netlink_cmd *cmd; struct w1_slave *sl; - msg = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (!msg) - return -ENOMEM; - - msg->id = req_msg->id; - msg->seq = req_msg->seq; - msg->ack = 0; - msg->len = sizeof(struct w1_netlink_msg) + - sizeof(struct w1_netlink_cmd); - - hdr = (struct w1_netlink_msg *)(msg + 1); - cmd = (struct w1_netlink_cmd *)(hdr + 1); - - hdr->type = W1_MASTER_CMD; - hdr->id = req_hdr->id; - hdr->len = sizeof(struct w1_netlink_cmd); - - cmd->cmd = req_cmd->cmd; - cmd->len = 0; - - dev->priv = msg; - dev->priv_size = PAGE_SIZE - msg->len - sizeof(struct cn_msg); + req_cmd->len = 0; + w1_netlink_queue_cmd(dev->priv, req_cmd); if (req_cmd->cmd == W1_CMD_LIST_SLAVES) { - __u64 rn; + u64 rn; mutex_lock(&dev->list_mutex); list_for_each_entry(sl, &dev->slist, w1_slave_entry) { memcpy(&rn, &sl->reg_num, sizeof(rn)); @@ -126,73 +297,26 @@ static int w1_get_slaves(struct w1_master *dev, } mutex_unlock(&dev->list_mutex); } else { - w1_search_process_cb(dev, cmd->cmd == W1_CMD_ALARM_SEARCH ? + w1_search_process_cb(dev, req_cmd->cmd == W1_CMD_ALARM_SEARCH ? W1_ALARM_SEARCH : W1_SEARCH, w1_found_send_slave); } - msg->ack = 0; - cn_netlink_send(msg, dev->portid, 0, GFP_KERNEL); - - dev->priv = NULL; - dev->priv_size = 0; - - kfree(msg); - return 0; } -static int w1_send_read_reply(struct cn_msg *msg, struct w1_netlink_msg *hdr, - struct w1_netlink_cmd *cmd, u32 portid) -{ - void *data; - struct w1_netlink_msg *h; - struct w1_netlink_cmd *c; - struct cn_msg *cm; - int err; - - data = kzalloc(sizeof(struct cn_msg) + - sizeof(struct w1_netlink_msg) + - sizeof(struct w1_netlink_cmd) + - cmd->len, GFP_KERNEL); - if (!data) - return -ENOMEM; - - cm = (struct cn_msg *)(data); - h = (struct w1_netlink_msg *)(cm + 1); - c = (struct w1_netlink_cmd *)(h + 1); - - memcpy(cm, msg, sizeof(struct cn_msg)); - memcpy(h, hdr, sizeof(struct w1_netlink_msg)); - memcpy(c, cmd, sizeof(struct w1_netlink_cmd)); - - cm->ack = msg->seq+1; - cm->len = sizeof(struct w1_netlink_msg) + - sizeof(struct w1_netlink_cmd) + cmd->len; - - h->len = sizeof(struct w1_netlink_cmd) + cmd->len; - - memcpy(c->data, cmd->data, c->len); - - err = cn_netlink_send(cm, portid, 0, GFP_KERNEL); - - kfree(data); - - return err; -} - -static int w1_process_command_io(struct w1_master *dev, struct cn_msg *msg, - struct w1_netlink_msg *hdr, struct w1_netlink_cmd *cmd) +static int w1_process_command_io(struct w1_master *dev, + struct w1_netlink_cmd *cmd) { int err = 0; switch (cmd->cmd) { case W1_CMD_TOUCH: w1_touch_block(dev, cmd->data, cmd->len); - w1_send_read_reply(msg, hdr, cmd, dev->portid); + w1_netlink_queue_cmd(dev->priv, cmd); break; case W1_CMD_READ: w1_read_block(dev, cmd->data, cmd->len); - w1_send_read_reply(msg, hdr, cmd, dev->portid); + w1_netlink_queue_cmd(dev->priv, cmd); break; case W1_CMD_WRITE: w1_write_block(dev, cmd->data, cmd->len); @@ -206,14 +330,13 @@ static int w1_process_command_io(struct w1_master *dev, struct cn_msg *msg, } static int w1_process_command_addremove(struct w1_master *dev, - struct cn_msg *msg, struct w1_netlink_msg *hdr, struct w1_netlink_cmd *cmd) { struct w1_slave *sl; int err = 0; struct w1_reg_num *id; - if (cmd->len != 8) + if (cmd->len != sizeof(*id)) return -EINVAL; id = (struct w1_reg_num *)cmd->data; @@ -241,7 +364,6 @@ static int w1_process_command_addremove(struct w1_master *dev, } static int w1_process_command_master(struct w1_master *dev, - struct cn_msg *req_msg, struct w1_netlink_msg *req_hdr, struct w1_netlink_cmd *req_cmd) { int err = -EINVAL; @@ -254,13 +376,13 @@ static int w1_process_command_master(struct w1_master *dev, case W1_CMD_ALARM_SEARCH: case W1_CMD_LIST_SLAVES: mutex_unlock(&dev->bus_mutex); - err = w1_get_slaves(dev, req_msg, req_hdr, req_cmd); + err = w1_get_slaves(dev, req_cmd); mutex_lock(&dev->bus_mutex); break; case W1_CMD_READ: case W1_CMD_WRITE: case W1_CMD_TOUCH: - err = w1_process_command_io(dev, req_msg, req_hdr, req_cmd); + err = w1_process_command_io(dev, req_cmd); break; case W1_CMD_RESET: err = w1_reset_bus(dev); @@ -269,8 +391,7 @@ static int w1_process_command_master(struct w1_master *dev, case W1_CMD_SLAVE_REMOVE: mutex_unlock(&dev->bus_mutex); mutex_lock(&dev->mutex); - err = w1_process_command_addremove(dev, req_msg, req_hdr, - req_cmd); + err = w1_process_command_addremove(dev, req_cmd); mutex_unlock(&dev->mutex); mutex_lock(&dev->bus_mutex); break; @@ -282,22 +403,21 @@ static int w1_process_command_master(struct w1_master *dev, return err; } -static int w1_process_command_slave(struct w1_slave *sl, struct cn_msg *msg, - struct w1_netlink_msg *hdr, struct w1_netlink_cmd *cmd) +static int w1_process_command_slave(struct w1_slave *sl, + struct w1_netlink_cmd *cmd) { dev_dbg(&sl->master->dev, "%s: %02x.%012llx.%02x: cmd=%02x, len=%u.\n", __func__, sl->reg_num.family, (unsigned long long)sl->reg_num.id, sl->reg_num.crc, cmd->cmd, cmd->len); - return w1_process_command_io(sl->master, msg, hdr, cmd); + return w1_process_command_io(sl->master, cmd); } -static int w1_process_command_root(struct cn_msg *msg, - struct w1_netlink_msg *mcmd, u32 portid) +static int w1_process_command_root(struct cn_msg *req_cn, u32 portid) { - struct w1_master *m; + struct w1_master *dev; struct cn_msg *cn; - struct w1_netlink_msg *w; + struct w1_netlink_msg *msg; u32 *id; cn = kmalloc(PAGE_SIZE, GFP_KERNEL); @@ -307,32 +427,30 @@ static int w1_process_command_root(struct cn_msg *msg, cn->id.idx = CN_W1_IDX; cn->id.val = CN_W1_VAL; - cn->seq = msg->seq; - cn->ack = 1; + cn->seq = req_cn->seq; + cn->ack = req_cn->seq + 1; cn->len = sizeof(struct w1_netlink_msg); - w = (struct w1_netlink_msg *)(cn + 1); + msg = (struct w1_netlink_msg *)cn->data; - w->type = W1_LIST_MASTERS; - w->status = 0; - w->len = 0; - id = (u32 *)(w + 1); + msg->type = W1_LIST_MASTERS; + msg->status = 0; + msg->len = 0; + id = (u32 *)msg->data; mutex_lock(&w1_mlock); - list_for_each_entry(m, &w1_masters, w1_master_entry) { + list_for_each_entry(dev, &w1_masters, w1_master_entry) { if (cn->len + sizeof(*id) > PAGE_SIZE - sizeof(struct cn_msg)) { cn_netlink_send(cn, portid, 0, GFP_KERNEL); - cn->ack++; cn->len = sizeof(struct w1_netlink_msg); - w->len = 0; - id = (u32 *)(w + 1); + msg->len = 0; + id = (u32 *)msg->data; } - *id = m->id; - w->len += sizeof(*id); + *id = dev->id; + msg->len += sizeof(*id); cn->len += sizeof(*id); id++; } - cn->ack = 0; cn_netlink_send(cn, portid, 0, GFP_KERNEL); mutex_unlock(&w1_mlock); @@ -340,100 +458,44 @@ static int w1_process_command_root(struct cn_msg *msg, return 0; } -static int w1_netlink_send_error(struct cn_msg *rcmsg, struct w1_netlink_msg *rmsg, - struct w1_netlink_cmd *rcmd, int portid, int error) -{ - struct cn_msg *cmsg; - struct w1_netlink_msg *msg; - struct w1_netlink_cmd *cmd; - - cmsg = kzalloc(sizeof(*msg) + sizeof(*cmd) + sizeof(*cmsg), GFP_KERNEL); - if (!cmsg) - return -ENOMEM; - - msg = (struct w1_netlink_msg *)(cmsg + 1); - cmd = (struct w1_netlink_cmd *)(msg + 1); - - memcpy(cmsg, rcmsg, sizeof(*cmsg)); - cmsg->len = sizeof(*msg); - - memcpy(msg, rmsg, sizeof(*msg)); - msg->len = 0; - msg->status = (short)-error; - - if (rcmd) { - memcpy(cmd, rcmd, sizeof(*cmd)); - cmd->len = 0; - msg->len += sizeof(*cmd); - cmsg->len += sizeof(*cmd); - } - - error = cn_netlink_send(cmsg, portid, 0, GFP_KERNEL); - kfree(cmsg); - - return error; -} - -/* Bundle together a reference count, the full message, and broken out - * commands to be executed on each w1 master kthread in one memory allocation. - */ -struct w1_cb_block { - atomic_t refcnt; - u32 portid; /* Sending process port ID */ - struct cn_msg msg; - /* cn_msg data */ - /* one or more variable length struct w1_cb_node */ -}; -struct w1_cb_node { - struct w1_async_cmd async; - /* pointers within w1_cb_block and msg data */ - struct w1_cb_block *block; - struct w1_netlink_msg *m; - struct w1_slave *sl; - struct w1_master *dev; -}; - static void w1_process_cb(struct w1_master *dev, struct w1_async_cmd *async_cmd) { struct w1_cb_node *node = container_of(async_cmd, struct w1_cb_node, async); - u16 mlen = node->m->len; - u8 *cmd_data = node->m->data; + u16 mlen = node->msg->len; + u16 len; int err = 0; struct w1_slave *sl = node->sl; - struct w1_netlink_cmd *cmd = NULL; + struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)node->msg->data; mutex_lock(&dev->bus_mutex); - dev->portid = node->block->portid; + dev->priv = node->block; if (sl && w1_reset_select_slave(sl)) err = -ENODEV; + node->block->cur_msg = node->msg; while (mlen && !err) { - cmd = (struct w1_netlink_cmd *)cmd_data; - if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) { err = -E2BIG; break; } if (sl) - err = w1_process_command_slave(sl, &node->block->msg, - node->m, cmd); + err = w1_process_command_slave(sl, cmd); else - err = w1_process_command_master(dev, &node->block->msg, - node->m, cmd); + err = w1_process_command_master(dev, cmd); + w1_netlink_check_send(node->block); - w1_netlink_send_error(&node->block->msg, node->m, cmd, - node->block->portid, err); + w1_netlink_queue_status(node->block, node->msg, cmd, err); err = 0; - cmd_data += cmd->len + sizeof(struct w1_netlink_cmd); - mlen -= cmd->len + sizeof(struct w1_netlink_cmd); + len = sizeof(*cmd) + cmd->len; + cmd = (struct w1_netlink_cmd *)((u8 *)cmd + len); + mlen -= len; } if (!cmd || err) - w1_netlink_send_error(&node->block->msg, node->m, cmd, - node->block->portid, err); + w1_netlink_queue_status(node->block, node->msg, cmd, err); /* ref taken in w1_search_slave or w1_search_master_id when building * the block @@ -442,99 +504,186 @@ static void w1_process_cb(struct w1_master *dev, struct w1_async_cmd *async_cmd) w1_unref_slave(sl); else atomic_dec(&dev->refcnt); - dev->portid = 0; + dev->priv = NULL; mutex_unlock(&dev->bus_mutex); mutex_lock(&dev->list_mutex); list_del(&async_cmd->async_entry); mutex_unlock(&dev->list_mutex); - if (atomic_sub_return(1, &node->block->refcnt) == 0) - kfree(node->block); + w1_unref_block(node->block); +} + +static void w1_list_count_cmds(struct w1_netlink_msg *msg, int *cmd_count, + u16 *slave_len) +{ + struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)msg->data; + u16 mlen = msg->len; + u16 len; + int slave_list = 0; + while (mlen) { + if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) + break; + + switch (cmd->cmd) { + case W1_CMD_SEARCH: + case W1_CMD_ALARM_SEARCH: + case W1_CMD_LIST_SLAVES: + ++slave_list; + } + ++*cmd_count; + len = sizeof(*cmd) + cmd->len; + cmd = (struct w1_netlink_cmd *)((u8 *)cmd + len); + mlen -= len; + } + + if (slave_list) { + struct w1_master *dev = w1_search_master_id(msg->id.mst.id); + if (dev) { + /* Bytes, and likely an overstimate, and if it isn't + * the results can still be split between packets. + */ + *slave_len += sizeof(struct w1_reg_num) * slave_list * + (dev->slave_count + dev->max_slave_count); + /* search incremented it */ + atomic_dec(&dev->refcnt); + } + } } -static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) +static void w1_cn_callback(struct cn_msg *cn, struct netlink_skb_parms *nsp) { - struct w1_netlink_msg *m = (struct w1_netlink_msg *)(msg + 1); + struct w1_netlink_msg *msg = (struct w1_netlink_msg *)(cn + 1); struct w1_slave *sl; struct w1_master *dev; u16 msg_len; + u16 slave_len = 0; int err = 0; struct w1_cb_block *block = NULL; struct w1_cb_node *node = NULL; int node_count = 0; + int cmd_count = 0; + + /* If any unknown flag is set let the application know, that way + * applications can detect the absence of features in kernels that + * don't know about them. http://lwn.net/Articles/587527/ + */ + if (cn->flags & ~(W1_CN_BUNDLE)) { + w1_netlink_send_error(cn, msg, nsp->portid, -EINVAL); + return; + } /* Count the number of master or slave commands there are to allocate * space for one cb_node each. */ - msg_len = msg->len; + msg_len = cn->len; while (msg_len && !err) { - if (m->len + sizeof(struct w1_netlink_msg) > msg_len) { + if (msg->len + sizeof(struct w1_netlink_msg) > msg_len) { err = -E2BIG; break; } - if (m->type == W1_MASTER_CMD || m->type == W1_SLAVE_CMD) + /* count messages for nodes and allocate any additional space + * required for slave lists + */ + if (msg->type == W1_MASTER_CMD || msg->type == W1_SLAVE_CMD) { ++node_count; + w1_list_count_cmds(msg, &cmd_count, &slave_len); + } - msg_len -= sizeof(struct w1_netlink_msg) + m->len; - m = (struct w1_netlink_msg *)(((u8 *)m) + - sizeof(struct w1_netlink_msg) + m->len); + msg_len -= sizeof(struct w1_netlink_msg) + msg->len; + msg = (struct w1_netlink_msg *)(((u8 *)msg) + + sizeof(struct w1_netlink_msg) + msg->len); } - m = (struct w1_netlink_msg *)(msg + 1); + msg = (struct w1_netlink_msg *)(cn + 1); if (node_count) { - /* msg->len doesn't include itself */ - long size = sizeof(struct w1_cb_block) + msg->len + - node_count*sizeof(struct w1_cb_node); - block = kmalloc(size, GFP_KERNEL); + int size; + u16 reply_size = sizeof(*cn) + cn->len + slave_len; + if (cn->flags & W1_CN_BUNDLE) { + /* bundling duplicats some of the messages */ + reply_size += 2 * cmd_count * (sizeof(struct cn_msg) + + sizeof(struct w1_netlink_msg) + + sizeof(struct w1_netlink_cmd)); + } + reply_size = MIN(CONNECTOR_MAX_MSG_SIZE, reply_size); + + /* allocate space for the block, a copy of the original message, + * one node per cmd to point into the original message, + * space for replies which is the original message size plus + * space for any list slave data and status messages + * cn->len doesn't include itself which is part of the block + * */ + size = /* block + original message */ + sizeof(struct w1_cb_block) + sizeof(*cn) + cn->len + + /* space for nodes */ + node_count * sizeof(struct w1_cb_node) + + /* replies */ + sizeof(struct cn_msg) + reply_size; + block = kzalloc(size, GFP_KERNEL); if (!block) { - w1_netlink_send_error(msg, m, NULL, nsp->portid, - -ENOMEM); + /* if the system is already out of memory, + * (A) will this work, and (B) would it be better + * to not try? + */ + w1_netlink_send_error(cn, msg, nsp->portid, -ENOMEM); return; } atomic_set(&block->refcnt, 1); block->portid = nsp->portid; - memcpy(&block->msg, msg, sizeof(*msg) + msg->len); - node = (struct w1_cb_node *)((u8 *)block->msg.data + msg->len); + memcpy(&block->request_cn, cn, sizeof(*cn) + cn->len); + node = (struct w1_cb_node *)(block->request_cn.data + cn->len); + + /* Sneeky, when not bundling, reply_size is the allocated space + * required for the reply, cn_msg isn't part of maxlen so + * it should be reply_size - sizeof(struct cn_msg), however + * when checking if there is enough space, w1_reply_make_space + * is called with the full message size including cn_msg, + * because it isn't known at that time if an additional cn_msg + * will need to be allocated. So an extra cn_msg is added + * above in "size". + */ + block->maxlen = reply_size; + block->first_cn = (struct cn_msg *)(node + node_count); + memset(block->first_cn, 0, sizeof(*block->first_cn)); } - msg_len = msg->len; + msg_len = cn->len; while (msg_len && !err) { dev = NULL; sl = NULL; - if (m->len + sizeof(struct w1_netlink_msg) > msg_len) { + if (msg->len + sizeof(struct w1_netlink_msg) > msg_len) { err = -E2BIG; break; } /* execute on this thread, no need to process later */ - if (m->type == W1_LIST_MASTERS) { - err = w1_process_command_root(msg, m, nsp->portid); + if (msg->type == W1_LIST_MASTERS) { + err = w1_process_command_root(cn, nsp->portid); goto out_cont; } /* All following message types require additional data, * check here before references are taken. */ - if (!m->len) { + if (!msg->len) { err = -EPROTO; goto out_cont; } - /* both search calls take reference counts */ - if (m->type == W1_MASTER_CMD) { - dev = w1_search_master_id(m->id.mst.id); - } else if (m->type == W1_SLAVE_CMD) { - sl = w1_search_slave((struct w1_reg_num *)m->id.id); + /* both search calls take references */ + if (msg->type == W1_MASTER_CMD) { + dev = w1_search_master_id(msg->id.mst.id); + } else if (msg->type == W1_SLAVE_CMD) { + sl = w1_search_slave((struct w1_reg_num *)msg->id.id); if (sl) dev = sl->master; } else { printk(KERN_NOTICE - "%s: msg: %x.%x, wrong type: %u, len: %u.\n", - __func__, msg->id.idx, msg->id.val, - m->type, m->len); + "%s: cn: %x.%x, wrong type: %u, len: %u.\n", + __func__, cn->id.idx, cn->id.val, + msg->type, msg->len); err = -EPROTO; goto out_cont; } @@ -549,8 +698,8 @@ static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) atomic_inc(&block->refcnt); node->async.cb = w1_process_cb; node->block = block; - node->m = (struct w1_netlink_msg *)((u8 *)&block->msg + - (size_t)((u8 *)m - (u8 *)msg)); + node->msg = (struct w1_netlink_msg *)((u8 *)&block->request_cn + + (size_t)((u8 *)msg - (u8 *)cn)); node->sl = sl; node->dev = dev; @@ -561,11 +710,15 @@ static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) ++node; out_cont: + /* Can't queue because that modifies block and another + * thread could be processing the messages by now and + * there isn't a lock, send directly. + */ if (err) - w1_netlink_send_error(msg, m, NULL, nsp->portid, err); - msg_len -= sizeof(struct w1_netlink_msg) + m->len; - m = (struct w1_netlink_msg *)(((u8 *)m) + - sizeof(struct w1_netlink_msg) + m->len); + w1_netlink_send_error(cn, msg, nsp->portid, err); + msg_len -= sizeof(struct w1_netlink_msg) + msg->len; + msg = (struct w1_netlink_msg *)(((u8 *)msg) + + sizeof(struct w1_netlink_msg) + msg->len); /* * Let's allow requests for nonexisting devices. @@ -573,8 +726,8 @@ out_cont: if (err == -ENODEV) err = 0; } - if (block && atomic_sub_return(1, &block->refcnt) == 0) - kfree(block); + if (block) + w1_unref_block(block); } int w1_init_netlink(void) @@ -591,7 +744,7 @@ void w1_fini_netlink(void) cn_del_callback(&w1_id); } #else -void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg) +void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *cn) { } diff --git a/drivers/w1/w1_netlink.h b/drivers/w1/w1_netlink.h index 1e9504e..c99a9ce 100644 --- a/drivers/w1/w1_netlink.h +++ b/drivers/w1/w1_netlink.h @@ -28,6 +28,17 @@ #include "w1.h" /** + * enum w1_cn_msg_flags - bitfield flags for struct cn_msg.flags + * + * @W1_CN_BUNDLE: Request bundling replies into fewer messagse. Be prepared + * to handle multiple struct cn_msg, struct w1_netlink_msg, and + * struct w1_netlink_cmd in one packet. + */ +enum w1_cn_msg_flags { + W1_CN_BUNDLE = 1, +}; + +/** * enum w1_netlink_message_types - message type * * @W1_SLAVE_ADD: notification that a slave device was added @@ -49,6 +60,19 @@ enum w1_netlink_message_types { W1_LIST_MASTERS, }; +/** + * struct w1_netlink_msg - holds w1 message type, id, and result + * + * @type: one of enum w1_netlink_message_types + * @status: kernel feedback for success 0 or errno failure value + * @len: length of data following w1_netlink_msg + * @id: union holding master bus id (msg.id) and slave device id (id[8]). + * @data: start address of any following data + * + * The base message structure for w1 messages over netlink. + * The netlink connector data sequence is, struct nlmsghdr, struct cn_msg, + * then one or more struct w1_netlink_msg (each with optional data). + */ struct w1_netlink_msg { __u8 type; @@ -66,6 +90,7 @@ struct w1_netlink_msg /** * enum w1_commands - commands available for master or slave operations + * * @W1_CMD_READ: read len bytes * @W1_CMD_WRITE: write len bytes * @W1_CMD_SEARCH: initiate a standard search, returns only the slave @@ -93,6 +118,17 @@ enum w1_commands { W1_CMD_MAX }; +/** + * struct w1_netlink_cmd - holds the command and data + * + * @cmd: one of enum w1_commands + * @res: reserved + * @len: length of data following w1_netlink_cmd + * @data: start address of any following data + * + * One or more struct w1_netlink_cmd is placed starting at w1_netlink_msg.data + * each with optional data. + */ struct w1_netlink_cmd { __u8 cmd; diff --git a/include/linux/connector.h b/include/linux/connector.h index be9c4747..f8fe863 100644 --- a/include/linux/connector.h +++ b/include/linux/connector.h @@ -71,6 +71,7 @@ struct cn_dev { int cn_add_callback(struct cb_id *id, const char *name, void (*callback)(struct cn_msg *, struct netlink_skb_parms *)); void cn_del_callback(struct cb_id *); +int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 group, gfp_t gfp_mask); int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 group, gfp_t gfp_mask); int cn_queue_add_callback(struct cn_queue_dev *dev, const char *name, diff --git a/include/linux/extcon.h b/include/linux/extcon.h index f488145..36f49c4 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -185,9 +185,22 @@ struct extcon_specific_cable_nb { */ extern int extcon_dev_register(struct extcon_dev *edev); extern void extcon_dev_unregister(struct extcon_dev *edev); +extern int devm_extcon_dev_register(struct device *dev, + struct extcon_dev *edev); +extern void devm_extcon_dev_unregister(struct device *dev, + struct extcon_dev *edev); extern struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name); /* + * Following APIs control the memory of extcon device. + */ +extern struct extcon_dev *extcon_dev_allocate(const char **cables); +extern void extcon_dev_free(struct extcon_dev *edev); +extern struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, + const char **cables); +extern void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev); + +/* * get/set/update_state access the 32b encoded state value, which represents * states of all possible cables of the multistate port. For example, if one * calls extcon_set_state(edev, 0x7), it may mean that all the three cables @@ -254,6 +267,30 @@ static inline int extcon_dev_register(struct extcon_dev *edev) static inline void extcon_dev_unregister(struct extcon_dev *edev) { } +static inline int devm_extcon_dev_register(struct device *dev, + struct extcon_dev *edev) +{ + return -EINVAL; +} + +static inline void devm_extcon_dev_unregister(struct device *dev, + struct extcon_dev *edev) { } + +static inline struct extcon_dev *extcon_dev_allocate(const char **cables) +{ + return ERR_PTR(-ENOSYS); +} + +static inline void extcon_dev_free(struct extcon_dev *edev) { } + +static inline struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, + const char **cables) +{ + return ERR_PTR(-ENOSYS); +} + +static inline void devm_extcon_dev_free(struct extcon_dev *edev) { } + static inline u32 extcon_get_state(struct extcon_dev *edev) { return 0; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 2d7b4f1..08cfaff 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -696,6 +696,8 @@ struct vmbus_channel { * preserve the earlier behavior. */ u32 target_vp; + /* The corresponding CPUID in the guest */ + u32 target_cpu; /* * Support for sub-channels. For high performance devices, * it will be useful to have multiple sub-channels to support @@ -732,6 +734,11 @@ struct vmbus_channel { * Support per-channel state for use by vmbus drivers. */ void *per_channel_state; + /* + * To support per-cpu lookup mapping of relid to channel, + * link up channels based on their CPU affinity. + */ + struct list_head percpu_list; }; static inline void set_channel_read_state(struct vmbus_channel *c, bool state) diff --git a/include/linux/mcb.h b/include/linux/mcb.h index 2db284d..ed06e15 100644 --- a/include/linux/mcb.h +++ b/include/linux/mcb.h @@ -16,6 +16,7 @@ #include <linux/irqreturn.h> struct mcb_driver; +struct mcb_device; /** * struct mcb_bus - MEN Chameleon Bus @@ -23,11 +24,14 @@ struct mcb_driver; * @dev: pointer to carrier device * @children: the child busses * @bus_nr: mcb bus number + * @get_irq: callback to get IRQ number */ struct mcb_bus { struct list_head children; struct device dev; + struct device *carrier; int bus_nr; + int (*get_irq)(struct mcb_device *dev); }; #define to_mcb_bus(b) container_of((b), struct mcb_bus, dev) @@ -105,7 +109,7 @@ extern void mcb_unregister_driver(struct mcb_driver *driver); module_driver(__mcb_driver, mcb_register_driver, mcb_unregister_driver); extern void mcb_bus_add_devices(const struct mcb_bus *bus); extern int mcb_device_register(struct mcb_bus *bus, struct mcb_device *dev); -extern struct mcb_bus *mcb_alloc_bus(void); +extern struct mcb_bus *mcb_alloc_bus(struct device *carrier); extern struct mcb_bus *mcb_bus_get(struct mcb_bus *bus); extern void mcb_bus_put(struct mcb_bus *bus); extern struct mcb_device *mcb_alloc_dev(struct mcb_bus *bus); diff --git a/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h index c9b332f..4992536 100644 --- a/include/linux/mfd/max14577-private.h +++ b/include/linux/mfd/max14577-private.h @@ -1,7 +1,7 @@ /* - * max14577-private.h - Common API for the Maxim 14577 internal sub chip + * max14577-private.h - Common API for the Maxim 14577/77836 internal sub chip * - * Copyright (C) 2013 Samsung Electrnoics + * Copyright (C) 2014 Samsung Electrnoics * Chanwoo Choi <cw00.choi@samsung.com> * Krzysztof Kozlowski <k.kozlowski@samsung.com> * @@ -22,9 +22,19 @@ #include <linux/i2c.h> #include <linux/regmap.h> -#define MAX14577_REG_INVALID (0xff) +#define I2C_ADDR_PMIC (0x46 >> 1) +#define I2C_ADDR_MUIC (0x4A >> 1) +#define I2C_ADDR_FG (0x6C >> 1) -/* Slave addr = 0x4A: Interrupt */ +enum maxim_device_type { + MAXIM_DEVICE_TYPE_UNKNOWN = 0, + MAXIM_DEVICE_TYPE_MAX14577, + MAXIM_DEVICE_TYPE_MAX77836, + + MAXIM_DEVICE_TYPE_NUM, +}; + +/* Slave addr = 0x4A: MUIC and Charger */ enum max14577_reg { MAX14577_REG_DEVICEID = 0x00, MAX14577_REG_INT1 = 0x01, @@ -74,20 +84,22 @@ enum max14577_muic_charger_type { }; /* MAX14577 interrupts */ -#define INT1_ADC_MASK (0x1 << 0) -#define INT1_ADCLOW_MASK (0x1 << 1) -#define INT1_ADCERR_MASK (0x1 << 2) - -#define INT2_CHGTYP_MASK (0x1 << 0) -#define INT2_CHGDETRUN_MASK (0x1 << 1) -#define INT2_DCDTMR_MASK (0x1 << 2) -#define INT2_DBCHG_MASK (0x1 << 3) -#define INT2_VBVOLT_MASK (0x1 << 4) - -#define INT3_EOC_MASK (0x1 << 0) -#define INT3_CGMBC_MASK (0x1 << 1) -#define INT3_OVP_MASK (0x1 << 2) -#define INT3_MBCCHGERR_MASK (0x1 << 3) +#define MAX14577_INT1_ADC_MASK BIT(0) +#define MAX14577_INT1_ADCLOW_MASK BIT(1) +#define MAX14577_INT1_ADCERR_MASK BIT(2) +#define MAX77836_INT1_ADC1K_MASK BIT(3) + +#define MAX14577_INT2_CHGTYP_MASK BIT(0) +#define MAX14577_INT2_CHGDETRUN_MASK BIT(1) +#define MAX14577_INT2_DCDTMR_MASK BIT(2) +#define MAX14577_INT2_DBCHG_MASK BIT(3) +#define MAX14577_INT2_VBVOLT_MASK BIT(4) +#define MAX77836_INT2_VIDRM_MASK BIT(5) + +#define MAX14577_INT3_EOC_MASK BIT(0) +#define MAX14577_INT3_CGMBC_MASK BIT(1) +#define MAX14577_INT3_OVP_MASK BIT(2) +#define MAX14577_INT3_MBCCHGERR_MASK BIT(3) /* MAX14577 DEVICE ID register */ #define DEVID_VENDORID_SHIFT 0 @@ -99,9 +111,11 @@ enum max14577_muic_charger_type { #define STATUS1_ADC_SHIFT 0 #define STATUS1_ADCLOW_SHIFT 5 #define STATUS1_ADCERR_SHIFT 6 +#define MAX77836_STATUS1_ADC1K_SHIFT 7 #define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) -#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) -#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) +#define STATUS1_ADCLOW_MASK BIT(STATUS1_ADCLOW_SHIFT) +#define STATUS1_ADCERR_MASK BIT(STATUS1_ADCERR_SHIFT) +#define MAX77836_STATUS1_ADC1K_MASK BIT(MAX77836_STATUS1_ADC1K_SHIFT) /* MAX14577 STATUS2 register */ #define STATUS2_CHGTYP_SHIFT 0 @@ -109,11 +123,13 @@ enum max14577_muic_charger_type { #define STATUS2_DCDTMR_SHIFT 4 #define STATUS2_DBCHG_SHIFT 5 #define STATUS2_VBVOLT_SHIFT 6 +#define MAX77836_STATUS2_VIDRM_SHIFT 7 #define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) -#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) -#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) -#define STATUS2_DBCHG_MASK (0x1 << STATUS2_DBCHG_SHIFT) -#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) +#define STATUS2_CHGDETRUN_MASK BIT(STATUS2_CHGDETRUN_SHIFT) +#define STATUS2_DCDTMR_MASK BIT(STATUS2_DCDTMR_SHIFT) +#define STATUS2_DBCHG_MASK BIT(STATUS2_DBCHG_SHIFT) +#define STATUS2_VBVOLT_MASK BIT(STATUS2_VBVOLT_SHIFT) +#define MAX77836_STATUS2_VIDRM_MASK BIT(MAX77836_STATUS2_VIDRM_SHIFT) /* MAX14577 CONTROL1 register */ #define COMN1SW_SHIFT 0 @@ -122,8 +138,8 @@ enum max14577_muic_charger_type { #define IDBEN_SHIFT 7 #define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) #define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) -#define MICEN_MASK (0x1 << MICEN_SHIFT) -#define IDBEN_MASK (0x1 << IDBEN_SHIFT) +#define MICEN_MASK BIT(MICEN_SHIFT) +#define IDBEN_MASK BIT(IDBEN_SHIFT) #define CLEAR_IDBEN_MICEN_MASK (COMN1SW_MASK | COMP2SW_MASK) #define CTRL1_SW_USB ((1 << COMP2SW_SHIFT) \ | (1 << COMN1SW_SHIFT)) @@ -143,14 +159,14 @@ enum max14577_muic_charger_type { #define CTRL2_ACCDET_SHIFT (5) #define CTRL2_USBCPINT_SHIFT (6) #define CTRL2_RCPS_SHIFT (7) -#define CTRL2_LOWPWR_MASK (0x1 << CTRL2_LOWPWR_SHIFT) -#define CTRL2_ADCEN_MASK (0x1 << CTRL2_ADCEN_SHIFT) -#define CTRL2_CPEN_MASK (0x1 << CTRL2_CPEN_SHIFT) -#define CTRL2_SFOUTASRT_MASK (0x1 << CTRL2_SFOUTASRT_SHIFT) -#define CTRL2_SFOUTORD_MASK (0x1 << CTRL2_SFOUTORD_SHIFT) -#define CTRL2_ACCDET_MASK (0x1 << CTRL2_ACCDET_SHIFT) -#define CTRL2_USBCPINT_MASK (0x1 << CTRL2_USBCPINT_SHIFT) -#define CTRL2_RCPS_MASK (0x1 << CTR2_RCPS_SHIFT) +#define CTRL2_LOWPWR_MASK BIT(CTRL2_LOWPWR_SHIFT) +#define CTRL2_ADCEN_MASK BIT(CTRL2_ADCEN_SHIFT) +#define CTRL2_CPEN_MASK BIT(CTRL2_CPEN_SHIFT) +#define CTRL2_SFOUTASRT_MASK BIT(CTRL2_SFOUTASRT_SHIFT) +#define CTRL2_SFOUTORD_MASK BIT(CTRL2_SFOUTORD_SHIFT) +#define CTRL2_ACCDET_MASK BIT(CTRL2_ACCDET_SHIFT) +#define CTRL2_USBCPINT_MASK BIT(CTRL2_USBCPINT_SHIFT) +#define CTRL2_RCPS_MASK BIT(CTRL2_RCPS_SHIFT) #define CTRL2_CPEN1_LOWPWR0 ((1 << CTRL2_CPEN_SHIFT) | \ (0 << CTRL2_LOWPWR_SHIFT)) @@ -198,14 +214,14 @@ enum max14577_charger_reg { #define CDETCTRL1_DBEXIT_SHIFT 5 #define CDETCTRL1_DBIDLE_SHIFT 6 #define CDETCTRL1_CDPDET_SHIFT 7 -#define CDETCTRL1_CHGDETEN_MASK (0x1 << CDETCTRL1_CHGDETEN_SHIFT) -#define CDETCTRL1_CHGTYPMAN_MASK (0x1 << CDETCTRL1_CHGTYPMAN_SHIFT) -#define CDETCTRL1_DCDEN_MASK (0x1 << CDETCTRL1_DCDEN_SHIFT) -#define CDETCTRL1_DCD2SCT_MASK (0x1 << CDETCTRL1_DCD2SCT_SHIFT) -#define CDETCTRL1_DCHKTM_MASK (0x1 << CDETCTRL1_DCHKTM_SHIFT) -#define CDETCTRL1_DBEXIT_MASK (0x1 << CDETCTRL1_DBEXIT_SHIFT) -#define CDETCTRL1_DBIDLE_MASK (0x1 << CDETCTRL1_DBIDLE_SHIFT) -#define CDETCTRL1_CDPDET_MASK (0x1 << CDETCTRL1_CDPDET_SHIFT) +#define CDETCTRL1_CHGDETEN_MASK BIT(CDETCTRL1_CHGDETEN_SHIFT) +#define CDETCTRL1_CHGTYPMAN_MASK BIT(CDETCTRL1_CHGTYPMAN_SHIFT) +#define CDETCTRL1_DCDEN_MASK BIT(CDETCTRL1_DCDEN_SHIFT) +#define CDETCTRL1_DCD2SCT_MASK BIT(CDETCTRL1_DCD2SCT_SHIFT) +#define CDETCTRL1_DCHKTM_MASK BIT(CDETCTRL1_DCHKTM_SHIFT) +#define CDETCTRL1_DBEXIT_MASK BIT(CDETCTRL1_DBEXIT_SHIFT) +#define CDETCTRL1_DBIDLE_MASK BIT(CDETCTRL1_DBIDLE_SHIFT) +#define CDETCTRL1_CDPDET_MASK BIT(CDETCTRL1_CDPDET_SHIFT) /* MAX14577 CHGCTRL1 register */ #define CHGCTRL1_TCHW_SHIFT 4 @@ -213,9 +229,9 @@ enum max14577_charger_reg { /* MAX14577 CHGCTRL2 register */ #define CHGCTRL2_MBCHOSTEN_SHIFT 6 -#define CHGCTRL2_MBCHOSTEN_MASK (0x1 << CHGCTRL2_MBCHOSTEN_SHIFT) +#define CHGCTRL2_MBCHOSTEN_MASK BIT(CHGCTRL2_MBCHOSTEN_SHIFT) #define CHGCTRL2_VCHGR_RC_SHIFT 7 -#define CHGCTRL2_VCHGR_RC_MASK (0x1 << CHGCTRL2_VCHGR_RC_SHIFT) +#define CHGCTRL2_VCHGR_RC_MASK BIT(CHGCTRL2_VCHGR_RC_SHIFT) /* MAX14577 CHGCTRL3 register */ #define CHGCTRL3_MBCCVWRC_SHIFT 0 @@ -225,7 +241,7 @@ enum max14577_charger_reg { #define CHGCTRL4_MBCICHWRCH_SHIFT 0 #define CHGCTRL4_MBCICHWRCH_MASK (0xf << CHGCTRL4_MBCICHWRCH_SHIFT) #define CHGCTRL4_MBCICHWRCL_SHIFT 4 -#define CHGCTRL4_MBCICHWRCL_MASK (0x1 << CHGCTRL4_MBCICHWRCL_SHIFT) +#define CHGCTRL4_MBCICHWRCL_MASK BIT(CHGCTRL4_MBCICHWRCL_SHIFT) /* MAX14577 CHGCTRL5 register */ #define CHGCTRL5_EOCS_SHIFT 0 @@ -233,7 +249,7 @@ enum max14577_charger_reg { /* MAX14577 CHGCTRL6 register */ #define CHGCTRL6_AUTOSTOP_SHIFT 5 -#define CHGCTRL6_AUTOSTOP_MASK (0x1 << CHGCTRL6_AUTOSTOP_SHIFT) +#define CHGCTRL6_AUTOSTOP_MASK BIT(CHGCTRL6_AUTOSTOP_SHIFT) /* MAX14577 CHGCTRL7 register */ #define CHGCTRL7_OTPCGHCVS_SHIFT 0 @@ -245,14 +261,111 @@ enum max14577_charger_reg { #define MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP 50000 #define MAX14577_REGULATOR_CURRENT_LIMIT_MAX 950000 +/* MAX77836 regulator current limits (as in CHGCTRL4 register), uA */ +#define MAX77836_REGULATOR_CURRENT_LIMIT_MIN 45000 +#define MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START 100000 +#define MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP 25000 +#define MAX77836_REGULATOR_CURRENT_LIMIT_MAX 475000 + /* MAX14577 regulator SFOUT LDO voltage, fixed, uV */ #define MAX14577_REGULATOR_SAFEOUT_VOLTAGE 4900000 +/* MAX77836 regulator LDOx voltage, uV */ +#define MAX77836_REGULATOR_LDO_VOLTAGE_MIN 800000 +#define MAX77836_REGULATOR_LDO_VOLTAGE_MAX 3950000 +#define MAX77836_REGULATOR_LDO_VOLTAGE_STEP 50000 +#define MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM 64 + +/* Slave addr = 0x46: PMIC */ +enum max77836_pmic_reg { + MAX77836_PMIC_REG_PMIC_ID = 0x20, + MAX77836_PMIC_REG_PMIC_REV = 0x21, + MAX77836_PMIC_REG_INTSRC = 0x22, + MAX77836_PMIC_REG_INTSRC_MASK = 0x23, + MAX77836_PMIC_REG_TOPSYS_INT = 0x24, + MAX77836_PMIC_REG_TOPSYS_INT_MASK = 0x26, + MAX77836_PMIC_REG_TOPSYS_STAT = 0x28, + MAX77836_PMIC_REG_MRSTB_CNTL = 0x2A, + MAX77836_PMIC_REG_LSCNFG = 0x2B, + + MAX77836_LDO_REG_CNFG1_LDO1 = 0x51, + MAX77836_LDO_REG_CNFG2_LDO1 = 0x52, + MAX77836_LDO_REG_CNFG1_LDO2 = 0x53, + MAX77836_LDO_REG_CNFG2_LDO2 = 0x54, + MAX77836_LDO_REG_CNFG_LDO_BIAS = 0x55, + + MAX77836_COMP_REG_COMP1 = 0x60, + + MAX77836_PMIC_REG_END, +}; + +#define MAX77836_INTSRC_MASK_TOP_INT_SHIFT 1 +#define MAX77836_INTSRC_MASK_MUIC_CHG_INT_SHIFT 3 +#define MAX77836_INTSRC_MASK_TOP_INT_MASK BIT(MAX77836_INTSRC_MASK_TOP_INT_SHIFT) +#define MAX77836_INTSRC_MASK_MUIC_CHG_INT_MASK BIT(MAX77836_INTSRC_MASK_MUIC_CHG_INT_SHIFT) + +/* MAX77836 PMIC interrupts */ +#define MAX77836_TOPSYS_INT_T120C_SHIFT 0 +#define MAX77836_TOPSYS_INT_T140C_SHIFT 1 +#define MAX77836_TOPSYS_INT_T120C_MASK BIT(MAX77836_TOPSYS_INT_T120C_SHIFT) +#define MAX77836_TOPSYS_INT_T140C_MASK BIT(MAX77836_TOPSYS_INT_T140C_SHIFT) + +/* LDO1/LDO2 CONFIG1 register */ +#define MAX77836_CNFG1_LDO_PWRMD_SHIFT 6 +#define MAX77836_CNFG1_LDO_TV_SHIFT 0 +#define MAX77836_CNFG1_LDO_PWRMD_MASK (0x3 << MAX77836_CNFG1_LDO_PWRMD_SHIFT) +#define MAX77836_CNFG1_LDO_TV_MASK (0x3f << MAX77836_CNFG1_LDO_TV_SHIFT) + +/* LDO1/LDO2 CONFIG2 register */ +#define MAX77836_CNFG2_LDO_OVCLMPEN_SHIFT 7 +#define MAX77836_CNFG2_LDO_ALPMEN_SHIFT 6 +#define MAX77836_CNFG2_LDO_COMP_SHIFT 4 +#define MAX77836_CNFG2_LDO_POK_SHIFT 3 +#define MAX77836_CNFG2_LDO_ADE_SHIFT 1 +#define MAX77836_CNFG2_LDO_SS_SHIFT 0 +#define MAX77836_CNFG2_LDO_OVCLMPEN_MASK BIT(MAX77836_CNFG2_LDO_OVCLMPEN_SHIFT) +#define MAX77836_CNFG2_LDO_ALPMEN_MASK BIT(MAX77836_CNFG2_LDO_ALPMEN_SHIFT) +#define MAX77836_CNFG2_LDO_COMP_MASK (0x3 << MAX77836_CNFG2_LDO_COMP_SHIFT) +#define MAX77836_CNFG2_LDO_POK_MASK BIT(MAX77836_CNFG2_LDO_POK_SHIFT) +#define MAX77836_CNFG2_LDO_ADE_MASK BIT(MAX77836_CNFG2_LDO_ADE_SHIFT) +#define MAX77836_CNFG2_LDO_SS_MASK BIT(MAX77836_CNFG2_LDO_SS_SHIFT) + +/* Slave addr = 0x6C: Fuel-Gauge/Battery */ +enum max77836_fg_reg { + MAX77836_FG_REG_VCELL_MSB = 0x02, + MAX77836_FG_REG_VCELL_LSB = 0x03, + MAX77836_FG_REG_SOC_MSB = 0x04, + MAX77836_FG_REG_SOC_LSB = 0x05, + MAX77836_FG_REG_MODE_H = 0x06, + MAX77836_FG_REG_MODE_L = 0x07, + MAX77836_FG_REG_VERSION_MSB = 0x08, + MAX77836_FG_REG_VERSION_LSB = 0x09, + MAX77836_FG_REG_HIBRT_H = 0x0A, + MAX77836_FG_REG_HIBRT_L = 0x0B, + MAX77836_FG_REG_CONFIG_H = 0x0C, + MAX77836_FG_REG_CONFIG_L = 0x0D, + MAX77836_FG_REG_VALRT_MIN = 0x14, + MAX77836_FG_REG_VALRT_MAX = 0x15, + MAX77836_FG_REG_CRATE_MSB = 0x16, + MAX77836_FG_REG_CRATE_LSB = 0x17, + MAX77836_FG_REG_VRESET = 0x18, + MAX77836_FG_REG_FGID = 0x19, + MAX77836_FG_REG_STATUS_H = 0x1A, + MAX77836_FG_REG_STATUS_L = 0x1B, + /* + * TODO: TABLE registers + * TODO: CMD register + */ + + MAX77836_FG_REG_END, +}; + enum max14577_irq { /* INT1 */ MAX14577_IRQ_INT1_ADC, MAX14577_IRQ_INT1_ADCLOW, MAX14577_IRQ_INT1_ADCERR, + MAX77836_IRQ_INT1_ADC1K, /* INT2 */ MAX14577_IRQ_INT2_CHGTYP, @@ -260,6 +373,7 @@ enum max14577_irq { MAX14577_IRQ_INT2_DCDTMR, MAX14577_IRQ_INT2_DBCHG, MAX14577_IRQ_INT2_VBVOLT, + MAX77836_IRQ_INT2_VIDRM, /* INT3 */ MAX14577_IRQ_INT3_EOC, @@ -267,21 +381,25 @@ enum max14577_irq { MAX14577_IRQ_INT3_OVP, MAX14577_IRQ_INT3_MBCCHGERR, + /* TOPSYS_INT, only MAX77836 */ + MAX77836_IRQ_TOPSYS_T140C, + MAX77836_IRQ_TOPSYS_T120C, + MAX14577_IRQ_NUM, }; struct max14577 { struct device *dev; struct i2c_client *i2c; /* Slave addr = 0x4A */ + struct i2c_client *i2c_pmic; /* Slave addr = 0x46 */ + enum maxim_device_type dev_type; - struct regmap *regmap; + struct regmap *regmap; /* For MUIC and Charger */ + struct regmap *regmap_pmic; - struct regmap_irq_chip_data *irq_data; + struct regmap_irq_chip_data *irq_data; /* For MUIC and Charger */ + struct regmap_irq_chip_data *irq_data_pmic; int irq; - - /* Device ID */ - u8 vendor_id; /* Vendor Identification */ - u8 device_id; /* Chip Version */ }; /* MAX14577 shared regmap API function */ diff --git a/include/linux/mfd/max14577.h b/include/linux/mfd/max14577.h index 736d39c..c83fbed 100644 --- a/include/linux/mfd/max14577.h +++ b/include/linux/mfd/max14577.h @@ -1,7 +1,7 @@ /* - * max14577.h - Driver for the Maxim 14577 + * max14577.h - Driver for the Maxim 14577/77836 * - * Copyright (C) 2013 Samsung Electrnoics + * Copyright (C) 2014 Samsung Electrnoics * Chanwoo Choi <cw00.choi@samsung.com> * Krzysztof Kozlowski <k.kozlowski@samsung.com> * @@ -20,6 +20,9 @@ * MAX14577 has MUIC, Charger devices. * The devices share the same I2C bus and interrupt line * included in this mfd driver. + * + * MAX77836 has additional PMIC and Fuel-Gauge on different I2C slave + * addresses. */ #ifndef __MAX14577_H__ @@ -32,7 +35,17 @@ enum max14577_regulators { MAX14577_SAFEOUT = 0, MAX14577_CHARGER, - MAX14577_REG_MAX, + MAX14577_REGULATOR_NUM, +}; + +/* MAX77836 regulator IDs */ +enum max77836_regulators { + MAX77836_SAFEOUT = 0, + MAX77836_CHARGER, + MAX77836_LDO1, + MAX77836_LDO2, + + MAX77836_REGULATOR_NUM, }; struct max14577_regulator_platform_data { diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h index 9974e38..b8f87b7 100644 --- a/include/linux/mfd/palmas.h +++ b/include/linux/mfd/palmas.h @@ -415,7 +415,7 @@ struct palmas_usb { struct palmas *palmas; struct device *dev; - struct extcon_dev edev; + struct extcon_dev *edev; int id_otg_irq; int id_irq; diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h index 51e26f3..ee80dd7 100644 --- a/include/linux/miscdevice.h +++ b/include/linux/miscdevice.h @@ -64,7 +64,7 @@ struct miscdevice { umode_t mode; }; -extern int misc_register(struct miscdevice * misc); +extern int misc_register(struct miscdevice *misc); extern int misc_deregister(struct miscdevice *misc); #define MODULE_ALIAS_MISCDEV(minor) \ |